【vulhub】CVE-2020-1957 Apache Shiro Servlet未授权访问
漏洞描述
1. 漏洞编号:CVE-2020-1957
2. 影响版本:Apache Shiro < 1.5.1
3. 漏洞产生原因:
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访问的资源是一样的。
漏洞成因
路径解析差异:
Shiro 的 PathMatchingFilterChainResolver 使用简单的字符串匹配逻辑(基于 AntPathMatcher),而 Spring 的 AntPathMatcher 支持更灵活的路径标准化(如忽略结尾的 / 或解析 ; 后的内容)。例如:
Shiro 将 /admin/ 和 /admin 视为不同路径,而 Spring 可能视为同一路径。
Shiro 在处理分号 ; 时会截断后续内容(如 /admin;xxx 解析为 /admin),而 Spring 可能保留完整路径。
配置缺陷:
Shiro 的过滤器链配置若未覆盖所有可能的路径变体(如未配置 /admin/** 或 /admin/),攻击者可构造 /admin/ 或 /admin;xxx 绕过校验。
漏洞复现
启动环境
docker-compose build
docker-compose up -d
环境启动后,访问http://your-ip:8080
即可查看首页
使用bp抓包,访问/admin目录,出现302,会跳转到登录接口:
访问/xxx/..;/admin/目录,绕过权限校验,访问到管理页面
其中以上处理过程:
-
客户端请求URL: /xxxx/..;/admin/
-
shrio 内部处理得到校验URL为 /xxxx/..,校验通过
-
springboot 处理 /xxxx/..;/admin/ , 最终请求 /admin, 成功访问了后台请求.
修复方案
1.升级1.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") }