shiro权限绕过总结

date
Mar 16, 2022
slug
shiro-bypass-auth
status
Published
tags
Java安全
安全研究
summary
总结一下shiro权限绕过的内容
type
Post

重写doGetAuthenticationInfo(用户身份认证信息)方法导致的问题

总结一下:就是session的处理问题
代码例子如下:
@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException,NumberFormatException {
        if (token.getPrincipal()==null){
            throw new UnknownAccountException();
        }
        Integer studentId = Integer.valueOf((String) token.getPrincipal());
        //取出数据库中的指定User
        User user= Optional.ofNullable(userMapper.selectByStudentId(studentId)).orElseThrow(UnknownAccountException::new);
        SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        Session session= SecurityUtils.getSubject().getSession();
        //存到shiro的session中(对于Web来说本质是HttpSession)
        session.setAttribute("USER_SESSION",user);
        return info;
    }
登陆处:
Subject subject=SecurityUtils.getSubject();
UsernamePasswordToken token=new UsernamePasswordToken(studentId,password);
        try {
            subject.login(token);
        }
API身份校验:
if(!SecurityUtils.getSubject().isAuthenticated()) {
            return resultJson.error(401,"未授权");
        }
User user = CommonUtil.getUserFromShiroSession();
//其中的getUserFromShiroSession():
public static User getUserFromShiroSession(){
        return (User)SecurityUtils.getSubject().getSession().getAttribute("USER_SESSION");
    }
这里的逻辑是身份验证时,先验证当前的Subject是否已经授权,如果已经授权的话,获取当前用户采用了从shiro的session中获取的方法
这里会出现什么问题?
根据作者的调试,得出这样的结论:
即使登录失败,我们在doGetAuthenticationInfo中设置的session属性依然可以生效。而且由于异常栈抛出的过程中并没有创建subject,也不会重新设置authenticated的状态。
这里的认证状态指的是下图这个变量:
notion image
 
所以就会存在这样的一个问题:
当用户重复登录的时候(即使登陆失败),会改变session中的USER_SESSION的值,但是并不会改变用户isAuthenticated的状态(所以对应API的权限就会改变)
当A用户使用自己的账号登录成功之后(这时Subject.isAuthenticated()已经变成了true),带着登录成功的Session,尝试登录另一个用户B的账号,Shiro在Subject.login()的时候调用了我们重写的doGetAuthenticationInfo(AuthenticationToken token)方法。
这时候,Session中的USER_SESSION的值已经变成了用户B的信息,而且shiro这时并不会更新isAuthenticated的状态,这样一来用户A就可以绕开了身份验证,能够以用户B的身份访问其他的API
作者攻击的一个流程:
登陆一个已知用户(该用户不可使用对应用户的接口)→获得session→然后再次登陆对应权限admin的用户(即使该用户密码错误)→此时isAuthenticated无改变,但是session变成了admin用户的session→触发admin权限api
 
所以最好的方法就是修改api权限校验点:
获取当前用户的方法应该为,不再使用对应的session进行判断
(User) SecurityUtils.getSubject().getPrincipal()
这里是因为Subject中的principal只有在用户成功登录之后才进行更新。

shiro与spring组合下对uri处理差异导致的漏洞

用的这个demo,需要打包成war部署再进行测试,不然直接Springboot启动会报错,IDEA直接整就完事了:
org.syclover.srpingbootshiro.ShiroConfig 里面filter设置了相关的权限,admin路由还是需要权限校验的
notion image
一些小知识点:
设置权限的相关属性
notion image
还有一些相关通配符
?:匹配一个字符
*:匹配零个或多个字符串
**:匹配路径中的零个或多个路径
对应的Controller需要修改一下
notion image
 
CVE-2020-11989
  • Apache Shiro < 1.5.3
  • Spring 框架中只使用 Shiro 鉴权
直接访问admin路由下的内容肯定是不可行的
notion image
当然这个场景下需要一些限制条件,首先权限ant风格的配置需要是*而不是**,同时controller需要接收的request参数(@PathVariable)的类型需要是String,否则将会出错。
当数据包是这样的时候
GET /shiro_war/admin/gss%252fe HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Connection: close
Cache-Control: max-age=0
就可以触发权限绕过
可以调试查看,开始的触发点在org.apache.shiro.web.util.WebUtils#getPathWithinApplication
getRequestUri 这一步的时候已经url解码完成了
notion image
跟进这个getRequestUri函数看一下,此时处理的uri是这样的,拼接到shiro_war 下了,但是会经过normalize,decodeAndCleanUriString 两个函数的处理
notion image
先看decodeAndCleanUriString 做了什么,先进行了一遍url解码(此时两次URL编码已经解码完了),然后从解码的内容里面寻找 如果找到; 截取; 之前的内容返回
notion image
再经过normalize函数的处理,主要是对下面这几种符号做处理,跟Tomcat的normalize处理差不多
notion image
处理之后从/shiro_war/admin/gss%252fe —> /shiro_war//admin/gss/e ,传递的2次编码url已经被完全decode。下一步就是Shiro的filter对请求资源的权限校验,主要的步骤跟进AntPathMatcher.class#doMatch
notion image
此时该函数会根据Shiro里面配置的ant来进行匹配,因为配置里面存在admin/* 所以会对此进行匹配
然后将路径拆分一个个进行匹配,通过matchStrings函数与pathDirs数组中的值进行匹配,匹配失败会直接返回false(也就是证明没有权限问题)
notion image
如果匹配到了则会每次使pattIdxStart++,例如这里的pattern数组分为[admin,*],则会依次匹配到admingss,那么pattIdxStart为2
是pattern数组的末尾索引,这里为1
notion image
而在之后的判断中,赋值后的pattIdxStart > pattIdxEnd返回false绕过了判断
notion image
也就是说,/*这种匹配只能命中/admin/gss这种格式,无法命中/admin/gss/xxx
返回false之后就能触发到相关路由了,在这个函数里可以找到对应的处理结果,Spring的url处理模块应该是org.springframework.web.util.UrlPathHelper#getLookupPathForRequest(javax.servlet.http.HttpServletRequest)
可以看到对应的DispatchPath
notion image
但是从结果看,可以发现本质上Spring框架只对内容进行了一次的url编码处理(下图中还剩下一个%2f没有解码),但是Shiro框架处理的时候会全部进行解码,这个trick可以记录一下
notion image
修复:
入口的函数修改成这样:
public static String getPathWithinApplication(HttpServletRequest request) {
        return normalize(removeSemicolon(getServletPath(request) + getPathInfo(request)));
    }
采用标准的 getServletPath 和 getPathInfo 进行uri处理,这样pattIdxStartpattIdxEnd就是相同的数值
notion image
CVE-2020-13933
  • Apache Shiro < 1.6.0
  • Spring 框架中只使用 Shiro 鉴权
问题出现在补丁中的removeSemicolon函数,触发的payload如下:
GET /shiro_war/admin/%3beee HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Connection: close
Cache-Control: max-age=0
 
调试可以发现经过removeSemicolon函数之后,它就是返回”;”之前的内容,实际上是为了处理GET请求中”/admin/;jessid=xxxx”这样的需求
此时path为/admin ,因此也不属于不属于请求/admin/*下的资源,因此不会被鉴权,此时会导致这个条件其实不通过,也就是不属于/admin/* 资源的意思,最后执行到return false
notion image
最后就可以直接进入contrller了
notion image
修复:
是在上面漏洞的流程走完之后,添加上一个InvalidRequestFilter 过滤器类,在处理到Spring框架之前会过滤一些内容
notion image
监测到就直接抛400了
notion image
CVE-2020-17523
  • Apache Shiro < 1.7.1
  • Spring 框架中只使用 Shiro 鉴权
这次出发的payload是
GET /shiro_war/admin/%20 HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Connection: close
Cache-Control: max-age=0
此时可以访问对应的接口
notion image
继续跟doMatch方法,发现处理之后的path变量时代有一个空格的/admin/
执行到/Users/4me/.m2/repository/org/apache/shiro/shiro-core/1.6.0/shiro-core-1.6.0-sources.jar!/org/apache/shiro/util/AntPathMatcher.java:144 这个点的时候会进行一次匹配,但是并不符合,然后就进入下面返回false的流程里面了,导致了绕过,又因为没有相关奇怪的符号,就导致之前的那个补丁也绕过了
notion image
主要原因还是在这个处理逻辑上面的循环函数里面,由于path只包含一个元素,因此我们的pathIdxStartpathIdxEnd初值均为0;经过一次循环后,pathIdxStart自增为1,跳出了这个循环判断,而且对path变量处理的时候会自动trim,导致了pathIdxEnd 计算的时候没有把空格算上
notion image
notion image
所以在寻找绕过的方法的时候,可以针对这个trim函数可以处理的空格入手,多找几个,就多了几种操作方式,有人测试了,发现其他空格不太可行
notion image
修复:
默认不进行trim操作
notion image

总结

1.关键处理函数在org.apache.shiro.util.AntPathMatcher#doMatch
notion image

© 4me 2021 - 2024