漏洞描述
Apache Shiro before 1.6.0, when using Apache Shiro, a specially crafted HTTP request may cause an authentication bypass.
漏洞条件
shiro < 1.6.0
分号使用url编码传递 :%3b,不能直接传递明文
注意:
/admin/* --> 不匹配 /admin
/admin/** -->匹配 /admin
漏洞复现
【注】:以下漏洞复现中其实不登入也可以,因为是直接绕过shiro,所以验证和访问控制都被绕过了。这里只是为了更好的展现越权(绕过访问控制)而登入一个低权限账户。不登录的话,就是漏洞描述中的“authentication bypass”。
环境:
shiro : 1.5.3 //使用的还是javax
spring-boot:2.7.4 //3.xx版本 最低java 17且依赖的是jakarta
shiro配置:
@Bean
public IniRealm getIniRealm() {
return new IniRealm("classpath:shiro.ini");
}
@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("/print", "authc, perms[printer:print]");
map.put("/query", "authc, perms[printer:query]");
map.put("/admin/*", "authc, roles[admin]"); //注意不可以是"/admin/**"
map.put("/login","authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
//shiro.ini
# format: roleName = permission1, permission2, ..., permissionN
[roles]
user = printer:print
admin = printer:*
# format: username = password, role1, role2, ..., roleN
[users]
user1 = pswd123, user
admin1 = pswd321, admin
controller方法:
@GetMapping("/admin/{param}")
public String adminInfo(@PathVariable String param) {
return "Admin Info: " + param;
}
具体步骤
以user1的身份登入,获取cookie;(不登入也可以)
恶意访问网站:/admin/%3bhello
越权成功!!!
漏洞分析
漏洞入口(不是具体位置):PathMatchingFilterChainResolver.getChain(...)
调试
请求路径:/admin/%3bhello
1.进入getChain:
2. 进入getPathWithinApplication():
可以看到getServletPath(),已经对url解码了(%3b
--> ;
)
查看removeSemicolon()源码:
private static String removeSemicolon(String uri) {
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
//是去除分号后面所有内容(包括分号)
因此返回值:
后面匹配时会去掉末尾的分割符
/
匹配路径:获取所有配置路径,然后遍历匹配
最终结果:不匹配
因此绕过了shiro。
spring为什么最终能定位到具体的controller?
入口函数:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
定位到获取查询路径的函数:iniLookupPath(request)
可以看到在去除分号前,并没有对请求url进行解码。
进入removeSemicolonContent(lookupPath):
返回入口:
因为没有解码,导致无法找到分号,故直接返回。
后面经过多轮匹配最终是匹配到了对应方法:
根据漏洞复现最终响应可以知道,传参controller前对%3b解码了
总结
shiro与spring对路径的处理不同导致的。前者是先对路径解码后再去除分号。后者是去除分号后再解码。
漏洞修复
经过调试发现(此时shiro版本为1.6.0,其他配置不变),默认添加一个 filterChain:(即便你没有配置)
/** --> InvalidRequestFilter
定位到对应修复代码:
第一处红框处:
第二处红框处:添加了一个类
只展示核心算法
其逻辑很简单,就是查看路径中是否有非法字符,有则默认不通过。
由于其他匹配规则不变,所以毫无疑问最终会匹配到/**
所以最终结果是:不通过
总结:
不改变原来路径处理逻辑的基础上,添加一个InvalidFilter类来检查请求路径;并且会在初始化时自动添加一个
/** --> InvalidFilter
的filterChain,从而截断攻击。