本文首发于公众号合天智汇,博客仅作记录之用。
前言
Shiro这个词最近在安全圈挺火的,我是第一次接触到这个,所以Apache发漏洞通告的时候还是给我整的有点懵。在看师傅们的复现时也感到有点吃力,因此整理了下关于这个漏洞的相关知识点,写了点自己的理解以供还未接触过这个知识点的同学们参考,同时也给今后复习留下参考。个人水平有限,可能存在理解偏差,请路过的大佬们指正。
cookie的cookiememeMe已通过AES-128-CBC模式加密,这很容易受到Padding oracle攻击的影响。攻击者可以使用有效的RememberMe cookie作为Padding Oracle Attack 的前缀,然后制作精心制作的RememberMe来执行Java反序列化攻击。
重现此问题的步骤:
- 登录网站,并从cookie中获取RememberMe。
- 使用RememberMe cookie作为Padding Oracle Attack的前缀。
- 加密syserial的序列化有效负载,以通过Padding Oracle Attack制作精心制作的RememberMe。
- 请求带有新的RememberMe cookie的网站,以执行反序列化攻击。
攻击者无需知道RememberMe加密的密码密钥。
注:此条为Apache-721官方通告(谷歌翻译)
shiro框架
我们先来了解一下什么是shiro框架,根据百度百科的资料,可以知道Apache Shiro是一个强大且易用的Java安全框架而且它具有执行身份验证、授权、密码和会话管理的功能。shiro简单来说就是权限管理组件,而通过shiro-721这个漏洞结合反序列化攻击可以达到远程执行的目的。根据漏洞公告来看,需要搞懂什么是padding oracle攻击以及它有什么效果,什么是shiro反序列化攻击为什么通过这个可以达到远程执行的目的,以及为什么要采用RememberMe cookie 来作为攻击的前缀。
shiro反序列化攻击
shiro反序列化攻击其实也就是对shrio-550漏洞的利用过程,网上对于这个漏洞的复现过程的相关记录还是挺多的,有兴趣的同学可以去找找相关的资料进行一个复现。这里我简单说说为什么会存在shiro反序列化攻击,根据Apache官网的资料,默认情况下shiro会使用CookieRememberMeManager。它会对用户的身份进行序列化,加密和编码,以供以后检索。
流程图如下:
![检索RememberMe cookie 的值](浅析Shiro-Padding-Oracle-Attack/检索RememberMe cookie 的值.png)
有过CTF经验的同学可能会对反序列化这个词比较敏感,这在CTF赛题中是一个常常出现的题型,有的同学可能立马就能想到能不能构造一个恶意的对象,当它被反序列化时自动触发魔术方法以达到攻击的目的。没错,这正是Shiro的反序列化攻击流程:构造一个恶意对象发送给服务器,当服务器执行反序列时便能触发攻击。根据流程图来看RememberMe Cookie采用的双重加密,先是使用了Base64加密,再用AES加密。Base64我们都知道怎么样去加解密,可是AES采用的是对称加密,要加密的话还必须有一个密钥Key。这也是Shiro-550漏洞存在的关键点:在默认情况下加密密钥Key是硬编码的,也就是说加密密钥Key编码在源码内。只要可以拿到源代码就可以拿到这个Key,从而进行shiro的反序列化攻击。但是在Shiro1.2.5以上,官方已经修复了这个漏洞,已经拿不到KEY了。
我们知道Shiro Padding Oracle Attack利用到了shiro反序列化,通过上面的知识知道要利用这个漏洞必须构造出一个可以让服务器反序列化的恶意对象,那么Shiro Padding Oracle Attack是可以拿到AES的加密密钥吗?还是有别方法可以绕过AES解密达到反序列化的目的呢?
接下来我们了解下padding oracle攻击。
padding oracle攻击
在了解这个攻击方式之前我们首先了解下Shiro所采用的AES-128-CBC加密模式:AES代表加密方式,CBC是分组模式,128也就是16个字节,这里采用的是PKCS#5填充方式。
AES-128-CBC对明文进行加密时,首先会对明文进行分组,这里每一组是16个字节。我们很容易可以想到的是并不是每一组明文都可以正好分配,也就是说最后一组明文可能存在需要数据填充的情况。实际上,采用分组方式时是必须要填充的,如果满了则填充满一个新组。
注:图来源于网络;此图为8位填充示意图,16位与之类似。
在明文被分组完成后便是对明文进行分组加密。流程图如下:
注:图片来源于网络
从图中我们可以看出CBC是分组加密的,每一组先与一个初始向量IV进行异或(异或:A异或B得C,C异或B得A),异或得到的值我们称之为中间值(IMV),再用KEY对中间值进行加密得到了第一组密文。第一组密文再作为IV与第二组明文进行异或得到第二个中间值,同样也是用Key将这个中间值加密后得到第二组密文,以此类推。看到这里其实我们可以思考一个问题,最后一组明文其实是经过填充的,这是它与其余几组的不同之处,那么是否意味着我们可以在这个地方有什么利用呢?
接下来我们看下解密流程,其实解密就是加密的翻转。
注:图片来源于网络
从上图我们可以看到,第一组密文块经过KEY解密后得到中间值,中间值与IV异或得到了第一组明文。然后,第一组密文块作为IV与第二组的中间异或得到了第二组明文,以此类推完成全部解密。这里我们还需要关注下服务器的解密过程:当我们提交一个IV时,服务器会用中间值与它异或得值然后先校验填充情况而非直接比对明文。此时服务器会返回两种情况:正确的密文返回200或者302;错误的密文返回500。
现在我们对AES加解密有了一定的了解,接下来就来看看padding oracle攻击到底是怎么实现的。上面我们已经提到了最后一个分组存在填充情况,并且填充情况应该是下面的其中一种。
1 | ①:明文 0x01 |
打起精神,重难点来啦:
整理下思路,现在我们可以捕获到密文,并且知道了加密算法也知道初始向量IV,在对密文进行分组后,只要我们有办法绕过KEY解密的过程拿到中间值是不是就能直接用中间值(IMV)与初始向量IV进行异或操作从而拿到所有的明文。
根据服务器会对填充错误进行报错这个关键点,我们构造出一个虚假的IV值来反推中间值,首先假定明文最后一位填充的是0x01,也就是上面八种填充情况中提到第①种情况。根据异或的特性,只有唯一的IV值与中间值异或能得到0x01,IV值最后一个字节的取值范围是 0×01~0xFF,一共255种情况。如下图所示,当我们试验到0x66时发现服务器返回了200,所以很容易可以知道IMV就是0x01与0x66的异或结果0x67。
回想下上面所写我们的解密过程,我们用初始向量IV与爆破得到的IMV值异或便可以得到最后一位明文。接下来我们再推导数第二个明文,根据填充规则,此时应该是上面八种填充情况中提到第②种情况。我们已经知道了IMV最后一字节是0x67,那么此次构造的IV最后一字节就是0x67与0x02异或的结果0x65。此时固定最后一位字节爆破倒数第二个字节,以此类推完成所有的解密。
注:图片来源于网络
总的来讲,padding oracle并不是可以拿到AES的密钥,而是可以绕过。正常的解密流程需要IV、密文、key;而padding oracle攻击只需要IV、密文即可。
以上是利用padding oracle攻击解密密文,与之类似的是利用padding oracle攻击加密明文。我们想一想在加密过程中是不是第一组的密文作为第二组的IV,此时这个中间量(IMV)我们是可以推导出来的,那么根据异或的唯一性如果攻击者想改变明文是不是通过改变密文就可以实现呢?
1 | 明文 = 密文(已知) 异或 IMV(已确定) |
总结
回到最初提出的问题,padding oralce攻击可以没有KEY的情况下加密密文或者解密密文,而shiro反序列化攻击可以达到远程执行的目的,但是想要成功让服务器反序列化必须传输符合格式的密文。因次通过padding oralce攻击构造恶意的对象再通过shiro反序列化攻击便能达到最终的Shiro Padding Oracle Attack。至于为什么需要RememberMe cookie是因为Shiro会先获取用户信息,当存在有效的用户信息时才会进入下一阶段的流程。