0x00 前言
其实已经有很多网上分析shiro的文章了,这里分析一下shiro给自己留一个记录加深印象
0x01 漏洞原理
shiro<1.2.4之前的版本存在反序列化漏洞,产生的原因是Shiro接受的Cookie里rememberMe
的值,然后去进行Base64解密,在使用AES密钥解密之后得到原始数据,对得到的原始数据进行反序列化。
于是只要我们知道了AES的密钥就可以对我们构造的恶意序列化数据进行加密然后Base64编码交给shiro让他去接收,然后达到攻击
获取rememberMe值 -> Base64解密 -> AES解密 -> 调用readobject反序列化操作
0x02 环境搭建
漏洞环境:https://codeload.github.com/apache/shiro/zip/shiro-root-1.2.4
下载了之后找到shiro-shiro-root-1.2.4/samples/web
目录在idea中打开就好,需要修改一些pom文件的配置。
贴出来我的部分pom文件,其他的部分不需要修改为了节省空间我就删掉了。
<?xml version="1.0" encoding="UTF-8"?>
<!--suppress osmorcNonOsgiMavenDependency -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.apache.shiro.samples</groupId>
<artifactId>shiro-samples</artifactId>
<version>1.2.4</version>
<relativePath>../pom.xml</relativePath>
</parent>
....
<dependencies>
<!--这里是加上的4版本的CC-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<!-- <scope>provided</scope>-->
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
<version>2.6</version>
<!-- <scope>test</scope>-->
</dependency>
</dependencies>
</project>
之后点击clean 和package,注意先clean之后package打包就可以启动项目了。
0x03 漏洞分析
加密
漏洞产生点就在CookieRememberMeManager
查看其中的rememberSerializedIdentity
方法。
该方法的作用为使用Base64对指定的序列化字节数组进行编码,并将Base64编码的字符串设置为cookie值继续查看到AbstractRememberMeManager
类的rememberIdentity
方法调用到了它
继续跟进可以发现AbstractRememberMeManager
的rememberIdentity
方法会调用自己的另一个重构函数,也就是rememberIdentity()
->rememberIdentity()
,有点意思,一开始以为我自己找错了。
之后可以看到还是这个类下的onSuccessfulLogin
方法会调用rememberIdentity()
该方法是生成加密的RememberMe Cookie
,然后将RememberMe Cookie
设置为用户的Cookie值,因为最终会调用到rememberSerializedIdentity
这个方法的。
在onSuccessfulLogin
处打上断点来正向的分析
Debug分析
使用root/secret登录系统,注意要选中rememberme
走到这里调用了isRememberMe()
方法,直接会返回到rememberIdetity()
方法
之后就会执行rememberIdentity
方法中的convertPrincipalsToBytes
,这个方法把传入的数据转换为字节数组传入的数据现在是root也就是用户名
跟进方法,看到传入的root被序列化成字节数组返回,并且看到这里getCipherService
方法就是if条件中的,就是获取解密方式的函数,我们会跟进这个方法
看到getCipherService
方法返回一个加密模式,之后会调用encrypt(bytes)
我们跟进该方法
这里的CipherService cipherService = getCipherService();
获取到加密模式,如果不为空就会进入到加密方法
ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
getEncryptionCipherKey()
获取到加密的密钥我们来看看如何获取到的密钥
这里直接get了密钥,而set方法在构造函数的时候就调用了,set的正是被Base64编码的密钥
最后shiro会将加密的信息设置为cookie
解密
上边我们知道了shiro把传进来的用户信息加密成密文设置成cookie的现在我们给shiro发送加密的cookie,shiro会自动解密然后反序列化,来debug一下其中的步骤
AbstractRememberMeManager
类存在encrypt
方法也有decrypt
方法,向上追溯convertBytesToPrincipals
方法会调用decrypt
方法getRememberedPrincipals
方法是其上层方法
在getRememberedSerializedIdentity(subjectContext);
处下断点调试程序
这里就进入到了getRememberedSerializedIdentity
方法中
返回到getRememberedPrincipals
方法中
这里就调用了convertBytesToPrincipals
方法
查看decrypt
方法具体实现
解密完成后返回到convertBytesToPrincipals
方法,这里return会执行deserialize()
方法
跟进该方法就会找到最后的入口DefaultSerializer
类的deserialize
方法,这里会readObject
就开始反序列化
到这里shiro550分析结束
0x04 结语
感觉水了一篇文章,分析的还是不够彻底:-(