Apache Shiro 1.2.4 反序列化漏洞
Apache Shiro 1.2.4 反序列化漏洞
漏洞描述
Apache Shiro 1.2.4 反序列化漏洞即shiro-550反序列化漏洞。Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
工作原理
Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie。cookie的key为RememberMe,cookie的值是经过对相关信息进行序列化,然后使用aes加密,最后在使用base64编码处理形成的。
Shiro记住用户会话功能的逻辑为:
获取RememberMe的值 —> Base64解密 —> ASE解密 –> 反序列化
在服务端接收cookie值时,按照如下步骤来解析处理:
1、检索RememberMe cookie 的值
2、Base 64解码
3、使用AES解密(加密密钥硬编码)
4、进行反序列化操作(未作过滤处理)
在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。
漏洞原理
因为在反序列化时,不会对其进行过滤,所以如果传入恶意代码将会造成安全问题
在 1.2.4 版本前,是默认ASE秘钥,Key: kPH+bIxk5D2deZiIxcaaaA==,可以直接反序列化执行恶意代码。而在1.2.4之后,ASE秘钥就不为默认了,需要获取到Key才可以进行渗透
漏洞特征: shiro反序列化的特征:在返回包的 Set-Cookie 中存在 rememberMe=deleteMe 字段
影响版本
- Apache Shiro <=1.2.4
shiro特征
- 未登陆的情况下,请求包的cookie中没有rememberMe字段,返回包set-Cookie里也没有deleteMe字段

- 登陆失败的话,不管勾选RememberMe字段没有,返回包都会有rememberMe=deleteMe字段

- 不勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段。但是之后的所有请求中Cookie都不会有rememberMe字段

- 勾选RememberMe字段,登陆成功的话,返回包set-Cookie会有rememberMe=deleteMe字段,还会有rememberMe字段,之后的所有请求中Cookie都会有rememberMe字段

判断网站是否使用shiro
找到网站登录的地方,随便输入账号密码抓包(一定要输入点击登录),看返回包是否有remembeMe字段
如果以上没有返回remembeMe字段还可以尝试在请求包中的cookie中加入 rememberMe=1 ,来查看返回包是否有rememberMe=deleteMe字段。如果cookie字段有值则先清空
如我们直接访问登录的页面不进行登录,此时返回的数据包是没有remember字段的

这时,我们手动加上一个cookie:rememberMe=1,注意cookie要放在Upgrade的上面,则返回了remember字段。说明使用了shiro框架

shiro漏洞环境搭建
1 | |
进入http://101.x.x.x:10005
这里使用工具,因为自己构造利用很是复杂
发现了AES密钥

点击检测当前利用链
然后点击命令执行

直接获取到root权限
拒绝脚本小子–来点代审!
JAVA反序列化与序列化
把对象转换为字节序列(字符串)的过程称为对象的序列化;把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中(持久化对象);
- 在网络上传送对象的字节序列(网络传输对象)。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接受方则需要把序列再恢复为java对象。
JAVA序列化实例
例子:文件名为People.java
1 | |

上述代码定义了People类,并且实现了Serializable接口,我们便可以对其进行序列化和反序列化操作:
下面的代码是Test.java
1 | |

运行Test.java
代码运行结果:
1 | |
序列化过程将字节流保存在当前目录下的People.txt中,我们可以在People.txt看到序列化后的二进制对象(其中开头的aced 0005是Java序列化文件的文件头):
反序列化漏洞
回想一下CTF比赛经常遇到的PHP的反序列化漏洞,反序列化对象时会调用类的魔法函数__construct()(创建对象时触发),我们可以构造pop链来控制(改造)__construct()函数,从而反序列化时执行我们需要的操作,Java也是类似。
在上面的代码中,我们通过调用readObject()方法来从一个源输入流中读取字节序列,再把他们反序列化为一个对象,那么我们如果控制了此类的readObject()方法会怎么样?为了验证想法,我们修改一下People类,重写其readObject()方法:
1 | |

切换到Test.java,运行程序,当执行People people1 = (People) ois.readObject();语句时会调用People类的readObject方法,弹出计算器:
由此可知控制了类的readObject方法便可以再反序列化该类时执行任意操作,事实上大多数Java反序列化漏洞可追溯到readObject方法,通过构造pop链最终改造readObject()方法。
源码审计分析
Apache Shiro是一个Java安全框架,执行身份验证、授权、密码和会话管理。2016年网络中曝光Apache Shiro1.2.4以前的版本存在反序列化漏洞,尽管该漏洞已经曝光几年,但是在实战中仍然比较实用。
影响版本
Apache Shiro <= 1.2.4
漏洞原理
Apache Shiro框架提供了记住我的功能(RememberMe),用户登录成功后会生成经过加密并编码的Cookie。Cookie的key为RememberMe,Cookie的值是经过对相关信息进行序列化,然后使用AES加密,最后在使用Base64编码处理形成的。
在服务端接收Cookie值时,按照如下步骤来解析处理:
- 检索RememberMe Cookie的值;
- base64解码;
- 使用AES解密(加密密钥硬编码);
- 进行反序列化操作(未作过滤处理)。
在调用反序列化时未进行任何过滤,导致可以触发远程代码执行漏洞。
IDEA部署环境
下面再本地IDEA部署Apache shrio 1.2.4 漏洞环境,以便于进行漏洞动态调试分析。
首先在Github下载项目源码:
1
2https://github.com/apache/shiro
https://github.com/apache/shiro/tags?after=shiro-root-1.2.6-release-vote1
编辑shiro/samples/web路径下的pom.xml文件,给jstl指定版本:

使用IDEA导入此MVN项目,导入上面修改的pom.xml文件即可


- 等待IDEA自动下载并导入完项目依赖的包,build完成后项目结构如下:

注意,pom.xml里面的配置会让程序自动下载shiro-core依赖包(后面程序加断电调试会用到该部分文件)
- 接着设置run/debug configurations,添加本地Tomacat环境(需要提前在本地安装Tomcat环境):


- 添加项目War包samples-web.war进Tomcat中:

附:此处该War包之所以存在,是因为它也是前面pom.xml配置文件设置的自动下载到本地的:

- 配置完以上的准备工作,就可以直接run运行程序了:

运行成功后浏览器自动打开目标程序站点,本地环境部署到此结束:
- 访问登录页面进行已提示账户的登录,抓包可见remerberme字段:


- 为了开始调试该程序,先停止程序运行,然后在外部库中找到shiro-core-1.2.4的jar包,打开RememberMeManager.class文件并在onSuccessfulLogin函数前加断点,然后点击Debug按钮开始在调试模式下运行程序:

- 接着在Web端登录账户root/secret,勾选上Remember Me的按钮,程序会停在断点处,然后便可以开始正式的Debug漏洞调试了;


- 以上项目工程相当于是只导入 samples-web 文件夹,实际上也可以直接在 IDEA 导入在 Github 下载的完整的 shiro-shiro-root-1.2.4 源码工程文件夹(以maven项目打开),待 Build 自动下载完所需的依赖包后,同样的步骤配置 Tomcat,然后直接找到 core 文件夹下的RememberMeManager.java 文件并在 onSuccessfulLogin 函数前加断点:


- 接着在Debug模式下运行程序,也可以在断点处拦截程序,进行调试:

后面的审计分析将基于上述导入所有项目源码的工程项目,因为相比于第一种仅导入samples-web文件夹并通过引入shiro-core-1.2.4的jar包来调试class文件的方式,直接审计全部java源码将更为直观。
序列化过程分析
下面开始正式调试分析Apache Shiro框架在登录过程中生成序列化Cookie对象的过程。
- 首先看下登录请求发送后断点停留的onSuccessfulLogin函数:

程序首先调用forgetIdentity构造方法处理request和response请求,包括在response中加入cookie信息,然后调用rememberIdentity函数,来处理cookie中的rememberme字段。
- 我们按F8来Step Over跨过forgetIdentity构造方法,然后F7来Step Into跟进下rememberIdentity函数:

可以看到,rememberIdentity函数首先调用getIdentityToRemember函数来获取用户身份,这里也就是”root”。
- 接着我们F7跟进rememberIdentity构造方法:

- 上面调用了convertPrincipalsToBytes方法将accountPrincipals也就是”root”转换为字节形式,跟进该方法查看内部如何转换:

- 转换过程是先序列化用户身份”id”,在对其进行encrypt加密,进一步跟进encrypt函数查看加密方式:

- encrypt函数就是调用AES加密对序列化的“root”进行加密,加密的密钥由getEncryptionCipherKey()得到,手动点击跟进getEncryptionCipherKey()函数会发现其值为常量(即密钥硬编码):

- Shift+F8进行Step Out步出,返回到rememberIdentity函数:

- 跟进rememberSerializedIdentity函数查看后续转换流程,发现该函数对上述root的AES加密后的序列化值进行base64编码后,设置到cookie中:

到这里我们可以梳理下上述整个Cookie的生成过程,当我们勾选上Remember Me选项后,以root身份登录,后端会进行如下操作:
1、 序列化用户身份”root”,得到值A;
2、 对root的序列化值A进行AES加密(密钥为硬编码的常量),得到值B;
3、 base64编码上述计算的结果B,得到值C;
4、 将值C设置到response响应包中cookie的rememberme字段。

恢复程序,清除断点
抓包查看
对应上了
反序列化过程分析
以上已经调试分析完shiro生成Cookie字段的序列化、加密过程,下面来进一步调试分析下Cookie字段的反序列、解密过程。
- 将断点打在org.apache.shiro.mgt.DefaultSecurityManager#getRememberedIdentity函数处,然后发送一个带有rememberMe Cookie的请求:

- 跟进查看getRememberedPrincipals

- 跟进getRememberedSerializedIdentity函数,发现函数提取出cookie并将其进行base64解码:

- Step Out 返回到getRememberedPrincipals函数,继续跟进到converBytesToPrincipals函数,发现其对cookie进行AES解密和反序列化:


- 同理再依次跟进查看 AES 解密函数 decrypt、反序列化函数 deserialize,如下:


至此 Cookie 的反序列化、解密流程分析完毕,整个流程大致为:
- 读取 cookie 中 rememberMe 值;
- base64 解码;
- AES解密;
- 反序列化
其中 AES 加解密的密钥为常量且反序列化过程没有进行过滤,于是我们可以手动构造rememberMe 值,改造其readObject()方法,让其在反序列化时执行任意操作。