Apache Shiro 身份验证绕过漏洞复现 (cve-2020-1957)

我不是女神ヾ 2024-04-07 10:45 135阅读 0赞

0x00 漏洞描述

Apache Shiro 1.5.2之前版本中存在安全漏洞。攻击者可借助特制的请求利用该漏洞绕过身份验证。

Shiro框架通过拦截器功能来对用户访问权限进行控制,如anon, authc等拦截器。anon为匿名拦截器,不需要登录即可访问;authc为登录拦截器,需要登录才可以访问。Shiro的URL路径表达式为Ant格式,路径通配符*表示匹配零个或多个字符串,/*可以匹配/hello,但是匹配不到/hello/,因为*通配符无法匹配路径。假设/hello接口设置了authc拦截器,访问/hello会进行权限判断,但如果访问的是/hello/,那么将无法正确匹配URL,直接放行,进入到spring拦截器。spring中的/hello和/hello/形式的URL访问的资源是一样的,从而实现了权限绕过。

0x01 漏洞影响

Apache Shiro < 1.5.2

0x02 Shiro拦截器

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

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

用户可以在Shiro.ini编写匹配URL配置,将会拦截匹配的URL,并执行响应的拦截器。从而实现对URL的访问控制,URL路径表达式通常为ANT格式。如下配置,访问 /index.html主页的时候,Shiro将不会对其进行登录判断,anon拦截器不需要登录就能进行访问。而对于/user/xiaoming 等 /user/xiaogang等接口,authc拦截器将会对其进行登录判断,有登录认证才能访问资源。

[urls] /index.html = anon /user/** = authc

Shiro的URL路径表达式为Ant 格式,路径通配符支持 ? * ** 。

?:匹配一个字符 *:匹配零个或多个字符串 **:匹配路径中的零个或多个路径

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

0x03 环境搭建

下载demo代码 :https://github.com/lenve/javaboy-code-samples/tree/master/shiro/shiro-basic

导入idea

Shiro版本1.4.2



org.apache.shiro

shiro-web

1.4.2



org.apache.shiro

shiro-spring

1.4.2

修改ShiroConfig配置文件,添加authc拦截器的拦截正则

@Bean

ShiroFilterFactoryBean shiroFilterFactoryBean() {

ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

//map.put(“/*“, “authc”);

map.put(“/hello/*“, “authc”);

bean.setFilterChainDefinitionMap(map);

return bean;

}

修改路由控制器方法

@GetMapping(“/hello/{currentPage}“)

public String hello(@PathVariable Integer currentPage) {

return “hello”;

}

通过idea进行编译,可获得war包

这里通过docker快速搭建漏洞环境:

docker pull vulfocus/shiro-cve_2020_1957

28cacfe4f794689d1f51edd13fe480f6.png

docker images

0ef9aae07b3ccfbfcf6e3ae363c95303.png

docker run -d -p 8080:8080 -v /var/run/docker.sock:/var/run/docker.sock -e VUL_IP=192.168.1.14 29136b1d3c61

9bde10c121e06dfa9ed543a06910064b.png

-v /var/run/docker.sock:/var/run/docker.sock 为 docker 交互连接。

-e DOCKER_URL 为 Docker 连接方式,默认通过 unix://var/run/docker.sock 进行连接,也可以通过 tcp://xxx.xxx.xxx.xxx:2375 进行连接(必须开放 2375 端口)。

-v /vulfocus-api/db.sqlite3:db.sqlite3 映射数据库为本地文件。

-e VUL_IP=xxx.xxx.xxx.xxx 为 Docker 服务器 IP ,不能为 127.0.0.1。

http://192.168.1.14:8080/login

96b1946c8cadc83bda9e083a0ee1c827.png

0x04 漏洞复现

1、Shiro1.4.2版本绕过权限

访问/hello/1接口,可以看到被authc拦截器拦截了,将会跳转到登录接口进行登录。

426949a40aaffe7a88dd994cf38cd156.png

访问/hello/1/,成功绕过authc拦截器,获取到了资源。

247e5271644a17cc3859cced0e8ac2be.png

2、Shiro1.4.2版本绕过漏洞分析

漏洞初始成因可以定位到 PathMatchingFilterChainResolver的getChain函数下,该函数作用根据URL路径匹配中配置的url路径表达式来匹配输入的URL,判断是否匹配拦截器,匹配成功将会返回响应的拦截器执行链,让ShiroFither执行权限操作的。

其对于URL路径表达式和输入URL的匹配主要通过pathMathches函数进行匹配。

1049983-20201129040313760-380732836.png

pathMatches函数其最终会调用shiro.util.AntPathMatcher类中doMatch的对于ant格式的pathPattern和requestURI进行匹配。

//pathMatches:135, PathMatchingFilterChainResolver (org.apache.shiro.web.filter.mgt) protected boolean pathMatches(String pattern, String path) { PatternMatcher pathMatcher = this.getPathMatcher(); return pathMatcher.matches(pattern, path); }

doMatch:109, AntPathMatcher (org.apache.shiro.util),当Shiro 的Ant格式的pathPattern 中的的 * 通配符是不支持匹配路径的,所以 /hello/* 不能成功匹配 /hello/1/ ,也就不会触发authc拦截器进行权限拦截。从而成功绕过了Shiro拦截器,而后再进入到spring拦截器中,/hello/1/与 /hello/1 能获取到相同的资源。

cf30f4a5ffc03e9b289ba30dc5071c3e.jpeg

3、Shiro≤1.5.1版本绕过

在1.5.1版本中,/hello/会直接跳转到登录

2ba4b3d6ca181258c91f4c2f121a02a6.png

绕过payload, /fdsf;/../hello/1 ,成功绕过。

bdf1dd99adedf936b1d30bdff91334d7.png

或者其他payload,xxxx/..; /hello/1 ,成功绕过(shiro的1.5.1及其之前的版本)

df805f6633ef91a51f1bbce376d954dd.png

其中以上处理过程:

  1. 客户端请求URL: /xxxx/..;/hello/1
  2. shrio 内部处理得到校验URL为 /xxxx/..,校验通过
  3. springboot 处理 /xxxx/..;/hello/1 , 最终请求 /hello, 成功访问了后台请求.

4.Shiro≤1.5.1版本漏洞分析

问题同样可以定位到getChain函数中对于requestURI的获取中,如下图所示, this.getPathWithinApplication(request) 获取的requestURI为 /fdsf ,而不是我们输入的 /fdsf;/../hello/1 ,从而导致后面的URI路径模式匹配返回False,从而再次绕过了shiro拦截器。

05019415c1cc21341aefba153d649565.png

getPathWithinApplication函数中会调用WebUtils (org.apache.shiro.web.util)中的getRequestUri函数获取RequestUri。

  1. public static String getRequestUri(HttpServletRequest request) {
  2. String uri = (String)request.getAttribute("javax.servlet.include.request_uri");
  3. if (uri == null) {
  4. uri = request.getRequestURI();
  5. }
  6. return normalize(decodeAndCleanUriString(request, uri));
  7. }

RequestUri函数中最终调用decodeAndCleanUriString函数对URI进行清洗。

  1. private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
  2. uri = decodeRequestString(request, uri);
  3. int semicolonIndex = uri.indexOf(59);//获取;号的位置
  4. return semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri;
  5. }

如果URI中存在 ; 号的话,则会删除其后面的所有字符。 /fdsf;/../hello/1/ 最终也就变成了 /fdsf 。

505fbf84e209b3f44cba5863f8c61848.png

5.漏洞 总结

在web容器中,Shiro的拦截器是先与spring(Servlet)执行,两者拦截器对于URI模式匹配的差异,导致Shiro拦截器的绕过,而Shiro对其进行了两次修复,其第一次在shiro1.4.2版本出现漏洞后为删除requestURI后面的 / 号进行URL路径匹配,算是简单的修复了添加 / 号绕过的方式,而后在1.5.2版本中通过requestURI自主拼接的方式修复了 /fdsf;/../hello/1/ 等使用了;号方式的绕过

0x05 修复方案

1.升级1.5.2版本及以上

在shiro1.5.2版本已加入的过滤器规则:

@Test

void testGetRequestUriWithServlet() {

dotTestGetPathWithinApplicationFromRequest(“/“, “/servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“”, “/servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“”, “servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“/“, “servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“//“, “servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“//“, “//servlet”, “//foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“/context-path”, “/servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“//context-path”, “//servlet”, “//foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“//context-path”, “/servlet”, “/../servlet/other”, “/servlet/other”)

dotTestGetPathWithinApplicationFromRequest(“//context-path”, “/asdf”, “/../servlet/other”, “/servlet/other”)

dotTestGetPathWithinApplicationFromRequest(“//context-path”, “/asdf”, “;/../servlet/other”, “/asdf”)

dotTestGetPathWithinApplicationFromRequest(“/context%2525path”, “/servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“/c%6Fntext%20path”, “/servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“/context path”, “/servlet”, “/foobar”, “/servlet/foobar”)

dotTestGetPathWithinApplicationFromRequest(“”, null, null, “/“)

dotTestGetPathWithinApplicationFromRequest(“”, “index.jsp”, null, “/index.jsp”)

}

2.尽量避免使用 * 通配符作为动态路由拦截器的URL路径表达式。

0x06 参考文献

https://www.zhihuifly.com/t/topic/2822

https://paper.seebug.org/1196/

https://xz.aliyun.com/t/8281

发表评论

表情:
评论列表 (有 0 条评论,135人围观)

还没有评论,来说两句吧...

相关阅读