越权漏洞:水平与垂直
OWASP Top 10 里,失效的访问控制(Broken Access Control)多年位居前列。它不像注入那样有技术门槛,纯粹是「忘了判断」,因此出现得最多。
两类越权
水平越权:同级用户之间互相访问。A 把请求里的 id=100 改成 id=101,看到了 B 的订单。
// 危险:只验证了登录,没验证归属
$order = $db->query('SELECT * FROM orders WHERE id = ?', [$id]);
// 正确:归属条件进 SQL
$order = $db->query('SELECT * FROM orders WHERE id = ? AND user_id = ?', [$id, $uid]);
垂直越权:低权限用户访问高权限功能。普通用户直接请求 /admin/users/delete。常见于前端隐藏了按钮,但接口没有校验——前端的权限控制只是 UI,不是防线。
测试方法
水平越权测试用两个账号 A/B:A 登录后,把请求里的 ID 全部替换成 B 的资源 ID,逐个接口验证是否 403。垂直越权测试把 A 的 Cookie 打向所有管理接口,看返回 401/403 还是 200。
这两个测试脚本化之后可以跑在回归里,比人工抽查可靠得多。
常见疏漏点
- 接口遗漏:页面有权限控制,但导出、详情、状态更新等辅助接口忘了加;
- GraphQL / 批量接口:一层 query 里嵌套的资源没有逐层校验;
- IDOR 变体:不用自增 ID 而用 UUID 只是提高门槛,归属校验依然不可省;
- 前端路由守卫:SPA 里「跳转到 /admin」的拦截毫无安全性,后端必须同样拦截;
- 默认拒绝:新接口上线时没配权限就默认公开。应该反过来——未声明权限的接口默认拒绝。
设计层面的解法
把权限判断收敛到统一入口,而不是散在各接口里:
- 中间件/装饰器声明式标注:
@require('order.view'); - 数据访问层统一注入归属条件(数据范围机制);
- 高层测试用「权限矩阵」:角色 × 接口 的期望结果表,回归时全量断言。
一条纪律:每次新建写接口,先问三个问题——谁可以调?能操作谁的数据?操作结果可逆吗? 答不清就不要上线。