沉铝汤的破站

IS LIFE ALWAYS THIS HARD, OR IS IT JUST WHEN YOU'RE A KID

Shiro漏洞复现之未授权访问

0x00 前言


最近代表某不知名学校参加了一场比赛,结果比完之后要我们去自费核酸检测,还不报销,天使学校,给cheater发奖学金,都不肯报销核酸检测的60块钱。因为有比赛,所以就有一周的时间没有学习了。本篇包含以下元素:

  • Shiro-682 (<=1.4.9的未授权访问漏洞,在1.5.0被修复,但仍可被其他方法绕过)
  • CVE-2020-1957 (<=1.5.1的未授权访问漏洞,在1.5.2被修复)

0x01 Shiro-682


漏洞简介

在Spring中,形如/admin/show/admin/show/的URL都能够成功访问admin页面。但由于Shiro拦截器的通配符问题,会导致虽然用通配符设定了访问admin下所有页面都需要先登录,却会因通配符只能够匹配到admin/show却无法匹配到admin/show/而存在绕过并且也能成功访问相关页面。

Shiro拦截器

在Shiro中,我们可以通过在Shiro.ini中对URL进行拦截器设置,不同的拦截器有不同的效果,其中一种常见的拦截器是:authc。当对一个路径设置了此种拦截器后,当访问时,便需要先登录才能够访问。如:

[urls]
/admin/show = authc

在进行完上面的设置之后,当我们访问/admin/show时便需要先登录才能够访问。但如果我们需要让admin下所有目录都需要先登录时,是不是只能一个个页面进行配置呢?并不是,在Shiro中为我们准备了通配符,如下:

? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;

\* : 匹配零个或多个字符串,如 /add* ,匹配 /addtest

** : 匹配路径中的零个或多个路径,如 /user/** 将匹配 /user/xxx 或 /user/xxx/yyy

所以我们似乎可以通过如下设置来让admin下所有页面都需要认证:

[urls]
/admin/* = authc

漏洞详情

上面的设置看似是可行的,但值得注意的是*通配符只支持字符而不支持路径,当我们输入/admin/show/的时候并不会被成功匹配,所以也就不会进行拦截,进而便实现了拦截器的绕过而不会跳转到登录,而在Spring中,admin/showadmin/show/都是可以正常访问show页面的,这就造成了未授权的访问。

漏洞修复

在1.5.0版本中,合并了国内开发者的PR,对此漏洞进行了修复(当然下面的代码还有点Bug,后来在1.5.1又合并了PR一次)。我们可以在Github中找到:[SHIRO-682]

#web/src/main/java/org/apache/shiro/web/filter/PathMatchingFilter.java 
    
private static final String DEFAULT_PATH_SEPARATOR = "/";
//...
String requestURI = getPathWithinApplication(request);
if (requestURI != null && requestURI.endsWith(DEFAULT_PATH_SEPARATOR)) {
    requestURI = requestURI.substring(0, requestURI.length() - 1);
}
if (path != null && path.endsWith(DEFAULT_PATH_SEPARATOR)) {
    path = path.substring(0, path.length() - 1);
}
//...

可以看到,当你用xxx/访问时,会进行截取,变成xxx,然后再进行匹配。

0x02 CVE-2020-1957


漏洞简介

虽然避免了通配符的匹配问题,但是Shiro的绕过还存在另外一种方式。

我们知道在浏览器的搜索栏使用http://xxx.com/admin/foobar/../show或者http://xxx.com/foobar/../admin/show时,浏览器会帮我们自动变成http://admin/show。而在Spring中也是如此,使用admin/foobar/../show依然可以访问到admin/show。由于Shiro<=1.5.1版本中拦截过程中对URI的获取会进行清洗,而导致了带有分号的URL会被截断一部分,如:/foobar;/../admin/show会被截断为/foobar然后进行拦截匹配,这样便绕过了过滤器的设置,而因为/foobar;/../admin/show也能成功访问,从而造成了未授权访问。

漏洞详情

注意到我们上一个漏洞,有这么一行代码: String requestURI = getPathWithinApplication(request);,跟进这个函数,代码如下:

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;
    }
}

跟进getRequestUri:

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));
}

最后跟进到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);
}

发现会对有无分号;进行判断,如果有,则只会截取分号前的部分。这就导致了当我们输入形如/foo;/../admin/show的URL只截取到/foo..,这个URL不符合我们拦截器中的设定,所以便绕过了拦截器,但是/foo;/admin/show又能够正常访问到/admin/show,所以产生了未授权访问。

漏洞修复

在1.5.2版本中,对此漏洞进行了修复,原来的 uri = request.getRequestURI();直接获取请求URL,改为了如下:

if (uri == null) {
    uri = valueOrEmpty(request.getContextPath()) + "/" +
    valueOrEmpty(request.getServletPath()) +
    valueOrEmpty(request.getPathInfo());
}

getContexPath返回的是Web应用所在的目录;getServletPath返回的是web.xml中url-pattern完全匹配的部分,如url-pattern=/admin/*,当访问/admin/show时,返回的/admin,getPathInfo则是返回url-pattern中通配符匹配到的那一部分,如/show

所以当我们访问http://xxx.com/foobar;/../admin/show时,得到的路径会为://admin/show, 再经过normalize进行标准化处理之后,就会变成/admin/show,于是便不能绕过拦截器。

0x03 参考


Shiro权限绕过漏洞分析(CVE-2020-1957) - FreeBuf网络安全行业门户