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二次反序列化的用法