JDK7u21反序列化Gadgets - 淚笑
2020-2-23 18:9:0 Author: www.cnblogs.com(查看原文) 阅读量:5 收藏

原文https://l3yx.github.io/2020/02/22/JDK7u21%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadgets/#more

一开始是学习FastJson反序列化的POC,然后发现欠缺不少知识,遂来补一下,收获良多,总结下笔记

所谓JDK反序列化Gadgets就是不同于利用Apache-CommonsCollections这种外部库,而是只利用JDK自带的类所构造的

先下载并配置好JDK7u21

Javassist

为了理解POC构造过程,还需要学习一些前置知识,Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含 Java 类或接口,Javassist 就是一个用来 处理 Java 字节码的类库

pom.xml

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.19.0-GA</version>
</dependency>

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.*;
import java.util.Arrays;

public class Demo {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();

将D:/Evil/Evil.calss拖入IDEA即可反编译,可以看见javassist动态构建出了如下类

image-20200222150342279

至于为什么要继承AbstractTranslet,和构造函数中写命令执行的payload就涉及到下面POC的构造,暂时只需要了解javassist的大概功能

TemplatesImpl

之前参考网上分析文章的POC是从ysoserial中修改得来的,代码中使用了ysoserial的一些类,我修改了一下将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
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.*;
import java.lang.reflect.Field;

public class Demo {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();

主要就是利用了TemplatesImpl,向其中的_bytecodes属性赋值了一个恶意类,最终该恶意类被实例化并且调用了构造函数中的命令执行payload。javassist在这里的作用呢其实主要就是构建这么一个恶意类,并且得到其字节码用以给TemplatesImpl相关属性赋值,所以可以自行编译一个恶意类并读入字节码来使用

但会发现这里其实反序列化TemplatesImpl后还需要调用getOutputProperties()方法才能触发,不过在FastJson中已经可以形成完整利用链

getOutputProperties()函数下断点,跟踪一下执行过程

强制进入该函数

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer

image-20200222160020838

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance

这里得到POC中两项属性的构造条件,即_name不能为null,_class为null,然后进入defineTransletClasses()

其实最终的触发点就在380行_class[_transletIndex].newInstance()defineTransletClasses()是对_class_transletIndex赋值

image-20200222160124453

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses

image-20200222160514649

代码比较长这里就直接复制出来了

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
private void defineTransletClasses()
throws TransformerConfigurationException {

defineTransletClasses()执行完以后,回到getTransletInstance(),此时_class[_transletIndex]已经为Evil类的一个类对象,调用newInstance()实例化Evil即可触发该类构造函数或者静态代码块中的代码

image-20200222160756476

所以总结以上条件,便可理解TemplatesImpl的构造

1
2
3
4
setFieldValue(templates,"_bytecodes",targetByteCodes);
setFieldValue(templates,"_class",null);
setFieldValue(templates,"_name","xx");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

动态代理

以上POC是需要反序列化TemplatesImpl类并调用其getOutputProperties()方法才能触发,即可以放入FastJson的反序列化处,但若没有触发getOutputProperties()的点,就需要寻找其他手段

代理是为了在不改变目标对象方法的情况下对方法进行增强,比如,我们希望对方法的调用增加日志记录,或者对方法的调用进行拦截

假设有一个Person类实现了IPerson接口中的say方法,但现在要在say方法前后实现一些逻辑,那么借助动态代理实现如下

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
public static void main(String[] args) throws Exception {
Person person = new Person();
Handler handler = new Handler(person);

AnnotationInvocationHandler

AnnotationInvocationHandler就是一个InvocationHandler的实现类,也在下面的POC中起到关键作用

先贴出整理好的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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.*;
import javassist.*;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;

public class Demo {

可见最后unserialize(obj)只是反序列化了一个LinkedHashSet类就触发了命令执行

Java在反序列化的时候会调用ObjectInputStream类的readObject()方法,如果被反序列化的类重写了readObject(),那么该类在进行反序列化时,Java会优先调用重写的readObject()方法

LinkedHashSet没有readObject()但是继承自HashSet

image-20200222200618469

HashSet实现了Serializable接口并且有readObject()方法,所以在反序列化LinkedHashSet时会调用其父类HashSetreadObject(),可以在该函数处下断点运行POC进一步跟踪调试

image-20200222200653974

java.util.HashSet#readObject

image-20200222200740551

到309行的逻辑是将POC中add到settemplatesproxy加入到map中,

image-20200222201701982

PRESENT是一个常量,就是一个新的object对象

image-20200222225202662

继续跟进put方法,会在第二次调用map.put时进入下面的475行的位置,即现在传入的keyproxy

java.util.HashMap#put

image-20200222203051744

这段代码本意是判断最新的元素是否已经存在的元素,如果不是已经存在的元素,就插入到table中,e.key为前一个元素即templates,key为当前元素proxy

table[i]就是一个键为我们构造的templates的Map

image-20200222220213982

当前的e.keykey,一个是templates,另一个是POC中的proxy,显然不同,(k = e.key) == key为false

image-20200222220451248

这条链想要完成是需要进入key.equals(k)的,依据短路特性,那么必须要e.hash == hash为true,也就是需要满足 hash(templates)== hash(proxy),看起来貌似不可能,但漏洞作者确实做到了(大写的佩服)

这里hash的绕过方法就暂时放在下面,先接着跟踪key.equals(k)

image-20200222203511373

由于POC中使用动态代理,这里调用Templates.equals()就会进入handlerinvoke

sun.reflect.annotation.AnnotationInvocationHandler#invoke

var1就是上图中的keyvar2equals方法对象,var3是传入的参数数组,即上图中的k(TemplatesImpl)

image-20200222204446774

继续跟入equalsImpl

sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl

image-20200222210434857

分析之前先看一下这个类的相关方法和属性

首先是构造函数

sun.reflect.annotation.AnnotationInvocationHandler#AnnotationInvocationHandler

image-20200222210525096

在构造handler时ctor.newInstance(Templates.class, map)

即这里的this.typethis.memberValues分别是Templates.classmap

sun.reflect.annotation.AnnotationInvocationHandler#getMemberMethods

image-20200222210905902

并未对this.memberMethods赋值,所以这里进入if分支,最后返回的是this.type的所有方法,即Templates的所有方法

sun.reflect.annotation.AnnotationInvocationHandler#asOneOfUs

判断var1对象若是一个AnnotationInvocationHandler实例的话则转换为AnnotationInvocationHandler

image-20200222211521556

然后接着看equalsImpl

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
private Boolean equalsImpl(Object var1) {

既然调用了Templates中的所有方法,自然包括getOutputProperties(),即完成了命令执行

Hash绕过

image-20200222203511373

java.util.HashMap#hash

hash()中调用了对象本身的hashCode()

image-20200222230037778

调用hash(templates)的时候,这个类没有重写,调用的是templates默认的hashCode()方法

当调用hash(proxy)的时候,则会跳到AnnotationInvocationHandler.invoke()

sun.reflect.annotation.AnnotationInvocationHandler#invoke

image-20200222223127808

sun.reflect.annotation.AnnotationInvocationHandler#hashCodeImpl

该方法会从memberValues中进行遍历,并且依次计算key.hashCode(),而这个memberValues是我们在初始化AnnotationInvocationHandler的时候传入的map

image-20200222223209538

sun.reflect.annotation.AnnotationInvocationHandler#memberValueHashCode

image-20200222230700698

所以

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

相当于

var1 = 127 * map中键的hashCode ^ map中值的hashCode

POC中构造map.put("f5a5a608", templates),而字符串的hashCode为0

所以

var1 = 127 * 0 ^ templates的hashCode

var1 = templates的hashCode

map.put的位置问题

仔细观察POC会发现,并没有在创建一个HashMap后就立即插入数据,而是把map.put("f5a5a608", templates)放在了set.add之后

image-20200223000229937

如果放在set.add之前会直接在本地触发命令执行,并且得到的序列化之后的数据不能反序列化成功

image-20200223000741750

java.util.HashSet#add

这是因为add方法中会直接调用map.put,然后后面的过程就同之前分析的一致了

image-20200223001156031

参考

JDK反序列化Gadgets 7u21

JDK7u21反序列化漏洞分析

秒懂Java动态编程(Javassist研究)

Java动态代理-实战


文章来源: https://www.cnblogs.com/leixiao-/p/12353006.html
如有侵权请联系:admin#unsafe.sh