漏洞描述
Apache Shiro before 1.8.0, when using Apache Shiro with Spring Boot, a specially crafted HTTP request may cause an authentication bypass.[1]
漏洞条件
- shiro <1.8.0
- 错误的的url配置
假设说”/admin"目录下有一个特殊页面"/admin/anonPage"不用登入也能访问,但是却错误地配置成以下内容:
map.put("/admin/*", "authc"); //若是"/admin/**" 则漏洞无效
map.put("/admin/anonPage", "anon"); //"anon" 是anonymous的缩写,表示不用登入即刻访问
按照配置而言,"/admin/*"是包含 ”/admin/anonPage“(或"/admin/anonPage/")的,所以按正确逻辑来讲,当请求是”/admin/anonPage"(或“/admin/anonPage/")shiro只能匹配第一项,永远不会匹配第二项,也就是说第二项配置是无效的。
- shiro是按照配置顺序匹配请求路径
- 从逻辑来讲,"/admin/anonPage"和"/admin/anonPage/"它们是相同,springweb中的匹配路径规则是如此,shiro的匹配规则会将后者处理成前者后再匹配,最终效果也是两者相同
正确的配置是两个配置交换顺序,优先匹配特例。
但现在配置是错误的,shiro却没有做出对应“正确”的反应,即以上配置可以绕过。
漏洞复现
配置环境
基础:
shiro : 1.7.1
spring : 2.7.4
shiro
@Bean
ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager);
bean.setLoginUrl("/login");
bean.setSuccessUrl("/loginSuccess");
bean.setUnauthorizedUrl("/unauthorized");
LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
//url --> filter1,filter2....
map.put("/admin/*", "authc");
map.put("/admin/anonPage", "anon");
map.put("/login","authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
controller
@GetMapping("/admin/anonPage")
public String adminInfo() {
return "This is an anonymous page";
}
- url == "/admin/anonPage":
很明显,由于没有会话id,因此此请求是匿名的(anonymous),但是"/admin/*"是"authc",是需要验证(即登入)的,因此重定向到登入页面。 - url == "/admin/anonPage/"
漏洞分析
首先需要知道,自shiro1.7.1开始,PathMatchingFilterChainResolver.getChain(...)
的匹配规则发生了一个比较明显的改变
shiro 1.7.0:
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("省略");
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
shiro 1.7.1:
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
final String requestURI = getPathWithinApplication(request);
final String requestURINoTrailingSlash = removeTrailingSlash(requestURI);
//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 the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("....");
}
return filterChainManager.proxy(originalChain, pathPattern);
} else {
// 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
pathPattern = removeTrailingSlash(pathPattern);
if (pathMatches(pathPattern, requestURINoTrailingSlash)) {
if (log.isTraceEnabled()) {
log.trace("....");
}
//漏洞所在!!!
return filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
}
}
}
return null;
}
前者是直接去除请求url和pattern末尾的slash(“/”),然后进行匹配;后者是先不去除slash匹配一次,若不匹配则去除slash后再匹配一次。
shiro虽然"/admin/anonPage/"没有直接匹配到 "/admin/*",但是在第二个分支,将其尾部slash去除后,是匹配上了的。那为什么明明匹配上了 "/admin/*",却还是被绕过了?
原因在于断点处(第二个分支):
filterChainManager.proxy(originalChain, requestURINoTrailingSlash);
可以看到,第二个参数,也就是要选择的chainName是去除尾部slash后的requestURINoTrailingSlash
(即“/admin/anonPage”)
public FilterChain proxy(FilterChain original, String chainName) {
NamedFilterList configured = getChain(chainName); //底层调用Map
if (configured == null) {
String msg = "There is no configured chain under the name/key [" + chainName + "].";
throw new IllegalArgumentException(msg);
}
return configured.proxy(original);
}
所以在获的过滤链时获取的自然就是“/admin/anonPage”对应的过滤链(anon)
漏洞修复
其实从漏洞分析部分可知,这个漏洞本质上是一个bug。[3]
其修复逻辑如下:[2]
将传递的参数改为pathPattern
总结
本次漏洞比较特殊,是“在错误配置下,没有执行对应”正确“行为”类型的漏洞,算是收获了漏洞理解的一种角度。
Reference
[1] Security Reports | Apache Shiro
[2] Backport SHIRO-825 · apache/shiro@4a20bf0
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)