Apache Shiro 在 Java 的权限及安全验证框架中占用重要的一席之地,在它编号为550的 issue 中爆出严重的 Java 反序列化漏洞。下面,我们将首先搭建漏洞环境,模拟还原此漏洞的场景及分析,最后根据不同的场景提出了三种办法来修补此漏洞。详见shiro反序列化漏洞分析、模拟攻击及修复(三)
1.从git上获取 Apache Shiro 存在漏洞的源代码。地址:https://github.com/apache/shiro/releases/tag/shiro-root-1.2.4
2.修改shiro/samples/web下的pom.xml文件,添加如下内容:
1.6
1.6
...
javax.servlet
jstl
1.2
runtime
.....
org.apache.commons
commons-collections4
4.0
3.使用maven进行存在漏洞环境的 war 包进行编译。
ps:在编译过程中出现了以下错误
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-toolchains-plugin:
1.1:toolchain (default) on project shiro-samples: Cannot find matching toolchain
definitions for the following toolchain types:
[ERROR] jdk [ vendor='sun' version='1.6' ]
4.将 target 目录下生成的 samples-web-1.2.4.war 文件拷贝至 tomcat 目录下的 webapps 目录,将其重命名为了 shiro.war 文件,启动 tomcat, 在浏览器当中输入 http://localhost:8080/shiro ,可以看到如下登录界面
1.登录root账号,勾选Remember Me,观察其cookie中的rememberMe内容
2.查看官方漏洞描述,其处理cookie的流程是:得到rememberMe的cookie值-->Base64解码-->AES解密-->反序列化。
3.将rememberMe内容使用base64解码后为
00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
--------------------------------------------------------------------
58 1D 21 BE 14 35 E0 1D 2A AD 0E 4C 74 9C DF 3A | X.!..5..*..Lt..:
A8 99 65 F5 25 38 25 D2 6A C5 83 7E 4D 5A F4 1A | ..e.%8%.j..~MZ..
0E 54 38 F2 C3 EE 6D B2 BF 4C 3E 14 AA A3 F0 44 | .T8...m..L>....D
45 5F 5A 9B 5A E8 CB 4A 62 EC 25 90 DA A2 27 1D | E_Z.Z..Jb.%...'.
A7 C4 85 36 94 CD 24 69 76 BB 6F 97 EA B1 4C 9C | ...6..$iv.o...L.
1A ED 33 70 CC E4 EE 64 64 8B 37 DB 9E 58 63 36 | ..3p...dd.7..Xc6
6A 42 60 C3 04 A6 E4 BE A7 10 87 F5 FC 54 FA 32 | jB`..........T.2
DC 5B 5F E7 D0 35 35 49 0A 53 A9 7B C0 A7 82 A6 | .[_..55I.S.{....
BC 90 70 60 0C 30 E3 13 4E 46 B2 88 21 52 26 D1 | ..p`.0..NF..!R&.
FC 37 55 8A 45 2F 9E 7B A8 84 C8 EF 19 15 AE 1E | .7U.E/.{........
72 28 FB 18 BE 8A 67 00 9B 9A 65 FA 0B 4E 35 BE | r(....g...e..N5.
DE 9C 00 C2 88 E9 45 71 D9 B7 98 59 6D DD 83 E6 | ......Eq...Ym...
A2 6A B9 33 89 2C FC F0 CD 7A 2E D3 BA 14 A9 4C | .j.3.,...z.....L
25 12 10 E8 FB 3F 86 B4 4A 7D 68 AF 0E 43 C3 B3 | %....?..J}h..C..
49 EC AC 0C 94 8C C5 9D 1E 70 00 24 20 C8 7A 17 | I........p.$ .z.
E3 3D DF C7 F7 35 71 8B F5 12 65 2C F8 23 A7 E6 | .=...5q...e,.#..
33 57 CE C4 1B 00 0C A5 6C 06 AE 5D 03 D8 AE 06 | 3W......l..]....
1A 7C C7 3E 23 0F 00 FE FD 29 32 58 E5 2C D7 63 | .|.>#....)2X.,.c
FD E4 A7 A4 69 17 4C 24 67 5A 1A 42 A4 14 F1 B2 | ....i.L$gZ.B....
BB 9B A2 A6 58 F0 B0 CF 1A 6F 5D B8 0C DD E9 A4 | ....X....o].....
A9 E9 DD CE 4D CD 1B A2 0F C8 3A EE 6E 38 B3 B0 | ....M.....:.n8..
B8 B6 E5 E3 5C B8 22 0B DF 0E 09 6B AA 42 DC 8F | ....\."....k.B..
FC C2 53 9E C2 8F 3D D5 DC 32 E2 1A 97 36 E6 AB | ..S...=..2...6..
68 47 83 3A 98 D4 8A 5F FF 1A 77 D5 98 B6 B5 94 | hG.:..._..w.....
4. 解码后没有看到有明确的 Java 序列化特征字,因为处理流程中提到了 AES和加密密钥硬编码,所以去跟一下源码。在AbstractRememberMeManager 看到了加密密钥。Base64.decode("kPH+bIxk5D2deZiIxcaaaA==") 就是我们要找的硬编码密钥,因为 AES 是对称加密,即加密密钥也同样是解密密钥。
5.除了密钥,我们还需知道两个属性,一个是 AES 中的 mode(加解密算法),另外一个是 IV(初始化向量),继续查看 AbstractRememberMeManager 的代码,发现了encrypt方法。
6.在实现CipherService这个接口的抽象类 JcaCipherService中,在它的成员函数 initNewCipher 中下断点,可以看到:AES 的 mode 为 CBC,IV是随机生成的。
7.利用上述获取到的信息,对 Base64 解码后的文件进行解密操作。
import base64
from Crypto.Cipher import AES
def decode_rememberme_file(filename):
with open(filename, 'rb') as fpr:
key = "kPH+bIxk5D2deZiIxcaaaA=="
mode = AES.MODE_CBC
IV = b' ' * 16
encryptor = AES.new(base64.b64decode(key), mode, IV=IV)
remember_bin = encryptor.decrypt(fpr.read())
return remember_bin
if __name__ == '__main__':
with open("decrypt.bin", 'wb+') as fpw:
fpw.write(decode_rememberme_file("remember.bin"))
8.如下为解密后的文件内容,可以看到第二行打头的 ac ed 00 05,这是Java序列化的标志。
9.解密后文件的第一行内容,在JcaCipherService 这个类中的一个加密函数 encrypt 发现是先将 IV 写入,然后再加密具体的序列化对象的字节码,这样 IV 值我们可以直接通过读取第一行(16个字节,128位)获得了。
10.最后还剩加密的序列化对象,在SimplePrincipalCollection 中我们发现了关键的两个方法: writeObject 和 readObject。到此,cookie的加密构造过程我们就都清楚了。
1.明白了shiro中rememberMe字段的解析过程,使用idea进行调试,进一步了解解析过程,调试时会出现错误,需要在sample-web下的pom文件中做以下修改。
2.使用刚刚登录所拿到的rememberMe值在getRememberedIdentity方法中打断点进行调试。可以看到拿到了cookie值。
3.继续跟踪,得到将cookie进行base64解码后的数据。
4.继续跟踪,得到将base64解码后的数据AES解密后的bytes。
5.继续跟踪,得到将AES解密后的bytes反序列化,通过readObject方法获得的cookie原值。至此,cookie解析结束。