filter设计缺陷导致的权限绕过

  • 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为例例,如下配置的话,在实际接口访问的时候会容错后⾯额外的/:

<servlet-mapping>
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

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

/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)来获取应用系统中的监控信息从而导致信息泄露的事件发生。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值