水平越权风险如何规避

wen 开源项目 24

本文目录导读:

水平越权风险如何规避

  1. 架构与设计层面:引入“用户上下文数据隔离”
  2. 接口与参数层面:严格校验“资源所有者”
  3. 数据访问层(DAL)与ORM设计:规则内嵌
  4. 测试与监控层面:主动发现与取证
  5. 特别场景处理:非CRUD操作
  6. 总结避坑清单

水平越权(Horizontal Privilege Escalation)是指攻击者通过某些手段,访问或操作了与自己同权限级别不属于自己的资源(用户A查看用户B的订单),其核心风险在于资源归属验证失效

规避水平越权风险,需要从架构设计、接口设计、数据访问层和监控审计四个层面构建纵深防御。

以下是具体的规避策略和最佳实践:

架构与设计层面:引入“用户上下文数据隔离”

这是最根本的解决方法,让系统“天然”知道当前用户是谁,而不依赖用户传入的参数来指定。

  • 使用认证令牌(Token/Session)中的用户ID:

    • 错误做法: 接口参数中传入 user_idaccount_id,后端直接使用该参数查询。GET /api/orders?user_id=123
    • 正确做法: 从JWT(JSON Web Token)、Session或OAuth(开放授权)令牌中解析出当前已认证用户的ID。GET /api/orders -> 后端自动获取 token 中的用户ID(如123)。
    • 极端场景: 如果确实需要管理员替用户查看,应通过专门的“模拟”或“代查”接口,并记录审计日志。
  • 资源ID不应是连续且可预测的整数:

    • 避免使用自增ID(如 order_id=1001, 1002),改用UUID(通用唯一识别码)或随机字符串(如 ord_8f3a2b1c...)。
    • 虽然不能完全阻止恶意用户遍历,但大大提高了“猜中”合法资源的难度。

接口与参数层面:严格校验“资源所有者”

所有涉及资源操作的接口(查询、修改、删除),都必须校验“当前用户是否有权访问该资源”。

  • 后端强制校验(核心):

    • 不要信任前端传来的任何资源归属ID。
    • 通用校验逻辑: SELECT * FROM orders WHERE order_id = ? AND user_id = ?
      • 这里的 order_id 来自URL参数(如 GET /api/orders/123),user_id 来自当前用户的认证令牌。
      • 如果数据库查不到记录,或 user_id 不匹配,直接返回 403 Forbidden404 Not Found
    • 示例代码逻辑(伪代码):
      def get_order(request, order_id):
          current_user_id = request.token.user_id  # 从令牌获取
          order = database.query("SELECT * FROM orders WHERE id = ? AND user_id = ?", [order_id, current_user_id])
          if not order:
              return Response(status=403, message="无权限访问该订单")
          return order
  • 避免在URL中暴露过多上下文:

    • 避免 GET /api/user/123/orders/456 这种结构(123 不是当前用户)。
    • 尽量使用相对路径或基于当前用户的路径:GET /api/me/orders/456
  • 批量操作接口需谨慎:

    • 对于批量查询接口(如 POST /api/orders/batch,传入ID列表),必须循环校验每个ID是否属于当前用户,或者使用数据库的 WHERE user_id = ? AND id IN (?) 进行过滤。

数据访问层(DAL)与ORM设计:规则内嵌

将权限校验逻辑下沉到数据访问层,避免业务逻辑中重复编写。

  • 实现“资源过滤器”(Filter/Scope):

    • 定义基础数据访问对象,自动为所有查询添加用户ID过滤条件。
    • 示例(基于ORM的Scope): 在模型定义中,所有查询默认加上 where(user_id: current_user_id),业务代码只需写 Order.find_by(id: params[:id]),底层会自动附加 user_id
    • 优点: 即使业务开发者忘记校验,底层依然有保护,这是成本最低、最可靠的规避方法。
  • 使用“数据租户”(Data Tenant)模式:

    • 在数据库表中显式设计 owner_idtenant_id 字段,并建立索引。
    • 所有SQL查询(尤其是 UPDATEDELETE)都必须包含该字段作为条件,防止误操作。

测试与监控层面:主动发现与取证

  • 自动化安全测试(渗透测试自动化):

    • 编写自动化测试用例,替换请求中的 user_idorder_id 等参数为其他用户的ID,断言期望返回 403401,而不是 200 和数据。
    • 这类测试应集成到CI/CD(持续集成/持续部署)流水线中。
  • 日志与实时监控:

    • 记录所有对敏感资源的访问(谁、在什么时间、访问了什么资源ID、结果如何)。
    • 异常检测: 监控短时间内单个用户访问了大量不同资源ID的行为(一个普通用户1分钟内请求了1000个不同的 order_id),触发告警。
  • 接口返回结果模糊化:

    • 当用户越权访问时,不要返回明确的消息,如“订单号123不属于你”,这会给攻击者提供线索。
    • 最佳实践:无论资源是否存在,只要权限不匹配,统一返回 404 Not Found(资源不存在)。 这会让攻击者无法区分是ID不存在还是无权访问。

特别场景处理:非CRUD操作

  • 文件下载/上传:

    • 文件名或路径参数绝不能直接拼接,应将文件URL映射到数据库中的记录,然后检查该记录的所有者。
    • GET /files?file_id=uuid -> 后端查表,校验 file.user_id == current_user_id,然后返回文件内容(而不是直接暴露文件服务器路径)。
  • WebSocket / 实时推送:

    • 连接时验证令牌。
    • 主动推送数据时,确保只推送属于该用户的数据(通常通过房间/通道隔离)。

总结避坑清单

场景 高风险做法(不要这样) 低风险做法(推荐)
查询订单 GET /api/orders?user_id={其他用户ID} GET /api/me/orders 或从Token取ID
获取详情 SELECT * FROM orders WHERE id = $id SELECT * FROM orders WHERE id = $id AND user_id = $current_user_id
批量操作 传入ID数组,直接循环执行 数据库 WHERE id IN (ids) AND user_id = current_user_id
权限提示 返回“你不属于张三” 统一返回 404 Not Found
数据模型 自增ID UUID或随机字符串
开发习惯 业务代码里手动检查 数据访问层默认加 user_id 过滤器

核心原则:永远不要信任客户端的输入(尤其是资源归属ID),始终在服务端基于当前会话的用户身份进行二次校验。

抱歉,评论功能暂时关闭!