从 mall 0day IDOR 挖掘看 Spring 权限管理
字数 1704 2025-09-01 11:25:54
Spring权限管理与IDOR漏洞挖掘实战指南
一、Spring权限管理概述
在企业级应用中,权限控制是核心安全环节。Spring生态提供了丰富的权限管理手段,主要分为两类:
- 垂直权限(Vertical Access Control):控制"角色等级"对资源或操作的访问
- 水平权限(Horizontal Access Control):防止用户操作自己无权访问的同级资源
二、垂直权限实现方式
1. HandlerInterceptor拦截器
在Spring MVC中,可以通过HandlerInterceptor的preHandle()方法在请求到达Controller前进行权限校验:
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 权限校验逻辑
if(!hasPermission(request)){
response.sendError(403);
return false;
}
return true;
}
}
优点:逻辑集中,可统一管理
缺点:复杂规则需要手写,维护成本高
2. Spring Security方案
URL级权限:通过配置URL与角色映射控制访问
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER");
方法级注解:
@PreAuthorize:方法执行前校验@PostAuthorize:方法执行后校验(可基于返回值判断)
@PreAuthorize("hasRole('ADMIN') or hasPermission(#id, 'read')")
public String sensitiveMethod(Long id) {
// ...
}
自定义PermissionEvaluator:实现复杂业务规则
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object target, Object permission) {
// 自定义权限判断逻辑
}
}
3. Apache Shiro方案
支持细粒度权限控制,如"document:read:123"表示对ID为123的文档有读权限
@RequiresPermissions("document:read:#documentId")
public Document getDocument(Long documentId) {
// ...
}
4. 自定义注解+AOP
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourcePermission {
String resourceType();
String permission();
}
@Aspect
@Component
public class PermissionAspect {
@Around("@annotation(resourcePermission)")
public Object checkPermission(ProceedingJoinPoint joinPoint,
ResourcePermission resourcePermission) {
// 权限校验逻辑
if(!hasPermission(resourcePermission.resourceType(),
resourcePermission.permission())){
throw new AccessDeniedException("无权限");
}
return joinPoint.proceed();
}
}
三、水平权限实现方式
水平权限检查依赖资源归属,通常需要分散在业务逻辑或数据库查询层。
1. 数据库层过滤
// 查询时自动添加用户ID条件
@Query("SELECT o FROM Order o WHERE o.id = ?1 AND o.userId = ?2")
Order findByIdAndUserId(Long id, Long userId);
2. 方法层判断
public Order getOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
if(order == null || !order.getUserId().equals(getCurrentUserId())){
throw new AccessDeniedException("无权访问该订单");
}
return order;
}
3. AOP+注解封装
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceOwner {
String idParam() default "id";
}
@Aspect
@Component
public class ResourceOwnerAspect {
@Around("@annotation(resourceOwner)")
public Object checkOwner(ProceedingJoinPoint joinPoint,
ResourceOwner resourceOwner) {
// 获取参数中的资源ID
Long resourceId = getResourceId(joinPoint, resourceOwner.idParam());
// 验证当前用户是否为资源所有者
if(!isOwner(resourceId)){
throw new AccessDeniedException("无权操作该资源");
}
return joinPoint.proceed();
}
}
4. 数据库行级安全(RLS)
PostgreSQL等数据库支持行级安全策略:
CREATE POLICY order_owner_policy ON orders
USING (user_id = current_user_id());
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
四、IDOR漏洞挖掘实战
以mall项目为例,分析其权限管理实现和漏洞挖掘过程。
1. 垂直权限分析
mall的Security模块中,DynamicSecurityFilter继承了AbstractSecurityInterceptor:
public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter {
@Override
public void doFilter(ServletRequest servletRequest,
ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
// 权限校验逻辑
}
}
核心权限决策流程:
AccessDecisionManager.decide()方法调用- 通过
DynamicSecurityMetadataSource.getAttributes()获取权限规则 - 规则通过
dynamicSecurityService.loadDataSource()加载 - 最终从数据库
ums_resource表读取URL-角色映射规则
结论:垂直权限控制完善,难以构造越权。
2. 水平权限漏洞挖掘
漏洞一:订单取消越权
接口:/order/cancelUserOrder
问题代码:
@PostMapping("/cancelUserOrder")
public CommonResult cancelUserOrder(@RequestParam Long orderId) {
return portalOrderService.cancelOrder(orderId);
}
// portalOrderService.cancelOrder实现
public CommonResult cancelOrder(Long orderId) {
// 直接操作订单,未校验订单归属
orderMapper.updateStatus(orderId, 4); // 4表示已取消
return CommonResult.success(null);
}
利用步骤:
- 注册新用户(无任何订单)
- 从数据库选择其他用户的订单ID(如状态为未支付的73号订单)
- 向接口发送取消请求:
POST /order/cancelUserOrder Content-Type: application/x-www-form-urlencoded orderId=73 - 数据库检查确认订单状态被修改
漏洞二:支付状态篡改
接口:/order/paySuccess
问题代码:
@PostMapping("/paySuccess")
public CommonResult paySuccess(@RequestParam Long orderId,
@RequestParam Integer payType) {
return portalOrderService.paySuccess(orderId, payType);
}
// portalOrderService.paySuccess实现
public CommonResult paySuccess(Long orderId, Integer payType) {
// 直接修改订单状态为已支付,无任何校验
orderMapper.updateStatus(orderId, 1); // 1表示待发货
return CommonResult.success(null);
}
严重性:
- 实现0元购:未支付订单直接改为已支付
- 平台攻击:枚举修改大量订单状态,导致交易系统瘫痪
利用步骤:
- 选择未支付订单(如73号)
- 发送支付成功请求:
POST /order/paySuccess Content-Type: application/x-www-form-urlencoded orderId=73&payType=1 - 数据库检查确认订单状态变为"待发货"
五、防御建议
-
垂直权限:
- 使用Spring Security或Shiro框架
- 采用RBAC模型管理角色和权限
- 最小权限原则分配权限
-
水平权限:
- 所有涉及资源ID的操作必须校验归属
- 使用统一拦截器或AOP处理资源权限
- 数据库查询自动添加用户ID条件
- 考虑行级安全(RLS)方案
-
代码审计重点:
- 检查所有接收资源ID的接口
- 验证Service层是否进行归属判断
- 确保数据库查询包含用户条件
- 特别注意状态修改类操作
-
测试建议:
- 使用不同权限账户测试相同接口
- 尝试操作非自己拥有的资源ID
- 自动化扫描工具结合人工验证
六、总结
- 垂直权限通常框架完善,风险较低
- 水平权限分散在业务代码中,是审计重点
- IDOR漏洞难以通过自动化工具发现,需要人工仔细审查
- 所有接收资源ID的操作必须进行归属校验
- 状态修改类接口需要特别关注权限控制
通过本案例可以看出,即使是在star数很高的开源项目中,水平权限控制也容易出现问题。开发者和安全审计人员都应给予足够重视。