Shiro 权限绕过 CVE-2020-13933分析

阅读量    148547 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

2020-08-19, Apache Shrio发布了CVE-2020-13933的漏洞, 其等级为高,影响范围为<=1.5.3。 在处理身份验证请求时存在权限绕过, 可发送特制的HTTP请求, 绕过身份验证过程并获得对应用的访问. 现对该漏洞进行分析。

 

Diff

可以看一下1.5.31.5.4版本的diff,看变动哪些文件
DIFF链接
web/src/main/java/org/apache/shiro/web/filter/mgt/DefaultFilter.java中,看到diff内容为

package org.apache.shiro.web.filter.mgt;

  import org.apache.shiro.util.ClassUtils;
+ import org.apache.shiro.web.filter.InvalidRequestFilter;
  import org.apache.shiro.web.filter.authc.*;
  import org.apache.shiro.web.filter.authz.*;
  import org.apache.shiro.web.filter.session.NoSessionCreationFilter;
@@ -48,7 +49,8 @@
     rest(HttpMethodPermissionFilter.class),
     roles(RolesAuthorizationFilter.class),
     ssl(SslFilter.class),
 -   user(UserFilter.class);
 +   user(UserFilter.class),
 +   invalidRequest(InvalidRequestFilter.class);

    private final Class<? extends Filter> filterClass;

可以看到添加了一个Filter:InvalidRequestFilter
InvalidRequestFilter.java

public class InvalidRequestFilter extends AccessControlFilter {

    private static final List<String> SEMICOLON = Collections.unmodifiableList(Arrays.asList(";", "%3b", "%3B"));

    private static final List<String> BACKSLASH = Collections.unmodifiableList(Arrays.asList("\\", "%5c", "%5C"));

\\;符号做了标记, 在testFilterBlocks函数中:

    @Test
    void testFilterBlocks() {
        InvalidRequestFilter filter = new InvalidRequestFilter()
        assertPathBlocked(filter, "/\\something")
        assertPathBlocked(filter, "/%5csomething")
        assertPathBlocked(filter, "/%5Csomething")
        assertPathBlocked(filter, "/;something")
        assertPathBlocked(filter, "/%3bsomething")
        assertPathBlocked(filter, "/%3Bsomething")
        assertPathBlocked(filter, "/\u0019something")
    }

其中上可以断定, 是;或者\\字符出的问题.

 

环境搭建

  • git clone https://github.com/l3yx/springboot-shiro.git
  • 在idea中修改om.xml文件,将shiro的版本改为1.5.3
    <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-web</artifactId>
         <version>1.5.3</version>
    </dependency>
    <dependency>
         <groupId>org.apache.shiro</groupId>
         <artifactId>shiro-spring</artifactId>
         <version>1.5.3</version>
    </dependency>
  • 修改LoginController.java 为 因为 在 CVE-2020-11989中有两种利用方式, 所以测试了两种, 分别是

        @GetMapping("/admin/{name}")
        public String admin(@PathVariable String name) {
            return "admin page";
        }
    //    @GetMapping("/admin/page")
    //    public String admin() {
    //        return "admin page";
    //    }

如diff中的, 可以分别测试 localhost:8080/shiro/admin/page 下的不同路径,

  • localhost:8080/%3bshiro/admin/page,
  • localhost:8080/shiro/%3badmin/page,
  • localhost:8080/shiro/admin/%3bpage

其中第三种, 在如上controller中可以成功, 下边详细分析一下。

 

Tomcat远程调试

  • 在idea中导出war包, 并放在tomcatwebapps目录下
  • tomcat/bin/catalina.sh下添加一行debug命令: export JAVA_OPTS='-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005'
  • 在idea中配置远程 调试IP
  • 配置断点即可

 

详细分析

  • 先看调用链

调用链

  • 调用链前半部分为 tomcat请求,然后调用 了springfilter, 接着又调用了shirofilter最后调用了springdispacher, 在 org.apache.shiro.web.util.WebUtils#getPathWithinApplication 函数中, 调用了

    public static String getPathWithinApplication(HttpServletRequest request) {
        return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
    }

getPathWithinApplication

  • 由上可知, uri = "/admin/;aa"org.apache.shiro.web.util.WebUtils#removeSemicolon
    private static String removeSemicolon(String uri) {
        int semicolonIndex = uri.indexOf(59);
        return semicolonIndex != -1 ? uri.substring(0, semicolonIndex) : uri;
    }
即只获取 分号`(; )`前作为返回值 此时返回  `"/admin/"`
  • org.apache.shiro.web.util.WebUtils#normalize(java.lang.String, boolean) 如下,可以看到仅过滤 不同类型的\ /, 所以这里返回 的仍然是 "/admin/"

    private static String normalize(String path, boolean replaceBackSlash) {
        if (path == null) {
            return null;
        } else {
            String normalized = path;
            if (replaceBackSlash && path.indexOf(92) >= 0) {
                normalized = path.replace('\\', '/');
            }

            if (normalized.equals("/.")) {
                return "/";
            } else {
                if (!normalized.startsWith("/")) {
                    normalized = "/" + normalized;
                }

                while(true) {
                    int index = normalized.indexOf("//");
                    if (index < 0) {
                        while(true) {
                            index = normalized.indexOf("/./");
                            if (index < 0) {
                                while(true) {
                                    index = normalized.indexOf("/../");
                                    if (index < 0) {
                                        return normalized;
                                    }

                                    if (index == 0) {
                                        return null;
                                    }

                                    int index2 = normalized.lastIndexOf(47, index - 1);
                                    normalized = normalized.substring(0, index2) + normalized.substring(index + 3);
                                }
                            }

                            normalized = normalized.substring(0, index) + normalized.substring(index + 2);
                        }
                    }

                    normalized = normalized.substring(0, index) + normalized.substring(index + 1);
                }
            }
        }
  • org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain中,代码片段为, 即将 ”/admin/" 处理为 "/admin"

    String requestURI = this.getPathWithinApplication(request);
    if (requestURI != null && !"/".equals(requestURI) && requestURI.endsWith("/")) {
        requestURI = requestURI.substring(0, requestURI.length() - 1);
    }
  • 同样是这个函数, 继续对比 pathPatternrequestURI, 而/admin/*/admin 导致的不匹配,所以绕过了认证

        pathPattern = (String)var6.next();
        if (pathPattern != null && !"/".equals(pathPattern) && pathPattern.endsWith("/")) {
            pathPattern = pathPattern.substring(0, pathPattern.length() - 1);
        }
    } while(!this.pathMatches(pathPattern, requestURI));

  • 继续跟踪在 org.springframework.web.util.UrlPathHelper#decodeAndCleanUriString 函数中
    private String decodeAndCleanUriString(HttpServletRequest request, String uri) {
        uri = this.removeSemicolonContent(uri);
        uri = this.decodeRequestString(request, uri);
        uri = this.getSanitizedPath(uri);
        return uri;
    }
分别去除了分号, URL解码,去除  `//`  , 最后获取到了 `admin/;aa ` 绕过了认证

以上原因即为不同的中间件对于路径去过滤不同, 对于URL编码的解码时间不同,导致了这个漏洞

类似这个 : how-i-chained-4-bugs-features-into-rce-on-amazon

 

参考:

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多