参考来源:https://blog.csdn.net/qq_38154820/article/details/106799046

    1.1 权限控制的本质

    一般来说,为了防止越权操作,通常会结合filter进⾏相关接⼝的鉴权操作。其中不不外乎就是对每⼀个接口(通俗来说就是我们的URI/URL)进行业务梳理,然后判断当前URI/URL是否具有相应的业务权限。

    1.2 常见权限控制的实现

    一般情况下,通常是获取到当前URI/URL,然后跟需要鉴权的接口进行⽐对,或者直接结合startsWith()或者endsWith()方法,设置对应的校验名单。

    例如下⾯的过滤器实现,以/login开头的不需要校验(登陆业务每个人都可以访问),所有.do/.action结尾的接⼝均需要做登陆检查,防止未授权访问等。

    String uri = request.getRequestURI();
    if(uri.endsWith(“.do”)||uri.endsWith(“.action”)) {
    //检测当前用户是否登陆
    User user =(User) request.getSession().getAttribute(“user”);
    if(user==null|| “”.equals(user)) {
    errorResponse(response, paramN, “未授权访问”);
    return;
    }
    }
    但是,在Java中获取当前request中的URI/URL通常会使用request.getRequestURL()和request.getRequestURI()这两个方法,但是如果没有进⾏相关的处理的话,有可能导致权限控制绕过的风险。

    1.3 绕过方式

    当权限过滤器获取当前request中的URI/URL使用request.getRequestURL()和request.getRequestURI()这两个方法时,可以考虑以下三种⽅式进行权限绕过:

    1.3.1 非标准化绕过

    相关场景:

    例如/system/login开头的接口是白名单,不需要进行访问控制(登陆页面所有人都可以访问),其他接⼝都需要进⾏登陆检查,防止未授权访问:

    String uri = request.getRequestURI();
    if(uri.startsWith(“/system/login”)) {
    //登陆接口设置⽩白名单
    filterChain.doFilter(request, response);
    }
    else if(uri.endsWith(“.do”)||uri.endsWith(“.action”)) {
    //检测当前⽤户是否登陆
    User user =(User) request.getSession().getAttribute(“user”);
    if(user==null|| “”.equals(user)) {
    errorResponse(response, paramN, “未授权访问”);
    return;
    }
    }
    相关效果如下:

    当未登录直接访问UserInfoSearch.do接口时,显示未授权访问:

    相关原理:

    中间件在进⾏解析时,会对我们URI中的../进行相关处理从⽽得到相关的servlet。也就是说尝试对我们访问的URL引入../,中间件是可以正常解析并完成正常业务的,以tomcat中的examples目录中的案例servlet访问为例,尝试访问一个不存在的目录login,然后通过../回到正常目录下,正常解析:

    绕过分析:

    使用request.getRequestURL()和request.getRequestURI()这两个方法进⾏访问接口的获取时,是不会对类似../等进⾏规范化处理的,也就是说刚刚我们访问的/system/login/../UserInfoSearch.do际获取到的URI为:

    同样是前⾯的例子,那么我们可以通过在URI中写⼊/login/../,使得权限过滤器认为我们当前访问的接⼝为白名单接口,从而绕过权限控制,使得系统认为我们当前访问的接⼝是登陆login,不需要进行权限校验:

    绕过方法:

    在URI引⼊入类似/login(白名单接口,可以通过测试得出,一般登陆都是不需要权限校验的)/../的
    样式,伪造⽩名单接口。
    1.3.2 URL截断绕过

    相关场景:

    例如/system/login开头的接⼝是⽩名单,不需要进行访问控制(登陆⻚面所有人都可以访问),其他接口都需要进行登陆检查,防止未授权访问,但是考虑到了../的非法访问问题:

    String uri = request.getRequestURI();
    if(uri.contains(“./“)){
    errorResponse(response, paramN, “⾮非法访问”);
    return;
    }
    else if(uri.startsWith(“/system/login”)) {
    //登陆接口设置⽩白名单
    filterChain.doFilter(request, response);
    }
    else if(uri.endsWith(“.do”)||uri.endsWith(“.action”)) {
    //检测当前用户是否登陆
    User user =(User) request.getSession().getAttribute(“user”);
    if(user==null|| “”.equals(user)) {
    errorResponse(response, paramN, “未授权访问”);
    return;
    }
    }
    相关效果如下:

    当未登录直接访问UserInfoSearch.do接⼝时,显示未授权访问:

    尝试结合../伪造白名单,失败:

    相关原理:

    URL中有一个保留字符分号(;),主要作为参数分隔符进行使用,有时候是请求中传递的参数太多了,所以使用分号(;)将参数对(key=value)连接起来作为一个请求参数进⾏传递。

    直接在URI中引入分隔符,正常来说是不会对实际接口的访问造成影响的。

    绕过分析:

    对于request.getRequestURL()和request.getRequestURI()来说,使用&连接的参数键值对,其是获取不到的,但是参数分隔符(;)及内容是可以获取到的:

    同样是前面的例子,访问.do结尾的接口需要进行登陆检查,否则认为未授权访问,那么此时可以利用分隔符,绕过endsWith()检测,使得权限过滤器认为我们访问的接口不是业务接口,从而达到绕过权限控制的效果:

    绕过⽅法:

    在URI引⼊入参数分隔符;,进⾏切割URI绕过限制,例例
    如/system;Bypass/UserSearch.do;Bypass
    1.3.3 URL编码绕过

    相关场景:

    例如/system/UserInfoSearch.do接⼝是管理员才能访问的接口,需要进⾏⽤户检查,防止越权访问:

    if(uri.equals(“/system/UserInfoSearch.do”)){
    User user =(User) request.getSession().getAttribute(“user”);
    String role = user.getRole();
    if(role.equals(“admin”)) {
    //当前⽤用户为admin,允许访问该接⼝
    filterChain.doFilter(request, response);
    }
    else {
    errorResponse(response, paramN, “越权访问”);
    return;
    }
    }
    相关效果如下:

    若不是admin用户登陆,拒绝访问UserInfoSearch.do接口:

    否则返回当前系统存在的用户名:

    相关原理:

    当filter处理完相关的流程后,中间件会对请求的URL进行一次URL解码操作,然后再找到对应的Servlet进行访问。

    也就是说尝试对我们访问的URL进行一次URL编码,中间件是可以正常解析并完成正常业务的,以tomcat中的examples⽬目录中的案例servlet访问为例,尝试将HelloWorldExample进行URL编码再进行访问,正常解析:

    绕过分析:

    除了前面介绍的两种方式以外,这里存在一个问题,使用request.getRequestURL()和request.getRequestURI()这两个⽅法进⾏行行访问接口的获取时,是不会进行URL解码操作的,也就是说刚刚我们访问的

    /system/%55%73%65%72%49%6e%66%6f%53%65%61%72%63%68%2e%64%6f实际获取到的URI为:

    那么我们可以通过对URI进行URL编码,此时filter中得到的uri并不是正常的/system/UserInfoSearch.do,⽽是编码后的,但是filter转发请求后浏览器可以解码并正常解析,从而达到以低权限用户绕过权限控制访问管理员接口的效果:

    绕过⽅法:

    对URI进行URL编码/多重URL编码,尝试绕过。
    1.3.4 Spring Web的动态Controller追加/绕过

    相关场景:

    一般情况下,通常是获取到当前URI/URL,然后跟需要鉴权的接⼝进行比对,结合endsWith()方法,设置对应的校验名单。

    例如下面的过滤器实现,所有.do、.action结尾的接口均需要做登陆检查,防⽌止未授权访问等。

    String uri = request.getServletPath()+(request.getPathInfo() == null ?
    “” : request.getPathInfo());
    if(uri.endsWith(“.do”)||uri.endsWith(“.action”)) {
    //检测当前用户是否登陆
    User user =(User) request.getSession().getAttribute(“user”);
    if(user==null|| “”.equals(user)) {
    errorResponse(response, paramN, “未授权访问”);
    return;
    }
    }
    相关原理:

    特定情况下,Spring web在匹配url接⼝的时候会容错后⾯额外的/。

    以Spring MVC为例例,如下配置的话,在实际接口访问的时候会容错后⾯额外的/:


    SpringMVC
    /

    例如以下两种访问方式效果都是一样的:

    /admin/info.do?param=value
    /admin/info.do/?param=value
    绕过分析:

    考虑到上⾯使用request.getRequestURL()和request.getRequestURI()这两个⽅方法进行访问接⼝的获取时存在的安全隐患,这里使用 request.getServletPath()+(request.getPathInfo() == null ? “” :

    request.getPathInfo())进行路径的获取,但是如果在接口URI后追加额外的/,还是可以获取到的:

    根据上⾯的filter代码,正常情况下非登陆访问/admin/info.do,会提示未授权访问:

    尝试在接口后追加额外的/,成功绕过权限校验:

    绕过方法:

    尝试在对应的URL接口后加入/,以绕过权限控制。

    1.4 修复建议及防御方式

    以Java为例:

    1.使用如下⽅法进⾏相关路路径的获取:

    request.getServletPath()+(request.getPathInfo() == null ? “” :
    request.getPathInfo());
    2.对相关的接口访问进行标准化处理,剔除不相关的元素,例如../,分隔符(;)后的内容以及接⼝后不必要的/等;

    3.使用ESAPI的canonicalize方法进行规范化处理:

    ESAPI.encoder().canonicalize(URI)
    同时在对应的配置⽂文件ESAPI.properties禁⽤用双重uri编码(默认开启):

    Encoder.AllowMultipleEncoding=false
    具体效果如下,当接收到双重url编码时,会触发报错:

    4.尽量不要使用类似startsWith()、endsWith()进⾏判断。

    5.使用成熟的权限控制框架进行权限校验。(Spring Security、Shiro等)

    1.5 思维导图

    相关实操练习:Springboot未授权访问 ——Actuator 是 springboot 提供的用来对应用系统进行自省和监控的功能模块,非法用户可通过访问默认的执行器端点(endpoints)来获取应用系统中的监控信息从而导致信息泄露的事件发生。