shiro是干什么的?
shiro提供认证、授权、会话管理、加密功能,这四个功能很难说与安全无关,4A里就占俩,会话管理和加密就更不用说了,集成了shiro后,可以靠shiro进行登录和授权
shiro的验证流程
1.集成shiro后,是有登录界面的,用户输入用户名密码进行登录,有一个很显眼的Remember Me选项是一个单选框,这个说白了就是记住会话,下一次访问url直接就进来,我想大多数用户都不愿意没事干总是手动输入用户名和密码吧,所以基本都会勾选
2.shiro去验证用户信息,并且要查看Remember Me勾选了没,所以这个会话管理这个逻辑还是稍微复杂一点的,我们可以想想如果我们自己实现记住会话应该如何操作
3.若勾选的话,就将用户身份序列化,毕竟要存起来方便下次使用嘛,然后序列化后再AES加密,在使用base64编码,这样一来又安全,占得地方又少,我们都清楚,加密最重要的不是算法,而是秘钥的妥善处理,所以在这一步AES的秘钥管理就显得格外的重要
4.将第3步处理好的内容,放入cookie的rememberMe字段中,这样的话,如果有客户端发送请求,都会带着cookie,而cookie里面带着rememberMe字段,服务器先用base64解码,再用AES解密,然后要进行反序列化,获取用户身份,这样才能判断用户的合法性,该不该保持会话
shiro550(CVE-2016-4437)
漏洞简介
清楚了shiro的工作流程,工作流程中也说了,是有反序列化操作的,既然有反序列化,就要看我们序列化的内容是否可控,也就是rememberMe字段中的内容是否可控,乍一看肯定是不行的,虽然base64相当于明文,但是AES可不是,AES是有秘钥的,值得回味的是,shiro的默认秘钥是个硬编码写在代码里面了,源码也是默认的,用户也没有更换的话,那么就给了我们构造序列化数据的机会了
影响版本
Apache Shiro < 1.2.4
漏洞复现
我们使用vulhub中的CVE-2016-4437
shiro开启远程调试
https://ares-x.com/2020/04/20/IDEA%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95Docker%E4%B8%AD%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%96%B9%E6%B3%95/
我们先看看登录过程,只要登录就会Set-Cookie,如果登录失败会在cookie里加入rememberMe=deleteMe,如果登录成功,则会给rememberMe再来一大段数据,也就是AES+base64后的数据
登录失败
登录成功
加密过程我们就不分析了,直接看攻击也就是解密过程,不管失败和成功,只有要rememberMe这个字段出现,是不是就意味着有可能会有这个漏洞了,只要AES的秘钥还用的默认的,那么就可以直接利用,我们使用工具,输入目标地址
http://192.168.174.134:8080/login
我连接ceye网络有点问题,就用dnslog了
如果攻击成功,就会出现输入命令的地方,我们来看一看这个工具的攻击过程,先探测AES的秘钥是什么,然后找利用点和cc链
秘钥也是提前准备了很多,还有个jsp马,也就是用于上传的
idea开启调试模式以后,输入whoami,idea已经
shiro-web-1.2.4.jar!\org\apache\shiro\web\servlet\AbstractShiroFilter.class的doFilterInternal方法,接收到了我们的请求,调用createSubject方法创建subject对象,这就是工具发出去的序列化的数据,保存在rememberMe字段里
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\DefaultSecurityManager.class的createSubject方法,调用了resolvePrincipals方法
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\DefaultSecurityManager.class的resolvePrincipals方法,调用了getRememberedIdentity方法
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\DefaultSecurityManager.class的getRememberedIdentity方法,调用了getRememberedPrincipals
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class的getRememberedPrincipals方法,为什么会到这个方法,因为rmm的类型是接口RememberMeManager,而类AbstractRememberMeManager实现了接口,并重写了方法,调用getRememberedSerializedIdentity方法,获取序列化的身份信息
shiro-web-1.2.4.jar!\org\apache\shiro\web\mgt\CookieRememberMeManager.class的getRememberedSerializedIdentity方法,为什么会到这个方法,因为在类AbstractRememberMeManager中,这个方法getRememberedSerializedIdentity是一个抽象方法,只有继承了这个类,重新了这个方法,才会走到那里,这里先base64解码
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class的getRememberedPrincipals方法,再回到这里调用convertBytesToPrincipals方法,这里会调用decrypt方法,对数据进行解密
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class的decrypt方法,调用了getCipherService方法来获取秘钥,这里获取的秘钥就是这个硬编码咯
shiro-core-1.2.4.jar!\org\apache\shiro\mgt\AbstractRememberMeManager.class的getRememberedSerializedIdentity方法,base64也解码了,AES也用默认秘钥解密了,还剩一步就是反序列化了
shiro-core-1.2.4.jar!\org\apache\shiro\io\DefaultSerializer.class的deserialize方法,调用readObject方法,触发
补丁分析
这里不再写死秘钥了,秘钥的获取方式改为动态获取了128位
shiro721(CVE-2019-12422)
漏洞简介
由于shiro550的补丁不再把秘钥写死了,所以我们的方向现在变了,秘钥拿不到了,只能退而求其次,研究算法了,反正我们的目标是在不知道秘钥的前提下,构造出之前那样的序列化数据
漏洞原理还得是蜗牛学苑的老师说的比较详细
影响版本
Apache Shiro < 1.2.4
对称加解密原理
加密的时候,先将明文分组,除了初始向量之外,每组先和上一组密文异或,再加密,最后得到的每组密文组合起来,由于依赖上一组,所以加密过程为串行,较为耗时
解密的时候,先将密文分组,除了初始向量之外,每组先解密,再和上一组密文异或,最后得到的每组明文组合起来,由于是和上一组密文进行异或,密文分组万就得到了,所以解密过程为并行,较为省时
Padding Oracle攻击原理
Padding就是填充,由于在分组时,空位置是不可避免的,那此时只能进行填充(分组,每组128bit,16字节)
假设每组8字节
1、最后一个明文分组填满,那就加额外分组,每个字节的值就是8,也就是16进制的0x08
2、最后一个明文分组只有7个字节,那就把第八个字节填为0x01
3、最后一个明文分组只有6个字节,那就把第七、八个字节填为0x02
当密文解密后,会检查最后一个字节
如果是0x01,那就看最后一个字节的值是不是0x01
如果是0x02,那就看倒数第一个和倒数第二个字节的值是不是0x02
以此类推,如果不是的话,就会报填充错误,会有异常或者延时
异或运算
相同为0,不同为1
1、a xor 0 = a
2、a xor a = 0
3、若a xor b = c,则b xor c = a,a xor c = b
4、若a xor b = c,则a xor b xor c = 0,c xor c xor d = d
Padding Oracle
shiro是提供加解密服务的,那我们利用的就是这个“解密器”
利用我们提供的密文,让shiro进行分组解密,利用其解密后对明文的填充校验,来计算中间值,进而得出明文值,再通过明文值和中间值,篡改序列化数据为我们所需数据
如果是解密过程,我们已知的就是密文了,我们需要的就是推导出中间值和明文
专业术语
Plaintext:明文,Plaintext[-n],明文分组中的倒数第n个字节
m:中间值,IV和Plaintext异或得到
IV:初始向量
G_IV:构造的IV
推导过程
先从第一组开始
1、我们构造解密的G_IV,将G_IV的前7个字节全部设置为0,这样不会改变明文的前7个字节,G_IV[-1]范围设置0x00~0xFF(一个字节8位,为256种可能),进行爆破,并且结果为0x01,不会报填充错误,如果不为0x01,说明这是完整明文,完整明文要增加额外分组,自然会报错了
G_IV[-1](0x00~0xFF) xor m[-1] = 0x01
最终得到一个G_IV[-1]使上述公式成立
2、反推m[-1] = G_IV[-1] xor 0x01
中间值就得到了
3、再反推明文(IV = Ciphertext[0],都包含在密文里了)
Plaintext[-1] = IV[-1] xor m[-1]
CBC翻转攻击
当我们经过不懈努力,得知了各种Plaintext和m,加入此时只需要修改明文分组3的内容,那么就要修改密文分组2的内容,因为毕竟是密文分组2要和中间值3进行异或,得到我们想要的明文分组3,如果密文分组2被修改了,解密后的中间值2也就被损坏了,既然我们之前能得知各种m,那么此时就构造出预期的损坏的中间值2,这样就达到了操纵明文的效果了
漏洞复现
环境使用vulfocus的
docker pull vulfocus/shiro-721
docker run -d -p 8080:8080 vulfocus/shiro-721
exp使用
https://github.com/inspiringz/Shiro-721
先生成payload.class
java -jar ysoserial.jar CommonsBeanutils1 "touch /tmp/1" > payload.class
再把payload.class放到exp文件夹开始爆破,刚刚只一组,就有很多种情况,所以这个执行结果很漫长
python2 shiro_exp.py http://192.168.174.134:8080/login.jsp LoACq+2/GrWZm6xfYsHbFVfqR1PI/2COphZ2JcpQPd/j/0qfQv1dx/xDzt0uIXVitv2apTvuMZhXd1OZXmGKn6cXBMRDkCPboh88Bce3fVZglp7rITchcemEKlg2zecgXdvcqsUe0/wAo8IQnXVvdlFmb3AAbekpvtTMFj2e/0SaMIHpkY6yBnP4v5Q2biIlXjlmJZRpv/OVfgVhWEndAOqI3dggN8mNEGbU/9xUCeu0WR3LL/w3NqbiK2hD4jB204YCGzsxNN21TuHEafwk3HMa7xOlM0u52B4MlFP2hXw6fUX06oe0yp1UM3v6QpON2xzSO21SMAn4mt+NQAwhsBh8OaaniRsR4JhUIlMLnEjiZ8BTjzrSiK4MGbrg68NPebhOymA09qU7ZNVZjRE6y04FE73FcwshOnPK5ok6Unmxnr0n6dZIc1KKXRRNpuj/l19vxWmF5UETB9QHfFUHEZYpMo74zSKDoY5DoaWA+hN1Z5iKFoEKoQtkDrL1TBLz payload.class