漏洞描述
Apache Shiro before 1.5.3, when using Apache Shiro with Spring dynamic controllers, a specially crafted request may cause an authentication bypass.[1]
和CVE-2020-1957漏洞原理非常相似,只是这个漏洞是出现在context-path位置。
漏洞条件
-
需要配置context-path,如
/appName
-
shiro < 1.5.3
-
使用spring
漏洞复现
环境
shiro : 1.5.2 //使用的还是javax
spring-boot:2.7.4 //3.xx版本 最低java 17且依赖的是jakarta
springboot配置文件:配置context-path
server:
port: 9090
servlet:
context-path: /test
shiro配置
package com.codereview.cve.config;
import java.util.LinkedHashMap;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ShiroConfig {
@Bean
public IniRealm getIniRealm() {
return new IniRealm("classpath:shiro.ini");
}
@Bean
public SimpleCookie rememberMeCookie(){
return new SimpleCookie("rememberMe");
}
@Bean
DefaultWebSecurityManager securityManager(IniRealm iniRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm((Realm)iniRealm);
manager.setRememberMeManager(new CookieRememberMeManager());
return manager;
}
//url配置
@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/info", "authc, roles[admin]");
map.put("/login","authc");
bean.setFilterChainDefinitionMap(map);
return bean;
}
}
shiro.ini:
# format: roleName = permission1, permission2, ..., permissionN
#permission分为`域`:printer 和`操作`:print,query
[roles]
user = printer:print
admin = printer:*
# format: username = password, role1, role2, ..., roleN
[users]
user1 = pswd123, user
admin1 = pswd321, admin
controller
假设存在一个打印机:
package com.codereview.cve.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PrinterController {
@GetMapping("/login")
public String login() {
return "This is a login page";
}
@GetMapping("/hello")
public String index() {
return "Hello World";
}
@RequestMapping ("/loginSuccess")
public String loginSuccess() {
return "login success!";
}
@GetMapping("/print")
public String print() {
return "you are printing content...";
}
@GetMapping("/query")
public String query() {
return "you are querying content...";
}
@GetMapping("/admin/info")
public String adminInfo() {
return "Admin Info";
}
@GetMapping("/unauthorized")
public String unauthorized() {
return "you are unauthorized";
}
}
测试
-
正常登入(以用户user1的身份):
-
正常访问管理员权限页面(越权):重定向到未授权页面
-
构造恶意路径,再次访问管理员权限页面:成功越权
漏洞分析
漏洞路口:PathMatchingFilterChainResolver.getChain(...)
源码如下
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
//获取请求路径
String requestURI = getPathWithinApplication(request);
//遍历filterChainManager管理的所有过滤链的名字
//也就是配置文件中配置的"/print","/query","/print"
for (String pathPattern : filterChainManager.getChainNames()) {
//检查是否匹配,如果匹配则返回pathPattern对应的过滤链
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("省略");
}
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
进入getPathWithinApplication(request):
-
正常访问时(
/test/admin/info
):能够获取到正确的路径 -
但是当路径为
/;anyContent/test/admin/info
的时候: -
毫无疑问这时放回的requestUri和所配置的路径不匹配,所以绕过了shiro;
为什么会出先上述结果?
-
进入getContextPath():
未normalize前:
normalize之后:根据上述内容可知没有发生变化 -
进入getRequestUri():
request.getServletPath()能够正常获取到相对路径(应用内部路径)
但是uri的构造 拼接 了带有分号的context-path:但是以下函数对带有分号的路径处理仍然是去除分号后所有内容(包括分号)
但是spring web的路径匹配机制却能够匹配到对应的方法:
入口函数org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#getHandlerInternal
也就是说,spring在获取contextPath时虽然是与shiro是一致的,但是spring是直接拿 相对路径查询,且去除分号的操作也只针对相对路径,而shiro却还要先拼接出完整路径再去除分号,然后最后又截取一下(正常情况)
漏洞修复
直接获取相对路径,然后去除分号;
private static String getServletPath(HttpServletRequest request) {
String servletPath = (String) request.getAttribute(INCLUDE_SERVLET_PATH_ATTRIBUTE);
return servletPath != null ? servletPath : valueOrEmpty(request.getServletPath());
}
request.getServletPath() 本身会像spring一样处理带分号的请求路径,所以在相对路径中放入分号也没用了:(Shiro CVE-2020-1957详细分析 - FreeBuf网络安全行业门户
//一般为空
private static String getPathInfo(HttpServletRequest request) {
String pathInfo = (String) request.getAttribute(INCLUDE_PATH_INFO_ATTRIBUTE);
return pathInfo != null ? pathInfo : valueOrEmpty(request.getPathInfo());
}
//去除逻辑不变,去除分号后面所有内容(包括分号)
private static String removeSemicolon(String uri) {
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
Reference
Security Reports | Apache Shiro
Apache Shiro 身份验证绕过漏洞 复现(CVE-2020-11989) - 渗透测试中心 - 博客园
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)