本文目录导读:

针对PHP项目的垂直越权(Vertical Privilege Escalation)校验,核心思路是:在每个需要权限的操作中,先验证用户身份,再验证用户角色/权限等级是否满足该操作的授权要求。
以下是具体、可落地的校验方案:
核心原理:基于角色的权限控制(RBAC)
垂直越权是指低权限用户(如普通用户)尝试执行高权限操作(如管理员删除用户),校验的核心是比对“当前用户的角色”是否在“该操作允许的角色列表”中。
具体实现方案(从简单到完善)
中间件/过滤器模式(推荐)
在控制器的入口处(路由分发前)统一校验,避免在每个函数里重复写判断。
<?php
// 假设你已经从 session/token 中拿到了当前登录用户的信息
class AuthMiddleware {
// 假设用户角色:1=普通用户, 2=编辑, 3=管理员
public static function requireRole(int $requiredLevel): void {
// 1. 校验身份是否存在(防止未登录)
$user = $_SESSION['user'] ?? null;
if (!$user) {
http_response_code(401);
echo json_encode(['error' => '未登录']);
exit;
}
// 2. 校验角色等级(核心:垂直权限检查)
if ((int)$user['role_level'] < $requiredLevel) {
http_response_code(403);
echo json_encode(['error' => '权限不足,需要更高等级的角色']);
exit;
}
// 通过,继续执行后续逻辑
}
}
// 在路由中使用
Route::group(['prefix' => 'admin'], function () {
Route::get('/users', function () {
AuthMiddleware::requireRole(3); // 只有管理员(3)才能访问
// ... 业务逻辑
});
Route::post('/delete-user/{id}', function ($id) {
AuthMiddleware::requireRole(3);
// ... 删除逻辑
});
Route::get('/dashboard', function () {
AuthMiddleware::requireRole(2); // 编辑(2)及以上可以看仪表盘
// ...
});
});
基于注解/属性的声明式校验(适合框架)
使用 PHP 8 的 Attribute(注解)或框架的 @Middleware 注解,将权限声明和业务逻辑分离。
<?php
#[Attribute(Attribute::TARGET_METHOD)]
class RequiresRole {
public function __construct(public int $minLevel) {}
}
class AdminController {
#[RequiresRole(minLevel: 3)]
public function deleteUser(int $id): void {
// 业务逻辑
}
#[RequiresRole(minLevel: 2)]
public function viewReports(): void {
// 业务逻辑
}
}
// 在路由分发时,自动读取方法上的 RequiresRole 属性并检查
function dispatch($controller, $method, $params) {
$reflection = new ReflectionMethod($controller, $method);
$attributes = $reflection->getAttributes(RequiresRole::class);
if (!empty($attributes)) {
$roleAttr = $attributes[0]->newInstance();
// 调用中间件校验
AuthMiddleware::requireRole($roleAttr->minLevel);
}
// 调用原方法
$controller->$method(...$params);
}
关键细节与陷阱
1 数据库层面的角色设计(不要只用个数字)
-- 推荐:角色表与用户表分离
CREATE TABLE roles (
id INT PRIMARY KEY,
name VARCHAR(50),
level INT NOT NULL UNIQUE -- 等级:数字越大权限越高
);
CREATE TABLE users (
id INT PRIMARY KEY,
username VARCHAR(100),
role_id INT,
FOREIGN KEY (role_id) REFERENCES roles(id)
);
-- 不要只存一个数字 'role = 3',原因:系统扩展后角色可能不连续,数字含义不直观
2 不要把“是否管理员”的判断硬编码在 SQL 里
// 错误示例(SQL 层直接判断,容易被绕过或误解):
$sql = "SELECT * FROM orders WHERE user_id = ? OR role = 'admin'";
// 这样如果 role 值错误,可能让普通用户看到所有订单
// 正确做法:
// 先由 PHP 逻辑判断用户角色,再决定 SQL 查询范围
$currentUserRole = $_SESSION['user']['role_level'];
if ($currentUserRole >= 3) {
// 管理员:查全部
$sql = "SELECT * FROM orders";
} else {
// 普通用户:只查自己的
$sql = "SELECT * FROM orders WHERE user_id = ?";
}
3 垂直越权的常见遗漏点
| 场景 | 错误做法 | 正确做法 |
|---|---|---|
| API 端点未校验 | /api/admin/deleteUser 没加中间件 |
每个管理员接口都要强制角色检查 |
| 前端隐藏按钮 | 只在 UI 上隐藏了删除按钮,后端没判断 | 后端必须独立校验,不能信任前端传的角色信息 |
| 通过 ID 越权 | 管理员接口接收 user_id 参数,未检查当前用户是否有权操作该 ID |
在修改/删除前,必须验证 user_id 是否属于当前用户或当前用户角色允许操作 |
| 缓存/序列化问题 | 从 session 取出的 role_level 可能被篡改或过期 |
定期刷新 session 信息,每次请求从可靠来源(DB/Redis)验证用户状态 |
4 防范“代理调用”越权
如果系统有“用户 A 可以以用户 B 的身份操作”的功能(如客服代下单),必须单独标记这种操作并限制范围。
// 安全的代理模式
class ImpersonateMiddleware {
public static function check(int $targetUserId): void {
$currentUser = $_SESSION['user'];
// 只有管理员可以模拟其他用户
if ($currentUser['role_level'] < 3) {
http_response_code(403);
exit('无权模拟他人');
}
// 管理员不能模拟超级管理员(防止低管爬高)
$targetRole = UserModel::getRoleById($targetUserId);
if ($targetRole['level'] >= $currentUser['role_level']) {
http_response_code(403);
exit('不能模拟同级或更高级用户');
}
}
}
终极方案:结合“最小权限原则” + “访问控制矩阵”
如果你需要更精确的控制(不只是按等级,而是按具体操作),建议实现一个访问控制矩阵(ACL):
// 定义权限
$permissions = [
'delete_user' => ['admin'],
'edit_article' => ['admin', 'editor'],
'view_article' => ['admin', 'editor', 'user'],
];
// 校验函数
function checkPermission(string $action, string $userRole): bool {
global $permissions;
if (!isset($permissions[$action])) {
return false; // 未知操作,默认不允许
}
return in_array($userRole, $permissions[$action]);
}
// 使用
if (!checkPermission('delete_user', $currentUser['role_name'])) {
die('无权限');
}
校验流程
- 身份认证:判断用户是否登录(Session/Token)。
- 角色识别:从可信存储(Session/DB/缓存)获取当前用户的角色和等级。
- 权限比对:用当前角色等级 >= 操作要求的最低等级(或角色名在允许列表中)。
- 结果处理:不通过则返回 403 + 日志记录(用于审计)。
- 防御加固:后端所有入口统一使用中间件,禁止在任何地方信任前端传入的角色信息。
做到这5步,可以在大部分场景下有效防止垂直越权漏洞。