shiro历史漏洞分析—CVE-2020-11989/CVE-2020-13933

声明

出品|先知社区(ID:alter99)

以下内容,来自先知社区的alter99作者原创,由于传播,利用此文所提供的信息而造成的任何直接或间接的后果和损失,均由使用者本人负责,长白山攻防实验室以及文章作者不承担任何责任。

CVE-2020-11989

漏洞信息

漏洞编号:CVE-2020-11989 / SHIRO-782
影响版本:shiro < 1.5.3
漏洞描述:在Shiro<1.5.3的情况下,将Shiro与Spring Controller一起使用时,相应请求可能会导致身份验证绕过。
漏洞补丁:Commit
参考: 腾讯安全玄武实验室Ruilin师傅 边界无限 淚笑师傅

这个漏洞有两种绕过方式,分别由腾讯安全玄武实验室的Ruilin师傅和来自边界无限的淚笑师傅报告

漏洞分析 —— 两次解码绕过

限制

这个场景下需要一些限制条件,首先配置文件的ant风格需要是*而不是**,测试发现,?也可以,另外controller需要接收的request参数(@PathVariable)的类型需要是String,否则将会出错。

图片

复现

首先复现一下,测试版本 1.5.2。
编写Controller

@RequestMapping("/toJsonList/{name}")
    @ResponseBody
    public List<User> toJsonList(@PathVariable String name){
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("alter1");
        user1.setAge(15);
        User user2 = new User();
        user2.setName("alter2");
        user2.setAge(12);
        List<User> userList = new ArrayList<User>();
        userList.add(user1);
        userList.add(user2);
        return userList;
    }

配置对应的shiro.ini

[urls]
/toJsonList/* = authc

此时请求/toJsonList/aaa那么将会被禁止。

图片

但是这里我们可以通过url双编码的方式来绕过。

/ -> %2f ->%25%32%66

图片

测试发现下面四种组合只有前两组可以绕过

yes
/toJsonList/a%25%32%66a
/toJsonList/%25%32%66

no
/toJsonList/%25%32%66a
/toJsonList/a%25%32%66

分析

首先要清楚Shiro支持 Ant 风格的路径表达式配置。ANT 通配符有 3 种,如下所示:

通配符说明
?匹配任何单字符
*匹配0或者任意数量的字符
**匹配0或者更多的目录

解释一下就是/** 之类的配置,匹配路径下的全部访问请求,包括子目录及后面的请求,如:/admin/** 可以匹配 /admin/a 或者 /admin/b/c/d 等请求。

对于/*的话 ,单个*不能跨目录,只能在两个/之间匹配任意数量的字符,如/admin/* 可以匹配 /admin/a 但是不能匹配 /admin/b/c/d。

那么问题来了,如果我们将其配置为/toJsonList/*,但是我们访问形如/toJsonList/a/b这种路径,此时就会绕过访问权限。

我们还记得为了修复CVE-2020-1957,shiro在1.5.2版本进行了更新,将request.getRequestURI()修改为request.getContextPath(),request.getServ-letPath()、request.getPathInfo() 拼接构造uri。

根据网上师傅们的总结,这几个方法的差异性如下:

  • request.getRequestURL():返回全路径;

  • request.getRequestURI():返回除去Host部分的路径;

  • request.getContextPath():返回工程名部分,如果工程映射为/,则返回为空;

  • request.getServletPath():返回除去Host和工程名部分的路径;

  • request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则此返回Null;

第一次解码发生在request.getServletPath()

图片

第二次解码发生在

decodeAndCleanUriString() -> decodeAndCleanUriString() -> decodeRequestString() -> URLDecoder.decode()

图片

因此org.apache.shiro.web.util.WebUtils#getRequest-Uri进行了两次解码,将/toJsonList/a%25%32%66a解码成/toJsonList/a/a

接着就走到org.apache.shiro.util.AntPathMatch-er#doMatch进行权限验证,/toJsonList/a/a不满足配置中的toJsonList/*,因此成功绕过。

但还要看Spring是怎么对其进行解析的,在org.s-pringframework.web.uti.UrlPathHelper#getPathWithinApplication中,将url解析为/toJsonList/a%2fa,这样其实就表示/toJsonList/{name}中的name值为a%2fa。

图片

分析完之后, 也就解释了为什么下面四种组合只有前两组可以绕过
(这种二次解码的方式我测试只适用于1.5.2的版本,之前的版本使用a%25%32%66a测试,因为只有一次解码,会跳转至登陆界面;a%2fa测试直接返回400 Bad Request,应该是请求的问题,希望能有师傅帮忙解答一下)

漏洞分析 —— 根路径差异化解析绕过

限制

  1. 若 Shiro >= 1.5.2 的话,应用不能部署在根目录,如果为根目录则 context-path 为空, CVE-2020-1957 更新补丁将 URL 格式化。

  2. Spring 控制器中没有另外的权限校验代码
    #### 复现
    本次复现使用的是1.4.2版本的shiro所以应用根目录是什么都没有关系
    配置为

/alter/* = authc
/** = anon

新增一个controller

@RequestMapping("/alter/test")
@ResponseBody
public List<User> test(){
 User user1 = new User();
 user1.setName("alter");
 user1.setAge(15);
 List<User> userList = new ArrayList<User>();
 userList.add(user1);
 return userList;
}

输入地址:

http://localhost:8080/;/shirodemo/alter/test

org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver#getChain会进行如下操作获取uri

图片

此时uri结果为/,绕过配置/alter/* = authc,符合配置/** = anon,达到绕过目的。Spring在处理uri时直接进行路径标准化,去掉了分号

图片

图片

Shiro < 1.5.2版本的话,根路径是什么没有关系

图片

漏洞修复

Shiro在Commit中修改了URL获取的逻辑,不单独处理context-path,这样不会导致绕过,同时也避免了二次URL解码的问题。

回退了WebUtils#getRequestUri的代码,并将其标记为@Deprecated

图片

可以看到,shiro建议使用getPathWithin-Application()方法获取路径减去上下文路径,或直接调用HttpServletRequest.getRequestURI()方法获取。

在WebUtils#getPathWithinApplication方法,修改了使用RequestUri去除 ContextPath 的方式,改为使用 getServletPath(request) + getPathInfo(request))。然后使用removeSemicolon方法处理分号问题,normalize方法进行路径标准化。

图片

图片

CVE-2020-13933

漏洞信息

**漏洞编号:**CVE-2020-13933 / CNVD-2020-46579
**影响版本:**shiro < 1.6.0
**漏洞描述:**Shiro 由于处理身份验证请求时存在权限绕过漏洞,特制的HTTP请求可以绕过身份验证过程并获得对应用程序的未授权访问。
**漏洞补丁:**Commit

漏洞分析

这个CVE其实就是对CVE-2020-11989 patch的绕过。上一个CVE使用getServletPath(request)+get-PathInfo(request))获取uri,回顾一下:

  • request.getServletPath():返回除去Host和工程名部分的路径;

  • request.getPathInfo():仅返回传递到Servlet的路径,如果没有传递额外的路径信息,则此返回Null;

Shiro在getChain内进行权限验证,首先通过getPathWithinAppli-cation(request)获得uri。

从下图可以看到,更新后使用HttpServlet-Request.getRequestURI() 方法获取uri;然后使用removeSemicolon去除uri中的分号,这里去除的是分号及分号后面的内容;然后使用normalize进行路径标准化。

图片

此时得到的路径为/hello,绕过了配置中的权限。
接着看Spring是怎么处理路径的:
在org.springframework.web.util#UrlPathHelper中的getPathWithinApplicat-ion方法内,使用getRequest-Uri(request)方法获取uri

图片

与Shiro处理的差异达到既绕过Shiro权限验证又成功访问的目的。

图片

漏洞修复

shiro在1.6.0版本中,org.apache.shiro.spring.web-#ShiroFilterFactoryBean中增加了/**的默认路径配置,使其可以全局匹配进行过滤校验

图片

默认的/**配置对应一个全局的filter:InvalidRequestFilter,这个类继承了 AccessCon-trolFilter。用来过滤特殊字符(分号、反斜线、非ASCII码字符),并返回 400 状态码。

图片

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值