0x00 漏洞复现
pom.xml
org.apache.shiro
shiro-core
1.5.2
org.apache.shiro
shiro-spring
1.5.2
application.properties
server.context-path=/test
ShiroConfig
@Bean
ShiroFilterFactoryBean shiroFilterFactoryBean() {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(securityManager());
bean.setLoginUrl("/login");
bean.setSuccessUrl("/index");
bean.setUnauthorizedUrl("/unauthorizedurl");
Map map = new LinkedHashMap();
map.put("/hello/*", "authc");
//map.put("/hello/**", "authc"); //in version 1.7.0 will not trigger CVE-2020-17523
bean.setFilterChainDefinitionMap(map);
return bean;
}
spring
@RequestMapping("/hello/{name}")
public String hello2(@PathVariable String name) {
return "auth hello/{_" + name + "_}, there ";
}
触发权限绕过的访问请求如下:
http://127.0.0.1:8080/test/hello/a%252fa
response如下:auth hello/{a%2fa}, there
正常的访问请求如下:
http://127.0.0.1:8080/test/hello/aa
response如下:跳转到登录页面。
"/"的URL编码为"%2f",在浏览器中"%"二次编码为"%252f"。
触发此漏洞的根源在于shiro在进行filter匹配的过程中,对url进行了两次解码;而在spring的框架中,并未进行两次URL解码。因此两者造成了不一致。
0x01 源码分析
PathMatchingFilterChainResolver.java文件中的getchain函数中如下调用,获取requestURI,导致requestURI被解析为"/hello/a/a",而无法与pattern"/hello/*"匹配上。
String requestURI = getPathWithinApplication(request);
在WebUtils.java中getPathWithinApplication函数
public static String getPathWithinApplication(HttpServletRequest request) {
String contextPath = getContextPath(request);
String requestUri = getRequestUri(request);
if (StringUtils.startsWithIgnoreCase(requestUri, contextPath)) {
// Normal case: URI contains context path.
String path = requestUri.substring(contextPath.length());
return (StringUtils.hasText(path) ? path : "/");
} else {
// Special case: rather unusual.
return requestUri;
}
}
getRequestUri函数
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == null) {
uri = valueOrEmpty(request.getContextPath()) + "/" +
valueOrEmpty(request.getServletPath()) +
valueOrEmpty(request.getPathInfo());
}
return normalize(decodeAndCleanUriString(request, uri));
}
decodeAndCleanUriString函数
private static String decodeAndCleanUriString(HttpServletRequest request, String uri) {
uri = decodeRequestString(request, uri);
int semicolonIndex = uri.indexOf(';');
return (semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri);
}
decodeRequestString函数
public static String decodeRequestString(HttpServletRequest request, String source) {
String enc = determineEncoding(request);
try {
return URLDecoder.decode(source, enc);
} catch (UnsupportedEncodingException ex) {
if (log.isWarnEnabled()) {
log.warn("Could not decode request string [" + Encode.forHtml(source) + "] with encoding '" + Encode.forHtml(enc) +
"': falling back to platform default encoding; exception message: " + ex.getMessage());
}
return URLDecoder.decode(source);
}
}
因为采用ant风格的匹配,所以,如果pattern配置为"/hello/**"则不会触发此漏洞。
patch分析
diff
https://github.com/apache/shiro/compare/shiro-root-1.5.2...shiro-root-1.5.3
getPathWithinApplication函数进行了升级,不再调用会进行二次URL解码的getRequestUri函数。
public static String getPathWithinApplication(HttpServletRequest request) {
return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
}
private static String getServletPath(HttpServletRequest request) {
String servletPath = (String) request.getAttribute(INCLUDE_SERVLET_PATH_ATTRIBUTE);
return servletPath != null ? servletPath : valueOrEmpty(request.getServletPath());
}
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 valueOrEmpty(String input) {
if (input == null) {
return "";
}
return input;
}
在shiro 1.5.3版本中,没有地方再调用WebUtils.getRequestUri,该方法标识为已废弃。
那么废弃了getRequestUri方法是否会触发CVE-2020-1957呢?
答案是并不会,在getPathWithinApplication方法中并未直接获取uri,而是通过getServletPath和getPathInfo分别获取的。
References
https://xlab.tencent.com/cn/2020/06/30/xlab-20-002/