Shiro权限绕过历史漏洞学习

Posted by kuron3k0 on June 11, 2021

0x00 Shiro简介

Apache Shiro™是一个强大且易用的Java安全框架,能够用于身份验证、授权、加密和会话管理。Shiro拥有易于理解的API,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。

0x01 搭环境

先起一个spring boot,然后装好依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>

几个简单的controller

@PostMapping("/doLogin")
public void doLogin(String username, String password) {
    Subject subject = SecurityUtils.getSubject();
    try {
        subject.login(new UsernamePasswordToken(username, password));
        System.out.println("登录成功!");
    } catch (AuthenticationException e) {
        e.printStackTrace();
        System.out.println("登录失败!");
    }
}

@ResponseBody
@RequestMapping(value="/admin/cmd", method= RequestMethod.GET)
public  String admin(){
    return "in admin panel";
}

配置也比较简单,新建一个继承AuthorizingRealm的类,其中重载的doGetAuthenticationInfo函数认证用的,这里插了一个账号进去;而doGetAuthorizationInfo则是授权用的

class MyRealm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        if("xxxxx".equals(username)){
            return new SimpleAuthenticationInfo(username, "yyyyy", getName());
        }
        return null;
    }
}

然后生成三个bean,一个是刚才的MyRealm;一个是设置了MyRealmDefaultWebSecurityManager;最后是ShiroFilterFactoryBean,注意setFilterChainDefinitionMap设置的map包含了需要认证的url,用authc标记,可匿名访问则是anon。其中/*只匹配下一段url,而/**匹配后面所有的url

@Configuration
class ShiroConfig {
    @Bean
    MyRealm myRealm(){
        return new MyRealm();
    }

    @Bean
    public DefaultWebSecurityManager manager(){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm());
        return manager;
    }

    @Bean
    public ShiroFilterFactoryBean filterFactoryBean(){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(manager());
        factoryBean.setUnauthorizedUrl("/dologin");
        factoryBean.setLoginUrl("/dologin");
        Map<String, String> map = new HashMap<>();
        map.put("/dologin", "anon");
        map.put("/admin/*", "authc");
        factoryBean.setFilterChainDefinitionMap(map);
        return factoryBean;

    }

}

CVE-2020-1957(ver < 1.5.2)

漏洞分析

正常访问/admin/cmd,会跳转到/doLogin

url后面加上斜杠,成功绕过

要实现绕过,就要让shiro匹配不上url,即getChain函数需要返回nullpathMatches函数一直返回false

跟进pathMatches,最后到达AntPathMatcherdoMatch函数,函数对/admin/cmd/以及shiro配置的路径/admin/*做了分词操作,admin匹配了admin,cmd匹配了*,最后到了如下代码

if (pathIdxStart > pathIdxEnd) {
    if (pattIdxStart > pattIdxEnd) {
        return pattern.endsWith(this.pathSeparator) ? path.endsWith(this.pathSeparator) : !path.endsWith(this.pathSeparator);

pathIdxEndpattIdxEnd分别是/admin/cmd//admin/*根据斜杠split后的数组长度,pathIdxStartpattIdxStart每遍历一段就加一,因为是正常遍历,且都匹配上了,所以最终会进到这个分支,因为pattern不是以/结尾的,所以函数的返回就是!path.endsWith(this.pathSeparator),但是因为我们手动添加了一个/在后面,所以最终返回了false,实现了绕过

CVE-2020-11989(ver < 1.5.3)

先说一个坑,spring boot的版本太高也是利用不了的。。因为UrlPathHelpergetPathWithinServletMapping函数中,由于从getPathWithinApplication获取路径的时候存在分号,所以导致context-path(即/shiro)无法删掉,低版本最终会返回servletPath,而高版本是不会返回的,因此导致后续匹配不上Controller的url,最终返回404

漏洞分析

第一种绕过方式:

正常访问跳转到登录页面

在前面添加分号即可绕过

这种比较简单,因为会把分号后面的内容全部去掉,因此只返回了/,匹配不上shiro设置的限制,从而导致绕过

第二种绕过方式: 新建一个controller

@ResponseBody
@RequestMapping(value="/user/{index}", method= RequestMethod.GET)
public String user(@PathVariable String index){
    return "i am user"+ index.toString() + "!";
}

正常访问跳转到登录页面

把带斜杠的字符串双重编码后,绕过成功

url进来的时候,getPathInfo会做一次解码。对于request做解码的函数,可以参考一下mi1k7ea大佬的这篇文章

然后shiro在decodeAndCleanUriString函数会自己再做一次解码

这里对shiro的pattern和url都做了分割,可以看到url比pattern多了一个值

遍历匹配

url比pattern长,所以pathIdxStart指针是没指到最后一个值的,导致最后返回false,从而绕过

CVE-2020-13933(ver < 1.6.0)

漏洞分析

先复现一下

正常访问,跳转登录

加上url编码的分号,绕过成功

先看看shiro怎么取的url,取了servletpath和pathinfo拼起来,然后去掉分号后面的内容

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

最终调用的是request的getServletPathgetPathInfo,但是request的getServletPathgetPathInfo是会做url解码的

所以最终会把%3b解码后,去掉后面的内容,/user自然与/user/*匹配不上

CVE-2020-17523(ver < 1.7.1)

漏洞分析

正常访问,跳转到登录页面

换成%20成功绕过

来看一下shiro是怎么解析url的,先解码成/user/

然后会分别对shiro的pattern和url用斜杠进行分割,但是shiro分割逻辑存在问题,对/user/ 分割的时候,分成了user和空格,并会对其进行trim操作,所以我们的空格就被过滤掉了,然后因为token.length() <= 0的值为true,这个空格就没有被加到数组中

很明显看到分割出来的两个数组长度是不一样的,从而完成绕过

而spring不会把空格去掉,匹配上正确的控制器

参考