漏洞原理分析
Springboot中对使用Apache Shiro进行身份验证、权限控制时,利用Apache Shiro和SpringBoot对URL的处理不同,实现越权访问
比如/xxx/..;/admin/这个路径,在shiro看到“;”分号后,就会进行截断,校验分号前面路径/xxx/..这个路径并没有包含admin/**于是校验通过。Spring Boot看到此路径后,会直接取有效路径/admin/于是就访问成功了
漏洞影响版本:
shiro的1.5.1及之前的版本
漏洞复现
复现环境:vulhub
docker-compose up -d //开启环境 docker-compose ps 查看环境
访问一下
点击Login
用bp抓包,
访问/admin 显示302重定向跳转到登录界面
将url改为/xxx/..;/admin/后绕过了登录
注意这里是 两个点 .. 因为我们进入xxx路径中,然后需要 ../返回原目录
成功,
关闭环境
原理分析
以/xxx/..;/admin/ 为例,一步步分析整个流程中的请求过程
protected String getPathWithinApplication(ServletRequest request) {
return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
}
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;
}
}
public static String getRequestUri(HttpServletRequest request) {
String uri = (String) request.getAttribute(INCLUDE_REQUEST_URI_ATTRIBUTE);
if (uri == null) {
uri = request.getRequestURI();
}
return normalize(decodeAndCleanUriString(request, uri));
}
INCLUDE_REQUEST_URI_ATTRIBUTE
此时的URL还是我们传入的原始URL:/xxx/..;/admin/接着,程序会进入到decodeAndCleanUriString(),
decodeAndCleanUriString 以 ; 截断后面的请求,所以此时返回的就是/xxx/.. 然后程序调用normalize() 对decodeAndCleanUriString()处理得到的路径进行标准化处理,都是一些很常见的标准化方法.
private static String normalize(String path, boolean replaceBackSlash) {
if (path == null)
return null;
// Create a place for the normalized path
String normalized = path;
if (replaceBackSlash && normalized.indexOf('\\') >= 0)
normalized = normalized.replace('\\', '/');
if (normalized.equals("/."))
return "/";
// Add a leading "/" if necessary
if (!normalized.startsWith("/"))
normalized = "/" + normalized;
// Resolve occurrences of "//" in the normalized path
while (true) {
int index = normalized.indexOf("//");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 1);
}
// Resolve occurrences of "/./" in the normalized path
while (true) {
int index = normalized.indexOf("/./");
if (index < 0)
break;
normalized = normalized.substring(0, index) +
normalized.substring(index + 2);
}
// Resolve occurrences of "/../" in the normalized path
while (true) {
int index = normalized.indexOf("/../");
if (index < 0)
break;
if (index == 0)
return (null); // Trying to go outside our context
int index2 = normalized.lastIndexOf('/', index - 1);
normalized = normalized.substring(0, index2) +
normalized.substring(index + 3);
}
// Return the normalized path that we have completed
return (normalized);
}
经过getPathWithinApplication()函数的处理,最终shiro 需要校验的URL 就是 /xxx/... 最终会进入到 org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver 中的 getChain()方法会URL校验.
由于/xxx/.. 截断了 并不会匹配到 /admin/** 所以shiro权限校验就会通过
最终我们的原始请求/xxx/..;/admin/ 就会进入到 springboot中. springboot对于每一个进入的request请求也会有自己的处理方式,找到自己所对应的mapping. 具体的匹配方式是在:org.springframework.web.util.UrlPathHelper 中的 getPathWithinServletMapping(),然后匹配到了/xxx/..;/admin