CVE-2020-17523

漏洞描述

Apache Shiro身份认证绕过漏洞

漏洞成因

Shiro中对于URL的获取及匹配org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain

先看下这个getChain方法:

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
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}

String requestURI = getPathWithinApplication(request);

// in spring web, the requestURI "/resource/menus" ---- "resource/menus/" bose can access the resource
// but the pathPattern match "/resource/menus" can not match "resource/menus/"
// user can use requestURI + "/" to simply bypassed chain filter, to bypassed shiro protect
if(requestURI != null && !DEFAULT_PATH_SEPARATOR.equals(requestURI)
&& requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
requestURI = requestURI.substring(0, requestURI.length() - 1);
}


//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
for (String pathPattern : filterChainManager.getChainNames()) {
if (pathPattern != null && !DEFAULT_PATH_SEPARATOR.equals(pathPattern)
&& pathPattern.endsWith(DEFAULT_PATH_SEPARATOR)) {
pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
}

// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + Encode.forHtml(requestURI) + "]. " +
"Utilizing corresponding filter chain...");
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}

return null;
}

其中DEFAULT_PATH_SEPARATOR值被定义为/

image-20210310155759523

该方法先检查requestURI是否以/结尾,如果是,就删掉最后一个/

然后在匹配路径的循环中,会先判断下路径规则pathPattern是否以/结尾,如果是也会删除。然后再去调用pathMatches()方法进行路径匹配。因此两种利用方式中,是否以/结尾都没有关系,因为开始经过getChain方法就会被删除。

空格绕过法分析

关注下pathMatches()方法:

1
2
3
4
protected boolean pathMatches(String pattern, String path) {
PatternMatcher pathMatcher = getPathMatcher();
return pathMatcher.matches(pattern, path);
}

追踪发现最终 matches(pattern, path)调用了AntPathMatcher.java#doMatchimage-20210310170814655

测试一下pathMatches("/admin/*","/admin/1")pathMatches("/admin/*","/admin/ "),前者正常匹配,后者匹配失败。

image-20210310180212241

image-20210310180259062

开始调试,一直到doMatch("/admin/*","/admin/ ")。可见,tokenizeToStringArray返回的pathDirs已经没有第二层路径了。因此会导致/admin/* /admin 不匹配。

image-20210310180446498

跟一下tokenizeToStringArray方法,发现其调用tokenizeToStringArray方法时的trimTokens参数为true。

image-20210310180513745

tokenizeToStringArray方法,在参数trimTokens为true时,会经过trim()处理,因此导致空格被清除。再次返回getChain时最后一个/被删除。因此tokenizeToStringArray返回的pathDirs没有第二层路径。

image-20210310180544455

总结一下:存在漏洞的shiro版本,由于调用tokenizeToStringArray方法时,trimTokens参数默认为true,空格会经过trim()处理,因此导致空格被清除。再次返回getChain时最后一个/被删除,所以/admin/admin/*匹配失败,导致鉴权绕过。而Spring接受到的访问路径为/admin/%20,按照正常逻辑返回响应,因此导致权限被绕过。

/./绕过分析

分析normalize()方法,此方法在org.apache.*shiro*.web.util.WebUtils#normalize

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
private static String normalize(String path, boolean replaceBackSlash) {

if (path == null)
return null;

// Create a place for the normalized path
String normalized = path;

if (replaceBackSlash && normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');

if (normalized.equals("/."))
return "/";

// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;

// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}

// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}

// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}

// Return the normalized path that we have completed
return (normalized);

}

其功能是:

条件 示例
正斜杠处理成反斜杠 \ -> /
双反斜杠处理成反斜杠 // -> /
以/.或者/…结尾,则在结尾添加/ /. -> /./ /… -> /…/
归一化处理/./ /./ -> /
路径跳跃 /aaa/…/bbb -> /bbb

所以/admin/.在被处理成/admin/./之后变成了/admin/

image-20210310175110763

再经过org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain处理,由于/结尾,如果是,就删掉最后一个/,变成了/admin/admin/admin/*不匹配,因此绕过了shiro鉴权。

image-20210310175539695

漏洞影响面

Apache Shiro < 1.7.1

复现步骤

下载shiro 1.7.0环境, 运行

  • %20绕过

    访问http://10.17.34.7:9090/admin/%20

image-20210310172138664

  • %2e绕过

访问http://10.17.34.7:9090/admin/%2e

image-20210310172220450

https://www.anquanke.com/post/id/216096

https://github.com/jweny/shiro-cve-2020-17523