前塵—數據連接池下的至暗之處
前言
這是分析Java反序列化系列的第四篇文章,內容的填充度已經過半。此系列的每一篇文章 都會對漏洞產生的原因進行剖析,理解事物的原理往往在攻擊時發揮奇效。
C3P0是一個開源的JDBC連接池,它實現了數據源和JNDI綁定,支持JDBC3規范和JDBC2的標準擴展。目前使用它的開源項目有Hibernate、Spring等。
序列化與反序列化
既然是反序列化漏洞必然要提起的就是序列化與反序列化,如果還有讀者對這個概念不清楚請參考文章《前塵——與君再憶CC鏈》,在Java反序列化漏洞中,序列化和反序列化是理解這些漏洞的基本條件。
導入Maven依賴
<dependencies>
<dependency>
<groupId>com.mchangegroupId>
<artifactId>c3p0artifactId>
<version>0.9.5.5version>
dependency>
dependencies>
此依賴為c3p0最新版本依賴,更新于2019年12月

最新版本沒有修復此問題
漏洞跟蹤
直接進入網上公開的鏈條類打開就是一頓分析com/mchange/v2/c3p0/impl/PoolBackedDataSourceBase

分析了這么多的漏洞鏈條,其實道理很簡單。將網上紕漏的漏洞類打開直接往下翻往下翻找到readObject()方法對其內容進行跟進就可以,三板斧直接一頓懟。
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
short version = ois.readShort();
switch(version) {
case 1:
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) {
o = ((IndirectlySerialized)o).getObject();
}
this.connectionPoolDataSource = (ConnectionPoolDataSource)o;
this.dataSourceName = (String)ois.readObject();
o = ois.readObject();
if (o instanceof IndirectlySerialized) {
o = ((IndirectlySerialized)o).getObject();
}
this.extensions = (Map)o;
this.factoryClassLocation = (String)ois.readObject();
this.identityToken = (String)ois.readObject();
this.numHelperThreads = ois.readInt();
this.pcs = new PropertyChangeSupport(this);
this.vcs = new VetoableChangeSupport(this);
return;
default:
throw new IOException("Unsupported Serialized Version: " + version);
}
}
獲取版本,使用switch case關鍵字做分支處理。這里拿到的version是1,所有走case1.
Object o = ois.readObject();
此語句反序列化出一個referenceSerialized對象,instanceof關鍵字用來測試一個對象是否為一個類的實例。com.mchange.v2.naming.ReferenceIndirector類中存在內部類ReferenceSerialized實現了IndirectlySerialized接口,所以類型比對通過。

然后調用IndirectlySerialized類的getObject方法,但是ReferenceSerialized實現了IndirectlySerialized接口。所以實際使用多態的方式調用的是ReferenceSerialized的getObject方法

ReferenceSerialized( Reference reference,
Name name,
Name contextName,
Hashtable env )
{
this.reference = reference;
this.name = name;
this.contextName = contextName;
this.env = env;
}
在ReferenceSerialized構造函數中傳入四個值進行賦值
public Object getObject() throws ClassNotFoundException, IOException
{
try
{
Context initialContext;
if ( env == null )
initialContext = new InitialContext();
else
initialContext = new InitialContext( env );
Context nameContext = null;
if ( contextName != null )
nameContext = (Context) initialContext.lookup( contextName );
return ReferenceableUtils.referenceToObject方法將( reference, name, nameContext, env );
}
catch (NamingException e)
{
//e.printStackTrace();
if ( logger.isLoggable( MLevel.WARNING ) )
logger.log( MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", e );
throw new InvalidObjectException( "Failed to acquire the Context necessary to lookup an Object: " + e.toString() );
}
}
如果contextName不為空則使用lookup,進行rmi觸發遠程調用,但是這里的contextName為空只能向下分析
return中調用了ReferenceableUtils類的referenceToObject方法將構造函數中傳入的四個值當作參數傳入繼續跟進。

reFerenceToObject根據Reference對象來獲取工廠類的名字,以及工廠類的地址,接著拿到類加載器,拿到appClassLoader(一般程序中類加載都用這個,它的上面還有jre核心類運行的加載(rt.jar)bootstrap classloader和擴展類加載ext classloader)
接著就判斷工廠類地址是否為空,不為空則去遠程地址加載工廠類,這里用到了urlclassLoader,然后通過class.forname生成一個class 類型的實例,就加載到了工廠類,即我們的惡意字節碼類
總結
個人認為此個序列化漏洞大致了解即可,因為此依賴新的架構項目已經不被廣泛使用了,并且maven可以的看到最后一次更新在2019年。
Java反序列化一直是一個老生常談的問題,理解這些原理性的知識可以更好的幫助我們找到執行鏈,你我終有一天也會發現理解事物的本質是如此重要。