目录
由于笔者个人水平有限,行文如有不当,还请各位师傅评论指正,非常感谢
Apache Shiro是一款开源安全框架,它的功能主要用于身份验证、授权、会话管理、加密......漏洞发生原因是:登入的时候序列化保存了登入信息到cookie(序列化、AES加密、Base64)
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 启动

docker-compose ps


ifconfig查看ip后,加上端口即可访问。


将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/, 成功访问了后台请求