shiro漏洞原理介绍

参考链接

https://www.freebuf.com/articles/web/364631.html

https://xz.aliyun.com/t/8997

shiro-550

Apache Shiro框架提供了记住密码的功能,用户登录成功后会生成经过加密并编码的cookie,在服务端对rememberMe的Cookie值,先base64解码再AES解密再反序列化,就导致了反序列化RCE漏洞。

调用逻辑

示例代码来源于vulhub镜像源码

加密部分

  1. 首先运行代码,在Bean中查看shiroConfig中的调用逻辑,我们知道shiro的漏洞就存在于RememberMe功能中,我们跟进cookieRememberMeManager方法。

image-20230607160535229

  1. 可以看到这个方法返回的是一个CookieRemberMeManager对象,我们跟进CookieRememberMeManager类。

image-20230607161415291

  1. 可以看到CookieRememberMeManager方法在Http请求体中设置了字段名为rememberMe的Cookie,CookieRememberMeManager方法所在的CookieRememberMe类继承了AbstractRememberMeManager类,我们跟进AbstractRememberMeManager类。

image-20230607161639678

  1. 可以看到一个私有的默认base64编码加密密钥kPH+bIxk5D2deZiIxcaaaA==,那么这个密钥用来做什么呢,根据另一个私有成员cipherService猜测是AES加密密钥,接着向下看发现这个加密密钥被默认设置了,也就意味着每一次加密使用的都是同一个密钥,这会导致严重的安全问题。我们向下看明文和密文的来源和去向。

image-20230607162625262

  1. 可以看到encrypt方法中对一个序列化字节数组进行了加密,但这里我们无法找到这个序列化字节数组的详情,于是在这里下了一个方法断点,通过调试来回溯。

image-20230607165348048

  1. 返回上一帧,发现调用位置是在convertPrincipalsToBytes方法,我们看到principals其实是我们的用户名”admin”, 这里将用户名转换成了字节数组然后进行了加密,再继续返回上一帧看看。

image-20230607182503867

  1. 可以看到rememberIdentity对前端获取的token进行了处理,以principal对象形式传递给后续的加密步骤。

解密部分

  1. 类似加密部分的第5步,我们在AbstractRememberMeManager类声明中可以找到decrypt方法,它对加密数据进行了解密,返回 了一个序列化字节数组,这个字节数组内容大概率是一个principal对象(根据前面的调试猜测),在此设置方法断点进行调试。

image-20230607184706824

  1. 单步步过直到convertBytesToPrincipals方法,发现了反序列化的位置就在convertBytesToPrincipals方法中,将解密的字节数组进行反序列化。

image-20230607185040888

  1. 单步步入deserialize方法中,发现这里创建了一个字节数组输入流对象用来存储序列化的字节数组,再通过readObject方法进行反序列化,这也是反序列化漏洞触发的接口位置。

image-20230607200615343

修复手段

  1. 更新shiro组件版本
  2. 不使用默认的加密密钥,一次一密最稳妥

shiro-721

Apache Shiro cookie中通过AES-128-CB模式加密的rememberMe字段存在问题,用户通过Padding Oracle加密生成的攻击代码来构造恶意的rememberMe字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行。

shiro-721 的调用逻辑和shiro-550的方式非常类似,差别最大的就是AbstractRememberMeManager类中,AES密钥不再使用硬编码模式了,而是通过KeyGenerator进行了密钥生成,这也意味着我们难以获取密钥,无法直接破解这个AES加密系统,但是攻击者提出了另一种思路,即Padding Oracle Attack(填充提示攻击),可以实现无需加密密钥直接构造密文。

Padding Oracle Attack

参考链接:

https://ctf-wiki.org/crypto/blockcipher/mode/padding-oracle-attack/

利用该漏洞可以破解出密文的明文以及将明文加密成密文,该漏洞存在条件如下:

  1. 攻击者能够获取到密文(基于分组密码模式),以及IV向量(通常附带在密文前面,初始化向量)
  2. 攻击者能够修改密文触发解密过程,解密成功和解密失败存在差异性

加密算法:AES

工作模式:CBC

填充方式: PKCS5Padding

密钥长度: 128bit

image-20230607221514892

PKCS5Padding

将数据填充到8的倍数,填充数据计算公式是,假如原数据长度为len,利用该方法填充后的长度是 len + (8 - (len % 8)), 填充的数据长度为 8 - (len % 8),块大小固定为8字节,如果刚好长度满足8字节,则新增一个全为0x08的块。所有填充的值为需要填充的字节数,这种填充方式只有在填充字符为0x01-0x08之间才是合法的。

攻击思路

image-20230607233135913

根据CBC加密模式,解密时初始IV将与经过key解密的密文块异或得到明文块(后面我们称经过key解密的密文块为中间块),如果我们能够不获取key而直接获得中间块,那么我们就可以破解出明文。

由于PKCS5Padding的特性,每个填充块有这么几种可能:

  1. XXXXXXX 0x01
  2. XXXXXX 0x02 0x02
  3. XXXXX 0x03 0x03 0x03
  4. XXXX 0x04 0x04 0x04 0x04
  5. XXX 0x05 0x05 0x05 0x05 0x05
  6. XX 0x06 0x06 0x06 0x06 0x06 0x06
  7. X 0x07 0x07 0x07 0x07 0x07 0x07 0x07
  8. 0x08 0x08 0x08 0x08 0x08 0x08 0x08 0x08

当我们提交一个IV时,服务器会用中间值与它异或得值然后先校验填充情况而非直接比对明文。

此时服务器会返回两种情况:正确的密文返回200或者302;错误的密文返回500。

  1. 假定明文最后一位填充为0x01,那么我们可以暴力枚举IV最后一个字节,若服务器响应200,则对应的中间块字节为该IV一个字节异或0x01。
  2. 假定明文最后两位填充为0x02,那么我们的IV最后一个字节固定为上次计算出的中间块字节异或0x02,暴力枚举IV倒数第二个字节,若服务器响应200,则对应的中间块字节为该IV倒数第二个字节异或0x02。

按照上述的攻击方式可以逐步推导出整个中间块,从而计算出第一个明文块,用同样的方式可以直接计算出所有明文。

修复手段

  • 更新shiro组件版本
  • 由于Padding Oracle Attack需要通过服务器响应进行判断,我们可以对服务器流量进行限制,将短时间多次访问的IP进行限制

shiro识别与漏洞发现

环境安装

使用docker一键部署

image-20230525203247649

shiro漏洞发现

记住密码功能

返回包set cookie位置显示字段rememberMe=XXX

如果返回包没有,在请求包的cookie中加上rememberMe字段,如果返回包里返回了,则使用了shiro

shiro漏洞检测

使用shiroscan工具进行检测

image-20230525215041885

在DNSLog上查看回显,可是太卡了没办法看到。

获取shell

使用python脚本进行将获取的shell反弹到vps上

image-20230526152826228

查看vps的情况,发现反弹了shell

image-20230526153031190

总结

不管是shiro-550还是shiro-721,导致反序列化接口暴露的原因都是不安全的加密方式,一个是直接使用了硬编码模式,将默认密钥写入了源码中,一个能够根据工作模式和填充方式组合的缺陷绕过获取密钥直接暴力破解出明文。

本文采用CC-BY-SA-3.0协议,转载请注明出处
Author: Sally