漏洞原理:Apache Shiro框架提供了记住密码的功能(RememberMe),用户登录成功后会将用户的登录信息加密编码,然后存储在Cookie中。对于服务端,如果检测到用户的Cookie,首先会读取rememberMe的Cookie值,然后进行base64解码,然后进行AES解密再反序列化。
1. 加密流程分析
当我们勾选记住密码的选项之后,登录时断点打到DefaultSecurityManager.rememberMeSuccessfulLogin方法下
获取一个RememberMeManager对象之后进入onSuccessfulLogin方法
调用forgetIdentity()方法对subject进行处理,subject对象表示单个用户的状态和安全操作,包含认证、授权等,跟进
在forgetIdentity()方法中,对subject进行了处理
我们继续跟进forgetIdentity,getCookie()方法获取请求的cookie,接着会进入到removeFrom()方法
跟进removeFrom()方法,removeForm主要在response头部添加字段Set-Cookie: rememberMe=deleteMe
然后我们再次回到onSuccessfulLogin方法中,如果设置了rememberMe则进入rememberIdentity方法
我们跟进看看后续怎么处理的,rememberIdentity方法中主要是两部分,一部分是convertPrincipalsToBytes方法用来清除之前的认证信息,并根据用户名生成新的Principal
我们跟进convertPrincipalsToBytes方法查看,发现主要干了两件事情
- 将我们的
principals(用户名)序列化 - 对序列化之后的结果进行加密
我们继续跟进我们的加密算法encrypt,看看如何加密的
这里可以通过getCipherService获取的加密对象,看到是一个很明显的AES加密
那么我们跟进getEncryptionCipherKey()函数,发现加密密钥返回的是encryptionCipherKey值
我们看看它是在哪里设置的,发现setEncryptionCipherKey函数是赋值的
在setCipherKey()函数中对其进行赋值,而且赋值的是一个常量DEFAULT_CIPHER_KEY_BYTES
是一个写死的base64字符串
1 | private static final byte[] DEFAULT_CIPHER_KEY_BYTES = Base64.decode("kPH+bIxk5D2deZiIxcaaaA=="); |
让我们返回到刚刚的流程,convertPrincipalsToBytes之后进入我们的rememberSerializedIdentity部分
这里就是很简单的将结果base64加密,然后设置到cookie里面
至此,加密部分我们基本了解完毕:
1 | 序列化principals(用户名) -> 对结果进行AES加密 -> 对结果进行base64加密 -> 设置到cookie中 |
大致流程
2. 解密流程分析
我们登录之后利用burp重新发包,看看cookie是如何解密的,发包之后发现并没有断住,是因为除了cookie之外,它还通过JSESSIONID=C434C16E1B719C7DFC51E116A05638E3来鉴权,当存在JSESSIONID的时候不会触发反序列化
我们删除JSESSIONID=C434C16E1B719C7DFC51E116A05638E3再试一次
成功断在AbstractRememberMeManager.getRememberedPrincipals处
我们进入getRememberedSerializedIdentity方法,看名字应该是处理反序列化的,它从我们的请求中读取base64加密之后的cookie
判断cookie是否被删除、确保base64字符串被正确的填充,最后将base64解码为字节数组
将字节数组返回到我们的getRememberedPrincipals方法中,然后调用convertBytesToPrincipals方法来从字节数组中获取我们的权限(Principals)
调用decrypt方法来AES解码,和上面加密步骤基本相同,密钥也是默认密钥
唯一值得注意的是我们的解密向量iv是根据传入的数据一起的
返回deserialize(bytes)的值,我们看看deserialize()方法,继续跟进发现调用的是默认的反序列化方法
一路走到readObject方法,触发反序列化
整体过程如下:
3. 利用
我们可以利用URLDNS链进行探测
1 | //URLDNS.java |
编译运行
1 | javac URLDNS.java |
在目录下生成了ser.bin的二进制文件,我们利用python脚本对其进行加密
1 | from Crypto.Cipher import AES |
将序列化数据填入到cookie中,然后删除JSESSIONID,发送数据包
burp成功收到请求
参考文档: