Introduction

在网络安全这个令人激动的世界里,黑客和渗透测试人员四处寻找漏洞,一个不断发展的概念被称为原型污染。这允许不良行为者操纵和利用 JavaScript 应用程序的内部工作原理,并使攻击者能够访问敏感数据和应用程序后端。

虽然原型污染最常在 JavaScript 环境中讨论,但该概念可以应用于使用类似基于原型的继承模型的任何系统。

然而,JavaScript 的广泛使用,特别是在 Web 开发中,以及其灵活而动态的对象模型,使原型污染成为这种语言中更突出和更相关的问题。相比之下,基于类的继承语言(如 Java 或 C++)具有不同的继承模型,其中类(对象的蓝图)通常是静态的,并且在运行时更改类以影响其所有实例并不是常见的做法或简单的任务。

学习目标
通过本课程,您将全面了解以下关键概念:

原型污染的工作原理
对 Web 应用程序的潜在风险
利用技术(客户端和服务器端)
缓解技术

学习前提条件
建议在开始本课程之前了解以下主题:

网站的工作方式
HTTP 协议和方法
OWASP 十大 Web 漏洞

Essential Recap

在我们深入讨论原型污染等高级内容之前,让我们先了解 JavaScript 中的一些基本知识。将对象视为保存信息的构建块。继承就像将特征从一个对象传递到另一个对象。函数就像可以单独使用或作为这些对象的一部分使用的工具。最后,JavaScript 中的就像蓝图,可以帮助我们轻松制作类似的东西。一旦我们掌握了这些基础知识,我们就可以在我们的房间里探索更复杂的原型污染主题。

对象

在 JavaScript 中,对象就像可以容纳不同信息的容器。将社交网络个人资料想象成一个对象,其中每个个人资料都具有姓名、年龄和关注者等属性。您可以使用花括号和键值对来表示它:

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

此处的“用户”是一个具有“名称”、“年龄”和“关注者”等属性的对象。这些属性存储有关用户的特定信息。JavaScript 中的对象使我们能够组织和管理相关数据,使其成为构建动态和交互式应用程序的基本概念。

要实际测试它,请在附加的 VM 中打开“Chrome”,打开浏览器后,右键单击并选择“检查”,然后选择“控制台”,这将打开以下窗口。请复制上述代码并将其粘贴到控制台中;它将为您创建一个用户对象。现在,您可以使用“console.log”访问对象的属性,如下所示:

image for console log

在 JavaScript 中,类就像蓝图一样,有助于创建具有相似结构和行为的多个对象。继续我们的社交网络示例,我们可以使用类来定义一般用户和内容创建者。类提供了一种方便的方式来组织和实例化具有共同特征的对象。

           // Class for User 
class UserProfile {
constructor(name, age, followers, dob) {
this.name = name;
this.age = age;
this.followers = followers;
this.dob = dob; // Adding Date of Birth
}
}

// Class for Content Creator Profile inheriting from User
class ContentCreatorProfile extends User {
constructor(name, age, followers, dob, content, posts) {
super(name, age, followers, dob);
this.content = content;
this.posts = posts;
}
}

// Creating instances of the classes
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);

现在,User 类将出生日期 (dob) 作为其属性的一部分,而 ContentCreatorProfile 类继承了此属性。在创建这些类的实例时,我们可以提供出生日期和其他详细信息。如我们所见,包括出生日期可以为用户资料提供更多信息。

原型

在 JavaScript 中,每个对象都链接到一个原型对象,这些原型形成一个通常称为原型链的链。原型充当对象的模板或蓝图。当您使用构造函数或类创建对象时,JavaScript 会自动在对象与其原型之间建立链接。在我们的社交网络示例的背景下,让我们说明原型的工作原理:

           // Prototype for User 
let userPrototype = {
greet: function() {
return `Hello, ${this.name}!`;
}
};

// User Constructor Function
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;
}

// Creating an instance
let regularUser = UserProfilePrototype('Ben S', 25, 1000, '1/1/1990');

// Using the prototype method
console.log(regularUser.greet());

您可以复制并试用附加 VM 中的代码,以了解 JavaScript 对象的工作原理。

类和原型之间的区别

JS 中的类和原型是实现类似目标的两种方式:创建具有行为和特征的对象。想象一下,您正在房间里建造汽车模型。使用类就像为要开发的每种汽车模型准备一份详细的蓝图或一套说明。您严格按照蓝图来制造每辆汽车,并且所有根据精确蓝图制造的汽车都保证具有相同的功能和行为。JavaScript 中的类的工作原理类似;它们提供了一种清晰、结构化的方式来创建共享相同属性和方法的对象,使它们易于理解和使用。

另一方面,原型就像拥有一个基本的汽车模型,然后通过直接在汽车本身上添加或修改功能来对其进行自定义。使用原型,您从一个简单的对象开始,然后通过将其链接到已经具有这些行为的原型对象来为其添加行为。以这种方式创建的对象通过原型链链接,允许它们从其他对象继承行为。这种方法更具动态性和灵活性,但与类的结构化方法相比,管理和理解起来可能更困难。

继承

在 JavaScript 中,继承允许一个对象从另一个对象继承属性,从而创建相关对象的层次结构。继续我们的社交网络示例,让我们考虑内容创建者的更具体的个人资料。这个新对象可以从一般用户个人资料中继承属性,如“名称”和“关注者”,并添加特定属性,如“内容”和“帖子”。

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

// Content Creator Profile inheriting from User
let contentCreatorProfile = Object.create(user);
contentCreatorProfile.content = 'Engaging Content';
contentCreatorProfile.posts = 50;

这里,contentCreatorProfile 使用 Object.create() 从用户那里继承属性。现在,它具有 contentposts 等特定属性,并从一般用户资料中继承了 nameagefollowers,如下所示。

image for console log

这样,继承有助于创建更专业的对象,同时重用父对象的公共属性。JavaScript 支持类和基于原型的继承。

  • 基于原型的继承:在 JavaScript 中,每个对象都有一个原型,当您创建新对象时,您可以指定其原型。对象从其原型继承属性和方法。您可以使用 Object.create() 方法创建具有指定原型的新对象,也可以使用现有对象的原型属性直接修改其原型。
  • 基于类的继承:JavaScript 还支持类,它为定义对象和继承提供了更熟悉的语法。JavaScript 中的类只是 JavaScript 现有基于原型的继承的语法糖。在底层,类仍然使用原型。

image for prototype of userprofile

右图表示 JS 中基于原型的继承:

  • 定义 UserProfile 对象:我们首先定义一个通用的 UserProfile 对象,该对象表示不同类型的配置文件共享的通用属性。在此示例中,UserProfile 包括电子邮件和密码等属性,这些属性可能对所有用户配置文件都通用。
  • 创建 ContentCreatorProfile:我们创建一个名为 ContentCreatorProfile 的专用配置文件。此配置文件特定于内容创建者,可能具有通用用户配置文件中没有的其他属性或行为。我们通过使用 Object.create(UserProfile) 创建 ContentCreatorProfile 来实现这一点,它将 UserProfile 设置为 ContentCreatorProfile 的原型。
  • 添加其他属性:创建 ContentCreatorProfile 后,我们添加特定属性,例如帖子。此属性是 ContentCreatorProfile 独有的,不从 UserProfile 继承。
  • 访问属性:访问“ContentCreatorProfile”的属性时,JavaScript 首先检查该属性是否直接存在于“ContentCreatorProfile”上。如果未找到该属性,则查找原型链并检查“UserProfile”上是否存在该属性。如果找到,则返回原型链中的值。因此,“ContentCreatorProfile”从“UserProfile”继承了属性 email 和 password,同时还具有自己独特的属性 number of posts。这允许使用分层结构,其中专门的配置文件可以从通用配置文件继承通用属性,同时添加其特定属性。

image for summary of classes and inheritance

让我们用上图总结一下到目前为止所讨论的内容。原型的概念在实现继承中起着至关重要的作用。JavaScript 中的每个对象都有一个原型,它是对象属性和方法的蓝图。在上图中,当我们定义一个像“UserProfile”这样的类时,它的原型将成为从它创建的所有实例的原型。这意味着“UserProfile”类中定义的属性和方法可供“UserProfile”的所有实例访问。此外,JavaScript 允许我们动态扩展这些原型,通过原型链实现继承。例如,“ContentCreator”、“ContentDesigner”和“Moderator”等子类可以扩展“UserProfile”类的原型以继承其属性和方法。通过利用原型,JavaScript 提供了一种灵活而有效的机制来实现继承,从而实现了面向对象编程范式中的代码重用和可维护性。

How it Works

现在我们对 JavaScript 有了基本的了解,让我们使用相同的社交网络示例深入研究原型污染。

原型污染是一种漏洞,当攻击者操纵对象的原型时,就会影响该对象的所有实例。在 JavaScript 中,原型有助于继承,攻击者可以利用这一点来修改共享属性或在对象之间注入恶意行为。

原型污染本身可能并不总是直接构成可利用的威胁。然而,当它与其他类型的漏洞(如 XSS 和 CSRF)结合在一起时,其真正的危害潜力就会变得尤为明显。

一个常见的例子

假设我们有一个带有“introduce”方法的“Person”基本原型。攻击者旨在通过改变原型来操纵所有实例中“introduce”方法的行为。

// Base Prototype for Persons
let personPrototype = {
introduce: function() {
return `Hi, I'm ${this.name}.`;
}
};

// Person Constructor Function
function Person(name) {
let person = Object.create(personPrototype);
person.name = name;
return person;
}

// Creating an instance
let ben = Person('Ben');

请复制上述代码,粘贴到控制台并按 Enter。当我们创建一个新对象“ben”并调用“introduce”方法时,它会显示“Hi, I’m Ben”,如下图所示。

image of console

如果攻击者使用 __proto__ 属性将恶意内容注入所有实例的 introduce 方法中,结果会怎样?在 JavaScript 中,__proto__ 属性是访问对象原型的常用方法,本质上指向继承属性和方法的对象。让我们看看,攻击者以某种方式使用任何攻击媒介(如 XSS、CSRF 等)执行以下代码。

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

我们将详细讨论后台到底发生了什么:

  • 原型定义:Person 原型 (personPrototype) 最初使用无害的 introduce 方法定义,介绍该人。
  • 对象实例化:使用名称 'Ben' (let ben = Person('Ben');) 创建 Person 实例。
  • 原型污染攻击:攻击者将恶意负载注入原型的 introduce 方法,更改其行为以显示有害消息。
  • 对现有实例的影响:结果,即使现有实例 (ben) 也会受到影响,并且调用 ben.introduce() 现在会输出攻击者注入的消息。

此示例显示了攻击者如何改变跨对象共享方法的行为,从而可能造成安全风险。防止原型污染包括仔细验证输入数据并避免直接使用不受信任的内容修改原型。

Exploitation - XSS

标准方法

众所周知,JavaScript 中的 Object 原型本身就具有许多属性。其中,constructor__proto__ 属性是威胁行为者特别值得注意的目标。constructor 属性指向构造对象原型的函数,而 __proto__ 是对当前对象直接继承的原型对象的引用。恶意行为者经常利用这些属性来操纵对象的原型链,从而可能导致原型污染。

黄金法则

该概念取决于攻击者影响某些关键参数的能力,例如 xval,表达式类似于 Person[x][y] = val。假设攻击者将 __proto__ 分配给 x。在这种情况下,由 y 标识的属性在与具有 val 表示的值的对象共享同一类的所有对象中都是通用的。

在更复杂的场景中,当攻击者控制结构(如“Person[x][y][z] = val”)中的“x”、“y”和“val”时,将“x”指定为“构造函数”,将“y”指定为“原型”,会导致在应用程序中所有具有指定“val”的对象中建立由“z”定义的新属性。后一种方法需要更复杂的对象属性排列,因此在实践中不太常见。

几个重要函数

在识别潜在的原型污染漏洞时,渗透测试人员应关注易受原型污染影响的常用向量/函数。彻底检查应用程序如何处理对象操作至关重要。我们将了解攻击者可以利用的一些重要函数,然后我们将实际执行利用。

  • 按路径定义属性:如果路径组件由用户输入控制,则根据给定路径设置对象属性的函数(如“object[a][b][c] = value”)可能很危险。应检查这些函数以确保它们不会无意中修改对象的原型。考虑一个允许用户更新任何好友评论的端点。

初始对象结构

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

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

从用户收到的输入

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

攻击者更新路径以瞄准原型:

{ "path": "reviews[0].content", "value": "<script>alert('anycontent')</script>" };

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

结果对象结构

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

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
}
];

实例

现在,我们将实际了解攻击者如何利用该漏洞。假设您在一家公司担任渗透测试员,负责渗透测试社交媒体应用程序。您可以使用以下登录凭据通过 10.10.144.78:5000 URL 访问该应用程序:

  • 用户名:bob
  • 密码:bob@123

登录后,您将看到如下仪表板:

image of the dashboard

为了识别原型污染,我们将探索社交媒体应用程序的不同功能,例如添加相册、发送好友请求、添加评论等。登录后,其他朋友可以添加到您的朋友列表中。访问任何朋友的个人资料(在本例中假设为 Sabalenka)。访问页面后,您将看到不同的选项,例如添加评论、克隆相册等。让我们探索提交评论功能。

submit review feature

提交评论功能允许用户提交任何评论,这些评论将保存在数据库中。让我们探索客户端和服务器端代码,以分析各种利用可能性。

<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>

客户端代码将评论作为输入参数,并调用 API 端点 /submit-friend-review 来添加评论以及隐藏参数 friendId。如前面的任务所述,单独的原型污染很少能被利用;然而,一旦与 XSS 等其他攻击媒介结合,它可以提供更好的攻击面。现在,让我们来看看服务器端代码。

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, payload.value);
} catch (e) { }
res.redirect(`/friend/${friendId}`);
});

此代码正在执行以下操作:

  • 首先,验证已登录用户的会话;如果没有,则将其重定向到登录页面。
  • 然后,代码提取参数并验证已登录用户与请求参数中收到的“friendId”之间的连接。
  • 验证好友的连接后,代码将评论插入好友的对象中。
  • 代码使用 Lodash 实用程序中的“_.set”函数,该函数允许您在对象的给定路径处设置属性的值。这是一种在对象上深度设置值的便捷方法,而无需检查路径的每个级别是否存在。如果路径的某个部分不存在,则“_set”将创建它。
  • 代码分析表明,这是执行按路径定义属性攻击的绝佳机会。
  • 接下来,让我们添加一个朋友并访问他的个人资料以添加一个简单的评论,看看它是否有效。

image after successfully adding a review

评论已成功添加。根据我们从源代码审查中获得的知识,让我们准备一个有效负载来发送触发警报的评论。我们知道朋友的对象有一个“评论”数组,其中存储了每个用户的评论。添加评论的功能仅适用于朋友的个人资料。

{"path": "reviews[0].content", "value": "<script>alert('Hacked')</script>"}

XSS image after prototype pollution

现在,每当有人访问此用户的个人资料时,就会触发 XSS 攻击。在这种情况下,攻击者甚至可以操纵其他可能允许管理员访问用户的属性,例如 isAdminisloggedIn 等。

在此任务中,我们了解了攻击者如何使用按定义属性技术发起原型污染和 XSS 攻击。在下一个任务中,我们将继续学习更多功能。

Exploitation - Property Injection

几个重要函数

  • 对象递归合并:此函数涉及将源对象的属性递归合并到目标对象中。如果合并函数未验证其输入并允许将属性合并到原型链中,攻击者可以利用此功能。考虑相同的社交网络示例,让我们假设以下代码。假设应用程序具有合并用户设置的功能:
// Vulnerable recursive merge function
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];
}
}
}

// Endpoint to update user settings
app.post('/updateSettings', (req, res) => {
const userSettings = req.body; // User-controlled input
recursiveMerge(globalUserSettings, userSettings);
res.send('Settings updated!');
});

攻击者发送一个包含“__proto__”的嵌套对象的请求:

{ "__proto__": { "newProperty": "value" } } 
  • 对象克隆:对象克隆是一种类似的功能,允许深度克隆操作无意中将属性从原型链复制到另一个原型链。测试应确保这些函数仅克隆对象的用户定义属性并过滤特殊关键字,如__proto__、构造函数等。一个可能的用例是应用程序后端克隆对象以创建新的用户配置文件:

实际示例

现在,我们将实际了解攻击者如何利用此漏洞。让我们探索克隆相册功能。克隆相册允许用户通过提供新名称来克隆相册。

Clone album feature

让我们探索客户端和服务器端代码来探索各种利用可能性。

<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>

客户端代码将名称作为输入,并调用 API 端点 /clone-album/{album_ID} 来克隆相册。如前面的任务所述,单独的 Prototype 污染很少能被利用;然而,一旦与 XSS 等其他攻击媒介结合,它可以提供更好的攻击面。现在,让我们来看看服务器端代码。

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;
}

在上面的代码中,服务器收到一个包含专辑名称的 JSON 对象,将需要复制的专辑复制到另一个对象中,并通过调用合并函数更改新创建的副本的名称。

我们知道,如果合并函数盲目复制所有对象和属性而不根据键进行清理,那么它就是原型污染的理想候选者。我们可以看到,开发人员编写的合并函数缺少任何此类清理过滤器。如果我们发送一个包含 __proto__ 的请求,其中包含 newProperty 和值,如下所示:

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

合并函数将把 __proto__ 视为属性,并调用 obj.__proto__.newProperty=value。通过这样做,newProperty 不会直接添加到朋友对象中。相反,它会被添加到朋友对象的原型中。这意味着 newProperty 不是朋友属性(如姓名、年龄等)的可见部分,但仍然可以访问。让我们通过访问 Josh 的个人资料并使用上述有效负载作为相册名称来克隆相册。

image after clonning an album

开始吧!我们为所有友元对象添加了一个新属性。

  • 对同一类型的所有对象的影响

:由于所有友元对象共享相同的原型(它们由相同的模板或构造函数创建),因此添加

newProperty

添加到原型意味着所有朋友对象现在都可以访问 newProperty。这就像向模板添加新功能一样;

现在,从该模板创建的每个对象都具有此新功能。

-观察变化:即使在打印朋友对象时无法直接看到 newProperty,但它仍然存在。您可以通过调用 friend.newProperty 来访问它,它将显示“testValue”。
-newProperty 如何变得可见:当您通过原型添加 newProperty 时,它并不直接存在于单个对象(如每个朋友)上,而是存在于它们的原型上。但是,当您访问对象的属性时,JavaScript 首先在对象本身上查找该属性。如果未找到,JavaScript 会查找原型链,直到找到它(或到达链的末尾)。

  • 在屏幕上渲染:在 EJS 模板中,当您使用 for...in loop (<% for (let key in friend) { %> ... <% } %>) 循环遍历朋友对象的属性并显示它们时,此循环将遍历朋友对象的所有可枚举属性,包括从原型继承的属性。因此,即使 newProperty 不是直接在朋友对象上,而是在其原型上,它仍会显示在此循环中并呈现在屏幕上。

在此任务中,我们学习了如何在对象的原型中添加新属性来污染整体结构。在下一个任务中,我们将了解如果开发人员不采取必要措施,如何使整个应用程序崩溃。

注意:要回答以下问题,请访问 URL http://10.10.168.64:8080/getFlag.php 以获取标志。您将看到以下仪表板以获取标志:

dashboard for getting flags

Exploitation - Denial of Service

拒绝服务

原型污染是 JavaScript 应用程序中的一个严重漏洞,它可能导致拒绝服务 (DoS) 攻击以及其他严重后果。当攻击者操纵广泛使用的对象的原型时,就会发生这种情况,导致应用程序出现意外行为或完全崩溃。在 JavaScript 中,对象从其原型继承属性和方法,更改此原型会影响共享它的所有对象。

例如,如果攻击者污染了 Object.prototype.toString 方法,则任何对象对此方法的后续调用都会执行更改后的行为。在经常使用 toString 的复杂应用程序中,这可能会导致意外结果,从而可能导致应用程序出现故障。toString 方法在 JavaScript 中被广泛使用。它在许多情况下都会自动调用,尤其是在需要将对象转换为字符串时。

如果污染的方法导致处理效率低下或无限循环,则可能会耗尽系统资源,从而导致 DoS 情况。此外,原型污染还会干扰应用程序的业务逻辑。更改基本方法或属性可能会触发未处理的异常或错误,从而导致进程或服务终止。这可能会导致 Web 应用程序中的服务器无响应,从而拒绝向合法用户提供服务。

实际示例

  • 再次访问 URL http://10.10.168.64:5000,看看我们是否可以通过任何输入使服务器崩溃。如前所述,我们有一个方法 Object.prototype.toString,可将对象转换为 String 数据类型。
  • 访问任何朋友的个人资料页面;我们可以从上一个任务中看到服务器端代码的样子
<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>
  • 我们看到此函数正在调用合并函数,该函数合并两个对象。如果我们尝试发送一个有效负载,该负载将覆盖现有函数(如“toString()”),然后如果我们在某个对象上调用它,它会导致服务器出现异常行为,该怎么办?
  • 要准备有效负载,让我们采用一个简单的 JSON 代码,它将覆盖 toString 函数,如下所示:
{"__proto__": {"toString": "Just crash the server"}}
  • 进入个人资料页面,输入 payload 代替新专辑名称,如下所示:

clone album image

  • 一旦 app.js 收到请求,解析 JSON,并在 friend 对象的 __proto__ 属性中分配 toString 函数值,我们就解码有效负载。
  • 这会产生异常行为,因为 toString 在不同对象中被广泛使用。当我们单击“克隆相册”时,应用程序崩溃,如下所示:

error screenshot after overriding the string

  • 我们得到的 TypeErrorObject.prototype.toString.call 不是一个函数,因为我们已经使用 Prototype 污染覆盖了该函数。
  • 您可以覆盖其他几个内置对象/函数,如 toJSONvalueOfconstructor 等,但应用程序不会在所有行为中崩溃。这完全取决于您要覆盖的函数。

注意:要再次启动服务器,请访问 URL http://10.10.168.64:8080 来启动服务器。

Automating the Process

识别过程中的主要问题

识别原型污染在任何语言中都是一个棘手的问题,尤其是在 JavaScript 中,因为 JavaScript 允许一个

对象与另一个对象共享其功能。使用软件工具自动检测此问题非常困难,因为它不像其他常见的网站安全问题那样简单。每个网站或网络应用程序都是不同的,要找出原型污染可能发生的位置,需要有人仔细查看网站的代码,了解其工作原理,并查看可能出现错误的位置。

与其他可以通过寻找特定模式或迹象发现的安全问题不同,发现原型污染需要渗透测试人员/开发人员深入研究网站的代码。这一切都是为了了解 JavaScript 中对象相互影响的复杂方式,并发现可能出错的地方。安全工具可以帮助指出可能的问题,但它们无法捕捉到所有问题。这就是为什么拥有知道如何仔细阅读和分析代码的人如此重要的原因。

一些重要的脚本

安全和开源社区已经开发了几种工具和项目来帮助自动查找原型污染漏洞。以下是一些著名的 GitHub 存储库,它们提供了用于检测原型污染漏洞的工具、库或见解:

  • NodeJsScan 是 Node.js 应用程序的静态安全代码扫描器。它包括对各种安全漏洞的检查,包括原型污染。将 NodeJsScan 集成到您的开发工作流程中可以帮助自动识别代码库中的潜在安全问题。
  • Prototype Pollution Scanner 是一种旨在扫描 JavaScript 代码以查找原型污染漏洞的工具。它可用于分析代码库中易受污染的模式,帮助开发人员识别和解决其应用程序中的潜在安全问题。
  • PPFuzz 是另一个模糊器,旨在自动检测 Web 应用程序中的原型污染漏洞。通过对可能与对象属性交互的输入向量进行模糊测试,PPFuzz 可以帮助识别应用程序中易受原型污染影响的点。
  • BlackFan 的客户端检测专注于识别客户端 JavaScript 中的原型污染漏洞。它包括如何在浏览器中利用原型污染进行 XSS 攻击和其他恶意活动的示例。它是了解原型污染对客户端影响的宝贵资源。

在识别原型污染时,渗透测试人员应寻找用户控制的输入可能影响正在合并、定义或克隆的键或属性的实例。验证应用程序是否正确清理并验证此类输入以防止修改原型链对于防止原型污染漏洞至关重要。

Mitigation Measures

减轻原型污染带来的风险对于渗透测试人员和安全代码开发人员都至关重要,因为该漏洞使攻击者能够操纵对象的原型,从而可能导致意外行为和安全问题。以下是一些原型污染缓解措施:

渗透测试人员

  • 输入模糊测试和操纵:广泛与用户输入交互,尤其是用于与基于原型的结构交互的输入,并使用各种有效载荷对其进行模糊测试。寻找不受信任的数据可能导致原型污染的场景。
  • 上下文分析和有效载荷注入:分析应用程序的代码库,了解用户输入在基于原型的结构中的使用方式。将有效载荷注入这些上下文以测试原型污染漏洞。
  • CSP 绕过和有效载荷注入:评估 CSP 等安全标头在减轻原型污染方面的有效性。尝试绕过 CSP 限制并注入有效载荷以操纵原型。
  • 依赖关系分析和利用:对应用程序使用的第三方库和依赖关系进行彻底分析。识别可能引入原型污染漏洞的过时或易受攻击的库。利用这些漏洞操纵原型并获得未经授权的访问或执行其他恶意操作。
  • 静态代码分析:在开发阶段使用静态代码分析工具识别潜在的原型污染漏洞。这些工具可以深入了解不安全的编码模式和潜在的安全风险。

安全代码开发人员

  • **避免使用 proto**:避免使用 __proto__ 属性,因为它最容易受到原型污染。相反,使用 Object.getPrototypeOf() 以更安全的方式访问对象的原型。
  • 不可变对象:尽可能将对象设计为不可变的。这可以防止对原型进行意外修改,从而减少原型污染漏洞的影响。
  • 封装:封装对象及其功能,仅公开必要的接口。这有助于防止未经授权访问对象原型。
  • 使用安全默认值:创建对象时,建立安全默认值,避免依赖用户输入来设置原型属性。安全地初始化对象以最大限度地降低污染风险。
  • 输入清理:彻底清理和验证用户输入。使用用户控制的数据修改对象原型时要小心谨慎。应用严格的输入验证实践来降低注入风险。
  • 依赖管理:定期更新和监控依赖项。选择维护良好的库和框架,并随时了解与原型污染相关的任何安全更新或补丁。
  • 安全标头:实施安全标头(如内容安全策略 (CSP))来控制可以从中加载资源的来源。这有助于降低加载操纵原型的恶意脚本的风险。

通过结合严格的测试实践、安全编码原则和持续的安全意识,渗透测试人员和安全代码开发人员都可以为有效缓解应用程序中的原型污染漏洞做出贡献。定期更新有关新出现的威胁和漏洞的知识对于避免潜在风险至关重要。