JAVA安全—Shiro反序列化DNS利用链CC利用链AES动态调试
前言
讲了FastJson反序列化的原理和利用链,今天讲一下Shiro的反序列化利用,这个也是目前比较热门的。
原生态反序列化
我们先来复习一下原生态的反序列化,之前也是讲过的,打开我们写过的serialization_demo。代码也很简单,先是读取一个文件,接着对其进行反序列化,最后再一个main函数去调用这个方法。
我们利用ysoserial这个工具生成一个dnslog利用链,写入到urldns.txt文件中。
运行上面的代码,对urldns.txt文件进行反序列化,可以看到在dnslog平台有回显。
shiro反序列化
现在开始说一下Shiro的反序列化,Shiro框架提供了记住密码的功能,cookie含有rememberMe字,⽤户登陆成功后会⽣成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造⼀个恶意对象,对其进⾏序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进⾏解密并且反序列化,最终造成反序列化漏洞。
网上找的一个Shiro demo,下载好就直接部署就行,具体如何部署就不说了,网上有。
登录抓包看一下,发现确实存在rememberMe字段,而且后面有一大堆值。
现在来分析一下,打开我们的login.jsp,可以看到是调用了include.jsp的。
接着去打开include.jsp看看,这里是引用了org.apache.shiro.SecurityUtils这个类。
可以在相应的位置找到这个类,其实它就是一层一层的引用的。
我们全局搜 rememberMeSuccessfulLogin,找到之后下个断点,来一步一步跟进看看是咋回事。
如果你搜不到的话,来到Maven这里把这个包下载一下就行。
接着以调试模式运行Tomcat,此时我们在登录页面进行登录,就可以看到已经代码断下来了。
开始步入,来到这里可以看到进行了一个判断,如果RememberMeManager的值不为空就进入,也就是说我们进行登录要勾选 “记住我” 才会触发判断。
我们一直步入,因为前面都是一些代码逻辑,没啥看的,代码到这里就出现了序列化,principals 的值就是我们的用户名 root,这里对 root 进行了序列化操作。还对 getCipherService() 进行了判断,如果不为空就进行加密,从字面来看这个方法应该是获取密码的。
接着往下跟进,这里调用了getSerializer().serialize() 进行序列化。
跟进到这个serialize 方法里面,里面的代码是不是和我们上面原生态的序列化差不多,这里调用了 ObjectOutputStream()这个类,并且还调用了这个类里面的 writeObject() 方法对 o 进行序列化,而 o 则是我们的用户名 root 。
这个 root是我们可控的,那么就基本满足了反序列漏洞的条件了。
接着再继续跟进看看,来到了加密这里,我们要知道是啥加密类型才能进一步的利用。
下面参数这里有个key。
展开参数 this ,发现modeName的值是CBC,那么可以猜测这是AES-CBC加密方式。
这个transformationString 就更进一步证实了我们的猜想。
跟到现在基本就可以确定。
发送数据的时候:数据—>序列化—>aes加密—>base64编码
接受数据的时候:base64解码—>aes解密—>反序列化—>数据
base64是可逆的,反序列的数据是可控的,那么我们现在只需要知道AES的Key、iv、mode,是不是就能构造出恶意的 RememberMe 的值了。
上面跟踪的时候我们获取到了Key和 iv 这两个值,但这里只是Ascii码,直接叫AI写个脚本把Ascii码变成base64字符串就行。其上上面说的先AES加密再base64加密,其实是不严谨的,base64只是编码了我们的Key,并没有对AES加密之后的数据进行base64编码。
我们用ysoserial 生成一个dnslog利用链。
再用下面这个脚本对我们的链条进行aes加密,Key是我们上面转换过来的。
from Crypto.Cipher import AES
import uuid
import base64
//若提示ModuleNotFoundError: No module named 'Crypto'
//需安装pycryptodome库:pip3 install pycryptodome
def convert_bin(file):
with open(file, 'rb') as f:
return f.read()
def AES_enc(data):
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)
ciphertext = base64.b64encode(iv + encryptor.encrypt(pad(data))).decode()
return ciphertext
if __name__ == "__main__":
data = convert_bin("urldns.txt")
print(AES_enc(data))
生成加密之后的数据。
替换掉原本的值进行发送。
成功解析。
上面的DNS链条只能访问一下dnslog,如果想要执行命令的话,要用到CC链才行,这个下次详细的讲。
总结
最后,以上仅为个人的拙见,如何有不对的地方,欢迎各位师傅指正与补充,有兴趣的师傅可以一起交流学习。