最简单的测试方法就是不登录用户,直接访问要测试的需要登录的功能模块,如果可以访问成功,则说明存在漏洞。
我们期望的应该是:
有些系统是在cookie中添加一个类似userId的字段来标识是否登录成功,这样的系统可以通过伪造cookie信息来进行测试。
我们期望的应该是:
水平越权常见于业务系统中,例如对用户信息或者订单信息进行增删改查操作时,由于用户编号或者订单编号有规律可循(有序递增,订单编号常发现以日期开头后面再接几位有序增长的数字,类似20200520xxxx1,20200520xxxx2),测试人员通过burpsuite的 intruder对目标参数进行遍历测试即可,如发现用户A能操作用户B的用户信息或者订单信息,则说明存在漏洞,其中用户A和用户B是具有同等权限的用户。
我们期望的应该是:
准备两个用户,用户A是一个低权限角色的用户,用户B是管理员角色的用户,对某些应该只有管理员角色才有访问权限的功能模块,使用用户A登录进行访问,如果能正常访问,则说明漏洞存在。这里有些系统是前后端分离,可能直接访问前端用户A是看不到用户B的某些菜单的,可以先使用用户B登录系统,将相关链接或接口记录下来,之后再用用户A登录,来直接访问记录下来的链接或相关接口。
我们期望的应该是:
对于越权问题的解决思路是对用户访问的资源进行拦截,判断当前用户是否有该资源的权限,如果有则放行,请求用户要访问的资源,如果没有则直接返回相应的提示,不再请求用户要访问的资源。
在SpringWeb项目中要解决越权问题,大体上可以有2种解决方案。
以下给出两种解决方案的可参考的代码实现(实际使用过程中,根据自身项目情况进行改造)
项目环境:
JDK 1.8
Maven 3.6.3
SpringBoot项目,版本如下
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.5version>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<version>2.6.5version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.78version>
dependency>
<dependencies>
这里提供的是基于注解方式实现的。
1 AuthorizeFilter过滤器
/**
* filterName:指定过滤器的名字
* urlPatterns: 指定需要过滤的地址
* @author luojj
* @since 2022-04-11 18:27
*/
@WebFilter(filterName = "authorizeFilter",urlPatterns = {"/user/*"})
public class AuthorizeFilter implements Filter {
private static final String USER_KEY = "APP_USER_ID";
@Autowired
UserService userService;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
final String userId = request.getHeader(USER_KEY);
if(canAccess(userId,request.getRequestURI())){
filterChain.doFilter(servletRequest,servletResponse);
return;
}
//处理鉴权失败
//final Rsp> rsp = Rsp.fail(403, "无权访问");
//这里实际项目上可以使用项目定义的Rsp对象
Map<String,Object> rsp = new HashMap<>(2);
rsp.put("code",403);
rsp.put("msg","无权访问");
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("application/json; charset=utf-8");
final PrintWriter writer = servletResponse.getWriter();
writer.write(JSON.toJSONString(rsp));
}
private boolean canAccess(String userId,String path) {
if(userId == null){
return false;
}
//此处是具体查询用户具有哪些权限的,具体使用自己的项目上的Service方法
final Set<String> userAccessUrls = userService.getUserAccessUrls(userId);
return userAccessUrls.contains(path);
}
}
2 需要在springboot启动类上添加以下注解,保证spring可以扫描到我们自己实现的过滤器
@ServletComponentScan(basePackageClasses = {AuthorizeFilter.class})
对于方案1至此就实现完成了。
1 AuthorizeInterceptor 拦截器
@Component
public class AuthorizeInterceptor implements HandlerInterceptor {
private static final String USER_KEY = "APP_USER_ID";
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!(handler instanceof HandlerMethod)){
return true;
}
final String userId = request.getHeader(USER_KEY);
if(canAccess(userId,request.getRequestURI())){
return true;
}
//处理鉴权失败
// final Rsp rsp = Rsp.fail(403, "无权访问");
Map<String,Object> rsp = new HashMap<>(2);
rsp.put("code",403);
rsp.put("msg","无权访问");
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
final PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(rsp));
return false;
}
private boolean canAccess(String userId,String path) {
if(userId == null){
return false;
}
final Set<String> userAccessUrls = userService.getUserAccessUrls(userId);
return userAccessUrls.contains(path);
}
}
2 鉴权拦截器相关MVC配置
@Configuration
@AllArgsConstructor
public class AuthorizeWebMvcConfig implements WebMvcConfigurer {
AuthorizeInterceptor authorizeInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authorizeInterceptor)
.addPathPatterns("/**")
//这是指定排除哪些访问地址不拦截
.excludePathPatterns("/swagger-resources","/v2/api-docs");
}
}
对于第2种方案至此就实现完成了。