过滤器设计缺陷导致权限绕过
权限控制的本质
  一般来说,为了防止越权操作,通常会结合filter进行相关接口的鉴权操作。其中不外乎就是对每一个接口(通俗来说就是我们的URI/URL)进行业务梳理,然后判断当前URI/URL是否具有相应的业务权限。
常见权限控制的实现
一般情况下,通常是获取到当前URI/URL,然后跟需要鉴权的接口进行比对,或者直接结合startsWith()或者endsWith()方法,设置对应的校验名单。当然也有结合一些类似Shiro、Spring Security等比较成熟的框架进行权限控制。
最简单的例如下面的过滤器实现,以/login开头的不需要校验(登陆业务每个人都可以访问),所有.do/.action结尾的接口均需要做登陆检查,防止未授权访问等。
java
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()这两个方法,但是如果没有进行相关的处理的话,有可能导致权限控制绕过的风险。
绕过方式
在进行权限测试时,平行越权&垂直越权是比较常见的测试点,一般都是结合cookie会话进行测试,例如删除cookie尝试未授权访问,以低权限用户的cookie尝试访问高权限admin用户的接口,看看是否可以进行垂直越权访问等。
但是,当权限过滤器获取当前request中的URI/URL使用request.getRequestURL()和request.getRequestURI()这两个方法时,可以考虑以下三种方式进行权限绕过:
非标准化绕过
以一个案例进行说明,例如/system/login开头的接口是白名单,不需要进行访问控制(登陆页面所有人都可以访问),其他接口都需要进行登陆检查,防止未授权访问:
java
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为:
request.getRequestURI=/system/login/../UserInfoSearch.do
到这里就很明了,同样是前面的过滤器,那么我们可以通过在URI中写入/login/../,使得权限过滤器认为我们当前访问的接口为白名单接口,从而绕过权限控制,使得系统认为我们当前访问的接口是登陆login,不需要进行权限校验,但是最后我们却在非登录情况下访问到实际业务了:
那么在黑盒测试时,可尝试通过在URI引入类似/login(白名单接口,可以通过测试得出,一般登陆都是不需要权限校验的)/../的样式,伪造白名单接口,进行权限绕过。
URL截断绕过
以一个案例进行说明,同样的例如/system/login开头的接口是白名单,不需要进行访问控制(登陆页面所有人都可以访问),其他接口都需要进行登陆检查,防止未授权访问,但是考虑到了../的非法访问问题:
java
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
URL编码绕过
以一个案例进行说明,例如/system/UserInfoSearch.do接口是管理员才能访问的接口,需要进行用户检查,防止越权访问:
java
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解码操作的,也就是说如果我们尝试对访问的/UserInfoSearch.do进行URL编码,访问/system/%55%73%65%72%49%6e%66%6f%53%65%61%72%63%68%2e%64%6f实际获取到的URI为:
request.getRequestURI=/system/%55%73%65%72%49%6e%66%6f%53%65%61%72%63%68%2e%64%6f
那么除了前面介绍的两种方法以外,我们可以通过对URI进行URL编码,此时filter中得到的uri并不是正常的/system/UserInfoSearch.do,而是编码后的。
但是filter转发请求后浏览器可以解码并正常解析,从而达到以低权限用户绕过权限控制访问管理员接口的效果:
那么在黑盒测试时,可尝试对URI进行URL编码/多重URL编码,尝试绕过。
修复建议及防御
以Java为例:
(1)使用如下方法进行相关路径的获取:
java
request.getServletPath()+(request.getPathInfo() == null ? "" : request.getPathInfo());
(2)对相关接口访问进行标准化处理,剔除不相关的元素,例如../,分隔符(;)后的内容等;
这里推荐使用ESAPI的canonicalize方法进行集成,对相关输入进行规范化处理,样例代码如下:
java
ESAPI.encoder().canonicalize(URI)
同时在对应的配置文件ESAPI.properties禁用双重uri编码(默认开启):
java
Encoder.AllowMultipleEncoding=false
(3)尽量不要使用类似startsWith()、endsWith()进行判断;
(4)使用成熟的权限控制框架进行权限校验。(Spring Security、Shiro等)
前言 这篇文章是零度安全2021年的首篇文章,后期将会陆续更新打靶,实战系列的文章。 在这里也感谢大家对零度安全的支持与信任。前期准备工作今天…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论