漏洞描述
Apache Shiro before 1.7.1, when using Apache Shiro with Spring, a specially crafted HTTP request may cause an authentication bypass.[1]
漏洞条件
shiro < 1.7.1
路径配置 是类似“/*”的只有一个通配符,而不能是类似“/**”的双统配符
使用spring
漏洞复现
环境
基础环境:
- shiro:1.7.0
- springboot: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, roles[admin]"); // 如果是"/admin/**"则漏洞无效
map.put("/login","authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
controller:
@ResponseBody
@GetMapping("/admin/{param}")
public String adminInfo(@PathVariable String param) {
if(param == null){
return "you are admin";
}
return "admin Info: " + param;
}
测试
payload
/admin/%20
结果:
漏洞分析
请求同上。
漏洞入口:PathMatchingFilterChainResolver.getchain(...)
getPathWithinApplication(request)保留了空格
springweb路径匹配路口:AbstractHandlerMethodMapping.getHandlerInternal(...)
从漏洞复现结果可知,最终能够匹配“/admin/{param}"。
但是为什么shiro中"/admin/ "(末尾有空格)无法匹配"/admin/*"?
PathMatchingFilterChainResolver.pathMatches(String pattern, String path) --不断跟进--->
AntPathMatcher.doMatch(pattern, path, true);
第一步:将传入的路径以分割符”/“,分割路径为数组:
不过,从图中发现,pattern(配置的路径)能够正常的被分割为["admin","*"],然而path 没有被分割为["admin"," "],而是缺少了空格符。
也就是说相当于把“/admin/ " 变成了“/admin",最终结果必然是不匹配的
第二步:进入以下while循环,进行循环匹配
// Match all elements up to the first **
while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) {
String patDir = pattDirs[pattIdxStart];
if ("**".equals(patDir)) {
break;
}
if (!matchStrings(patDir, pathDirs[pathIdxStart])) {
return false;
}
pattIdxStart++;
pathIdxStart++;
}
第三步:由于pathDirs长度小于pattDirs长度,所以最终path会先耗尽(exhausted),从而跳出循环,进入下图分支(最上层的if条件),最终返回false
综上可知,最根本的原因就在StringUtils.tokenizeToStringArray(...)
:分割时去除了空格
public static String[] tokenizeToStringArray(String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) {
if (str == null) {
return null;
} else {
StringTokenizer st = new StringTokenizer(str, delimiters);
List tokens = new ArrayList();
while(true) {
String token;
do {
if (!st.hasMoreTokens()) {
return toStringArray(tokens);
}
token = st.nextToken();
if (trimTokens) {
//token = " "时,token.trim()会返回去除首尾空格后的字符串:""
//因此最终数组中没有空格符
token = token.trim();
}
} while(ignoreEmptyTokens && token.length() <= 0);
tokens.add(token);
}
}
}
trim(): Returns a string whose value is this string, with any leading and trailing whitespace removed.
漏洞修复
将trimTokens
参数设置为false: [3]
Reference
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)