Java安全之C3P0链利用与分析
前言
C3P0是JDBC的一个连接池组件
JDBC
“JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。”
- 连接池
“我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。”
C3p0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。 使用它的开源项目有Hibernate、Spring等。
利用链
URLClassLoader 也叫 http base链
hex base
- jndi
一共就是这三个利用链(下面是利用链分析)
URLClassLoader(http base)
先搭建环境来测试
1 | <dependencies> |
先导入maven
然后写个恶意类
1 | package org.example; |
特别重要的一点就是别在idea里边进行编译,还有就是编译的时候别加上package (血的教训)
接着写个demo(记得导入ysoserial.jar)
1 | package org.example; |
然后在编译的文件目录下开个python服务
分析链子
跟进getObject这个类
这里话是对我们传进的url进行分解 分解为url和classname
反射创建了一个PoolBackedDataSource实例对象,然后反射将connectionPoolDataSource的值设置为PoolSource类的实例,传递className和url参数。即我们传入的远程地址和类名。
然后我们直接来跟这个反序列化入口com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject
在入口点打个断点进行分析
判断反序列化的参数类型是否是IndirectlySerialized,是的话进入getobject()方法
然后接着进入这个类里边
获取到类名和url
然后接着进入到这个URLClassLoader方法
关键点来了
一般来说这个三个方法是成对出现的
(这里的一些参数能直接获取到是因为在序列化的时候进行了赋值) 参考文章
ForName是为了初始化 newInstance是为了实例化 因为我们构造的恶意代码是个静态代码块,在初始话的时候就会自动执行了,不需要下面的实例化了
这里话要重新实例化一遍URL是因为这里的URLClassloader需要接收的参数是URL数组
Gadget
1 | com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject |
jndi
POC
1 | package org.example; |
分析链子
因为这个是可以用来打fastjson的,所以说这里的关键就是JndiRefConnectionPoolDataSource里面的set方法
然后跟进这个方法
然后发现会给JndiName赋值之后会调用下图的这个方法
在这个jndi给赋值之后
接着调用另一个set方法
然后调用到WrapperConnectionPoolDataSource的setLoginTimeout的方法里面
这里调用到这个里的方法是因为 wcpds是属于这个类的
然后就接着跳到了这个类的JndiRefForwardingDataSource的setLoginTimeout方法里
重点就是这个inner()方法了 跟进他
然后发现它会调用这个dereference方法 跟进去
最后就会在这个dereference()方法里调用lookup方法来执行我的rmi恶意服务代码
最后测试一下代码可行性
在kali搭建一个rmi恶意服务
发现是会弹计算器的
hex base(二次反序列化)
这个方法可以用来进行二次反序列化的绕过
如果不出网,而且是fastjson或jackson的情况,可以用这个Gadget。
这里其实就是常听到的就是用C3P0二次反序列化打Fastjson,因为像Fastjson和Jackson在反序列化时都会触发setter方法的执行,而C3P0中userOverridesAsString的setter会将HexAsciiSerializedMap开头的hex字符串进行解码再去触发Java原生的反序列化
- 其实简单来说就是这个序列化的时候会先生成一堆16进制的字符串,然后这些字符串就可以绕过黑名单检测(这就是二次反序列化的用法)
先写个demo来触发
(注意这里绕过HexAsciiSerializedMap后面跟着的16进制字符串不对的话,是不会进入到序列化那个类里边的,在转化为字节流的时候就会告诉你失败)
接着来到这个类里边
给this.userOverridesAsString赋值
然后最后会调用到这个函数
这里的话会先截取HexAsciiSerializedMap后面跟着的16进制,然后将其转化为字节数组
然后接着进入到这个formByteArray函数里
接着进入到这个deserializeFromByteArray这个函数里
最后在这里进行反序列化
(所以说利用方法就是将序列化的结果转化为16进制放在HexAsciiSerializedMap的后面,然后传入wrapperConnectionPoolDataSource.setUserOverridesAsString())
这样就可以进行黑名单绕过了
在fastjson,jackson等环镜下,userOverridesAsString属性可控,导致可以从其setter方法setuserOverridesAsString开始到最后deserializeFromByteArray对其调用readObject进行反序列化,造成反序列化漏洞
这里的有一道题会来讲解这个c3p0二次反序列化的用法
































