CVE-2016-4437 Shiro Rememberme反序列化漏洞
漏洞简介
https://issues.apache.org/jira/browse/SHIRO-550
在shirt <= 1.2.24版本中,如果用户选择了Remember Me,那么shiro就会进行如下操作
1 | 获取Remember Me cookie值 |
而我们知道Remember cookie的生成方式是
1 | 序列化 |
因为AES是对称密码,密钥可用于加密和解密而密钥是硬编码在文件中的,所以就可导致利用密钥将一个恶意对象序列化后加密。选择Remember Me后解密并反序列化时就会触发恶意代码
环境搭建
首先下载存在漏洞版本的shiro
1 | git clone https://github.com/apache/shiro.git |
然后修改pom.xml,在里面添加
1 | <dependencies> |
然后将整个shiro文件导入Idea后通过mvn导入依赖包,接着配置tomcat
我这里使用的是jdk7+tomcat7,这里要配置一下Artifact
然后就可以运行了
漏洞复现
首先我们检测一下搭建的环境是否存在该漏洞,这里使用检查工具探测
发现漏洞确实存在,那就触发一下试试
接着使用手动复现一下
首先需要在vps上有一个rmi注册服务,执行
1 | ysoserial % java -cp ysoserial-0.0.5.jar ysoserial.exploit.JRMPListener 6666 CommonsCollections4 'curl 127.0.0.1:2345' |
然后使用如下poc生成Remember Me Cookie
1 | import sys |
然后burpsuite抓包把Remember Me Cookie带入
漏洞分析
加密cookie流程分析
当我们成功登陆时如果选择了Remember Me,那么就会进入到AbstractRememberMeManager#onSuccessfulLogin
接着进入AbstractRememberMeManager#rememberIdentity
这里创建了一个principals对象,跟进rememberIdentity方法
跟进convertPrincipalsToBytes方法
这里将principals对象进行了序列化然后使用encrypt方法加密,也就是AES加密,这里跟进一下
这里的getCipherService方法作用是获取密码服务,这里的cipherService是AesCipherService
接下来的ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
作用就是加密serialized了,这里的getEncryptionCipherKey
作用是获取密钥,来看一下是怎么获取密钥的。
这里的encryptionCipherKey
哪来的呢
发现在构造方法中有一个setCipherKey
这里的DEFAULT_CIPHER_KEY_BYTES
就是硬编码在文件里的密钥
跟进setCipherKey
跟进setEncryptionCipherKey
这里就将密钥赋值给了encryptionCipherKey
,所以回到上面的getEncryptionCipherKey
方法就得到了密钥
接着继续跟进encrypt
方法
这里的iv值由generateInitializationVector
方法得到,返回一个类型为Bytes,长度为16的数组
接着调用encrypt
的重载方法
这里使用了crypt方法对plaintext进行了加密,得到encrypted
接着新建了一个byte型的数组,长度为iv的长度加上encrypted的长度
然后调用arraycopy方法得到了新的密文output
1 | Java.lang.System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length) |
回到rememberIdentity,跟进rememberSerializedIdentity方法
这里就将AES加密后的bytes进行了base64加密,最后通过response返回设置为用户的Cookie的rememberMe字段中
解密cookie流程分析
首先到达getRememberPrincipals方法处
跟进getRememberedSerializedIdentity方法
RememberMe Cookie从这里获得赋值给base64
接着调用了Base64#decode
跟进decode方法
通过toBytes方法转换成字节码的形式,再通过decode方法进行base64解码
接着进入convertBytesToPrincipals方法
跟进decrypt方法
这里的getCipherService和getDecryptionCipherKey和加密时是一样的值,具体的解密方法在decrypt里,跟进
看上去和加密也差不多,得到了一个iv,将ciphertext值通过arraycopy放入新数组encrypted里,将iv, key,encrypt代入decrypt的重载方法
最后通过crypt方法实现AES解密返回解密后的值decrypted
回到convertBytesToPrincipals方法,跟进deserialize方法
在这里触发反序列化执行命令
修复方式
将之前的固定Key改为随机生成Key