出品|先知社区(ID:alter99)
以下内容,来自先知社区的alter99作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。
漏洞信息
漏洞编号:CVE-2020-17510 / CNVD-2020-60318
影响版本:shiro < 1.7.0
漏洞描述:第三种AntPathMatcher的绕过方式
漏洞补丁:Commit
这个漏洞还是对AntPathMatcher的继续绕过,在CVE-2020-11989和CVE-2020-13933分别尝试了/的双重URL编码和 ; 的URL 编码绕过,归根到底这种方式还是因为Shiro与Spring对URI处理的差异化导致的。那么字符 . 是不是也可以进行绕过呢?其实是可以的(测试环境Shiro 1.6.0,SpringBoot 2.5.3),还是添加如下配置和Controller
map.put("/hello/*", "authc");
@GetMapping("/hello/{name}")
public String hello(@PathVariable String name) {
return "hello";
}
当Shiro获得的uri为/hello时,是无法和/hello/*匹配的,所以就在/hello后面加上%2e,这样Shiro解码之后变成/hello/.,然后路径标准化成为/hello,绕过身份验证
对于Spring来说,正如之前讲的,Spring Boot 版本在小于等于 2.3.0.RELEASE时,会对uri进行解码然后路径标准化,这样得到的路径为/hello,没有页面与之匹配。所以只有当 Spring Boot 版本在大于 2.3.0.RELEASE时标准化路径后/hello/%2e,然后解码/hello/.
下面的payload都可以使用:
/%2e
/%2e/
/%2e%2e
/%2e%2e/
在Commit中发现org.apache.shiro.spring.web下新增了ShiroUrlPathHelper类,属于UrlPathHelper的子类,重写了getPathWithinApplication和getPa-thWithinServletMapping两个方法
通过相关配置后,Spring就会使用Shiro的UrlPathHelper,这样两者判断逻辑一致,就不存在因差异性问题而导致的绕过了。
其实我认为1.7.1才算真正的更新,因为它是依次对原uri和去除uri尾部斜线的uri进行验证,这样就可以避免因直接去除尾部uri导致/hello和/hello/*不匹配而导致的绕过问题。
问题一
根据官方发布的公告,发现其实需要配置shiro-spring-boot-web-starter才有效
if you are NOT using Shiro’s Spring Boot Starter
(`shiro-spring-boot-web-starter`), you must configure add the
ShiroRequestMappingConfig auto configuration[1] to your application or
configure the equivalent manually[2].
[1] https://shiro.apache.org/spring-framework.html#SpringFramework-WebConfig
[2]https://github.com/apache/shiro/blob/shiro-root-1.7.0/support/spring/src/main/java/org/apache/shiro/spring/web/config/ShiroRequestMappingConfig.java#L28-L30
由于我导入的dependency如下
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.6.0</version>
</dependency>
如果直接将版本升为1.7.0的话,其实并没有触发更新,原payload还是可以绕过。
只有按照上面官网所述的两种配置方式修改后,才能防御成功
问题二
在旧版的SpringBoot 中,当我们需要获取当前请求地址的时候,直接通过如下方式获取:
//org.springframework.web.servlet.handler#getHandlerInternal
String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);
但是在新版Spring里边,通过如下方式获取
String lookupPath = this.initLookupPath(request);
initLookupPath()代码如下:
protected String initLookupPath(HttpServletRequest request) {
if (this.usesPathPatterns()) {
request.removeAttribute(UrlPathHelper.PATH_ATTRIBUTE);
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(request);
String lookupPath = requestPath.pathWithinApplication().value();
return UrlPathHelper.defaultInstance.removeSemicolonContent(lookupPath);
} else {
return this.getUrlPathHelper().resolveAndCacheLookupPath(request);
}
}
如果this.usesPathPatterns() == true的话,就可以绕开问题一中我们配置的ShiroUrlPathHelper
此时也成功绕过。
所以这就存在一个矛盾:只有Spring Boot 版本在大于 2.3.0.RELEASE才能触发这个漏洞,修复之后由于版本问题,SpringBoot又不走那条语句。
另外在配置的时候,当Spring Boot版本在小于等于2.3.0.RELEASE,如2.1.5.RELEASE,时,this.get-UrlPathHelper()并不是ShiroUrlPathHelper,不清楚是不是配置问题还是版本兼容问题。
漏洞信息
漏洞编号:CVE-2020-17523 / CNVD-2021-09492
影响版本:shiro < 1.7.1
漏洞描述:Shiro 1.7.1 之前的版本,在将 Shiro 与 Spring 结合使用时,特制的 HTTP 请求可能会导致身份验证绕过。
漏洞补丁:Commit
如CVE-2020-17510那样,这个漏洞可以使用空格%20进行绕过。
我们输入路径为http://localhost:8080/hello/%20,进入getChain,经过路径获取后要进行权限的匹配与验证
注意,获取的路径后面有空格,这里主要看一下/hello/和/hello/*比较时发生了什么经过:
pathMatches(pathPattern,requestURI)-> pathMatcher.matches(pattern,path)->match(pattern,source)-> doMatch(pattern, path, true) 来到了主要的判断方法doMatch()。
其中StringUtils.tokenizeToStringArray()方法是将它的参数,也就是传进来的两个路径拆解成字符串数组,然后进行比较。进入方法,可以看到当对空格进行转换时,直接trim为空。
这样就导致与shiro中的配置本意想违背,导致绕过。
然后在Spring中的处理时,uri又包含空格,这样就能访问到/hello/%20页面
在Commit中,主要修复点AntPathMatcher.java,在tokenizeToStringArray方法中加了false和true两个参数
漏洞信息
漏洞编号:CVE-2021-41303 / SHIRO-825
影响版本:shiro < 1.8.0
漏洞描述:1.8.0 之前的Apache Shiro,在Spring Boot中使用Apache Shiro 时,特制的HTTP请求可能会导致身份验证绕过。用户应该更新到Apache Shiro 1.8.0。
漏洞补丁:Commit
参考:threedr3am师傅
根据threedr3am师傅博客提供的方向,看了一Shiro1.7.1前后PathMatchingFilterChainResolver-#getChain的对比
发现在1.7.1版本中,先是对pathPattern和requestURI进行比较,比较成功,返回:
filterChainManager.proxy(originalChain, pathPattern);
否则对删除尾部斜线的pathPattern和requestURI进行比较,比较成功,跳出循环,返回:
filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
但是正常访问,都会返回第一个proxy,什么时候才能绕过第一个比较并符合第二个比较呢?
可以看到,两者差别是对uri尾部斜线的处理,所以当在uri尾部加一个/,就会进入第二种比较方式。
结合之前的多次调试再根据threedr3am师傅博客中的认证,可以知道shiro的认证鉴权会根据配置的先后顺序去依次实施,所以当我有如下配置时:
map.put("/admin/*", "authc");
map.put("/admin/page", "anon");
循环中先匹配到/admin/*(这里是通过while语句对去除尾部斜线的uri进行匹配)然后跳出循环进入到
filterChainManager.proxy(originalChain, requestURINoTrailingSlash);,
注意,这里真正的参数就是去除尾部斜线的uri,也就是/admin/page,所以在DefaultFilterChain-Manager#getChain中得到的权限是anon,这样就达到绕过目的。
直接将filterChainManager.proxy的第二个参数改为pathPattern,直接传配置中的uri了
漏洞信息
漏洞编号:CVE-2022-32532
影响版本:shiro < 1.9.1
漏洞描述:在1.9.1之前的Apache Shiro中,RegexRequestMatcher可能会被错误配置,从而在某些servlet容器上被绕过。应用程序使用RegExPatternMatcher与.的正则表达式可能容易被授权绕过。
漏洞补丁:Commit
参考:4ra1n师傅
这是最新的一个洞,看Shiro发布的公告显示,是由于RegexRequestMatcher的错误配置导致的问题。
简单了解了一下,RegexRequestMatcher和Ant-PathMatcher类似,都是Shiro用于路径匹配的配置,只是RegexRequestMatcher需要用户自己配置。
根据4ra1n师傅的分析,可以知道,正常正则表达式.并不包含r和n字符
修改成如下代码就可修复问题
// flag为Pattern.DOTALL时,表达式 .可以匹配任何字符,包括行结束符。
Pattern pattern = Pattern.compile(regex,Pattern.DOTALL);
那么回头看一下RegexRequestMatcher用于匹配的代码
public boolean matches(String pattern, String source) {
if (pattern == null) {
throw new IllegalArgumentException("pattern argument cannot be null.");
} else {
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(source);
return m.matches();
}
}
可以发现,当pattern存在带.的正则表达式并且source中存在r或n字时,此时判断错误。
此时我们在配置完RegexRequestMatcher之后增加如下Controller
public String alter( String value) {
System.out.println("绕过成功");
return "绕过成功"+value;
}
增加如下配置
//myFilter.java中设置成需要权限
manager.addToChain("/alter/.*", "myFilter");
这个洞限制还是比较多的,既要服务器配置了RegExPatternMatcher,又要设置带有.的正则表达式
在Commit可以看到,对compile方法设置了flag
▇ 扫码关注我们 ▇
长白山攻防实验室
学习最新技术知识
原文始发于微信公众号(长白山攻防实验室):shiro历史漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论