Apache Shiro CVE-2016-4437反序列化漏洞学习
0x00 漏洞信息
1 | VUL = ["CVE-2016-4437"] |
0x01 复现
目标环境:
- Centos7 + vulhub[shiro/CVE-2016-4437]
为了能让docker环境上网,改写了docker-compose.yml,加了”network_mode:host“:
POC
根据https://github.com/Medicean/VulApps/blob/master/s/shiro/1/poc.py 稍微加工了下:
1 | import os |
执行结果:
vul.exp(‘touch /tmp/test2’):生成了文件
vul.poc():DNSLog记录如下
0x02 漏洞分析
调试环境搭建
如果不想像我这么麻烦,可以直接阅读下大佬的博客,我觉得讲得很好http://saucer-man.com/information_security/396.html#comment-175
基于:
- Windows + IDEA + python3.7 + git
- Centos7.8 + docker
首先准备调试环境。
调试环境搭建遇到了无数的坑,因为之前没接触过远程调试,过程如此艰难,前前后后花了2周/(ㄒoㄒ)/~~,现在记录下,也是总结。本来考虑的是直接用vulhub的docker环境进行远程调试,但是发现用的jar包,没有源代码,这一方法不考虑了;接着找了vulfocus的镜像,可能本人技术不行,命令一直运行失败(后来发现可能跟选择的ysoserial的Gadget有关);最后找到一篇saucerman写的博客,给了我启发,打算自己生成war包结合docker进行远程调试(当然也可以在本地分析,但我当练手了。。)。
1、本地生成war包
- 拉取git分支:
1 | git clone https://github.com/apache/shiro.git |
修改samples\web\pom.xml,pom.xml提供了项目依赖信息,方便下载进行包管理【Maven项目使用项目对象模型(Project Object Model,POM)来配置,项目对象模型存储在名为 pom.xml 的文件中。我觉得跟python的pip稍微有点像,都是包管理工具】。这里修改jstl【jsp标准标签库,jsp需要引用】版本为1.2,说明下为什么加版本为1.2,查了下是因为1.0与1.1没有在Maven仓库中存在,而且其使用方式都是将jstl.jar与standard.jar添加到编译路径下,并将tld文件夹中的文件(或者包含文件夹)复制到/WEB-INF,然后配置web.xml文件。
samples\web\pom.xml 1
2
3
4
5
6
7<!--修改jstl[jsp标准标签库]版本为1.2-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>接下来点击“File”的open,直接打开samples\web目录,导入文件,IDEA的maven会自动通过pom.xml下载依赖包。
等待下载构建完成,会自动生成war包,war包在如图target下
如果要本地直接进行debug分析,可以往下看,忽略【2、远程调试环境】;如果想远程调试,忽略下面直接跳到【2、远程调试环境】
右上角设置Run/Debug Configurations,添加tomcat server->local
添加tomcat,选择本地tomcat目录
设置Deployment部署包,点击右侧+,添加图片war包,点击apply,然后点击OK确定。
2、远程调试
如果本地分析的话,就直接忽略这里就好。
centos上写好Dockerfile,自己制作镜像然后远程调试。
步骤如下:
创建目录
1 | mkdir shiro_test |
Dockerfile编辑内容如下,其中ENV JPDA_ADDRESS=”12345”和CMD [“catalina.sh”, “jpda”, “run”]可以设置远程调试:
1 | FROM tomcat:8.5.46 |
用Dockerfile制作镜像,并创建容器
1 | docker build -t shiro:1.4 . |
进入容器拷贝ROOT出来
1 | docker exec -it b2e:/usr/local/tomcat/webapps/ROOT . |
把拷贝出来的ROOT导入IDEA,然后根据下图设置远程调试
调试环境搭建完成,接下来debug调试分析。
调试分析
看了几篇博文都是在org.apache.shiro.mgt.DefaultSecurityManager#resolvePrincipals开始断点,后来发现这里已经开始处理cookie中rememberMe值进行解密过程了。
用户名->Cookie:
首先在lib的shiro-core-1.2.4.jar找到AbstractRememberMeManager#onSuccessfulLogin 83、84、85行打上断点。AbstractRememberMeManager是rememberMe的处理类,onSuccessfulLogin登录成功后的处理。
访问web页面http://192.168.116.142:8080/,点击进入log in:
可以看到这里提供了几组用户名密码可以登录:
点击debug开始进行调试:
选择一组进行登录,这里我选了lonestarr,因为是RememberMe导致的漏洞,这里要勾选上,点击login:
成功弹出debug界面
onSuccessLogin方法获取到三个参数:subject、token和info。forgetIdentity处理HTTP请求和响应,接着判断rememberMe是否为真,之前勾选了所以这里是true,接着rememberIdentity处理
F7进入rememberIdentity方法,首先getIdentityToRemember返回了authcInfo.principals的值为lonestarr,赋值给principals,紧接着调用this.rememberIdentity方法
F7进入this.rememberIdentity后,首先调用this.convertPrincipalsToBytes(accountPrincipals),CTRL+左击方法名,这个方法就在下面,convertPrincipalsToBytes首先对accountPrincipals进行序列化,如果加密cipherService not null就进行加密,根据下面变量值transformationString可以知道似乎是AES/CBC/PKCS5Padding加密
F7进入convertPrincipalsToBytes,跟进encrypt方法,encrypt对序列化后的数据加密
跟进cipherService.encrypt,encrypt的两个参数,数据和key,进行加密,之前就得知AES用了硬编码,所以我们找下key
回退到上一断点
再F7进入到encrypt,到cipherService.encrypt按向下箭头,有两个方法可以进入,点击 getEncryptionCipherKey,
跟进后,CTRL+左击encryptionCipherKey
发现使用的默认key:kPH+bIxk5D2deZiIxcaaaA==
回到rememberIdentity方法,经过上面的分析我们知道到这里已经获取了remeberMe的序列化后的并且经过AES硬编码加密的字节数据bytes
接着F8调用this.rememberSerializedIdentity(subject, bytes),F7跟进。rememberSerializedIdentity主要对数据进行了base64编码,并且设置cookie值为base64编码后的数据
至此,rememberIdentity通过convertPrincipalsToBytes和rememberSerializedIdentity完成了用户名的序列化->AES硬编码加密->base64编码 数据设置给cookie。
Cookie->用户名:
在DefaultSecurityManager#resolvePrincipals 245行打上断点,然后开始debug
用burp发送请求(之前登录的lonestarr生成的cookie保存了),debug窗口弹开
跟进getRememberedIdentity,首先获取cookieRememberMeManager
rmm not null,调用getRememberedPrincipals,跟进getRememberedPrincipals
接着跟进getRememberedSerializedIdentity,对RememberMe进行base64解码并返回
F8继续往下
F7跟进convertBytesToPrincipals
调用this.decrypt,跟进decrypt方法
decrypt将加密数据使用默认key进行AES解密,是之前加密的逆过程
回到 convertBytesToPrincipals,接着进行反序列化并返回数据,此时数据已经经过了base64解码->AES解密->反序列化
接着F8,获取了最终身份:lonestarr
至此,用户名保存成cookie和cookie反解析成用户身份完成分析,反解析过程中反序列化没有验证对象,直接信任并进行反序列化从而导致可以执行命令。
0x03 问题记录
学习过程中遇到了几个问题,记录下,方便思考
- 为什么要从AbstractRememberMeManager#onSuccessfulLogin和在DefaultSecurityManager#resolvePrincipals开始打断点,我是看了大佬们的博客才知道,但是如果我自己独立分析的话肯定是要找很久或者找很久都不清楚,怎么能够在新爆出漏洞时,自己独立分析时准确找到比较好的断点?
- 利用了ysoserial的CommonsBeanutils1的Gadgets,在分析调试时没有成功,但是却在vulhub复现时成功了,为什么?两者环境有什么区别?看到有遇到可能是版本问题引起的,我没有认真分析,留下这个疑惑,有空再更新。
- 经过本次深入分析学习的过程,Dockerfile构建镜像和远程调试也有了进一步的提升,虽然过程时艰难的,但是还是收获很多
参考链接:
https://www.freebuf.com/vuls/178014.html
https://www.freebuf.com/column/220958.html
https://www.freebuf.com/articles/system/125187.html
https://www.cnblogs.com/loong-hon/p/10619616.html
https://github.com/Medicean/VulApps/blob/master/s/shiro/1/poc.py
https://www.jianshu.com/p/d168ecdce022
https://www.cnblogs.com/yangxiaodi/p/10077054.html
http://saucer-man.com/information_security/396.html
https://blog.xinpapa.com/2019/03/19/docker-tomcat-debug/
https://stackoverflow.com/questions/35584063/debugging-tomcat-in-docker-container