目录
由于笔者个人水平有限,行文如有不当,还请各位师傅评论指正,非常感谢
(一)Apache Shiro介绍
Apache Shiro是一款开源安全框架,它的功能主要用于身份验证、授权、会话管理、加密......漏洞发生原因是:登入的时候序列化保存了登入信息到cookie(序列化、AES加密、Base64)
1、漏洞原理分析
Spring Boot中使用 Apache Shiro 进行身份验证、权限控制时,利用 Apache Shiro 和 Spring Boot 对URL的处理的不同,实现越权访问。
比如/xxx/..;/admin/这个路径,在shiro看到“;”分号后,就会进行截断,校验分号前面路径/xxx/..这个路径并没有包含admin/**于是校验通过。Spring Boot看到此路径后,会直接取有效路径/admin/于是就访问成功了。
在shiro的1.5.1及其之前的版本都可以完美地绕过权限检验:
(二)漏洞复现
我这里用的是vulhub的shiro-2020,docker-compose up -d 启动
1、查看环境
docker-compose ps
2、进入页面
ifconfig查看ip后,加上端口即可访问。
3、BP抓包
将url改为admin会显示302跳转到登录界面
将url改为/xxx/..;/admin/后绕过了登录
关闭环境:docker-compose down
(三)原理分析
以/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));
}
此时的URL还是我们传入的原始URL:/xxx/..;/admin/接着,程序会进入到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);
}
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()
getPathWithinServletMapping() 在一般情况下返回的就是 servletPath, 所以本例中返回的就是 /admin/.最终到了/admin/对应的requestMapping, 如此就成功地访问了后台请求.
Download Apache Shiro | Apache Shiro修补方案
总结:
- 客户端请求URL: /xxx/…;/admin/
- Shrio 内部处理得到校验URL为 /xxxx/…;校验通过
- SpringBoot 处理 /xxx/…;/admin/ , 最终请求 /admin/, 成功访问了后台请求