什么是原型污染?
要全面了解 JavaScript 中的原型污染,首先掌握语言中原型的概念非常重要。JavaScript是一种面向对象的语言,严重依赖继承。例如,以下示例中的方法从何而来?hasOwnProperty
const x = {a: 42}
x.hasOwnProperty("a") // true
x.hasOwnProperty("hasOwnProperty") // false
当你尝试访问一个属性时,JavaScript 首先会查看对象本身并检查该属性是否存在。如果存在,则返回此属性的值。如果不是,JavaScript 会查看其原型中是否存在相同的属性。然后重复此过程,直到找到属性,或者当对象不再具有原型时。
这个方法其实来源于的原型,即。x
Object.prototype
您可以通过魔术属性访问任何对象的原型。__proto__
const x = {a: 42}
typeof x.hasOwnProperty // "function"
x.__proto__ === Object.prototype // true
x.__proto__.hasOwnProperty("hasOwnProperty") // true
const s = "test"
typeof s.hasOwnProperty // "function"
s.__proto__ === String.prototype // true
s.__proto__.hasOwnProperty("hasOwnProperty") // false
s.__proto__.__proto__ === Object.prototype // true
s.__proto__.__proto__.hasOwnProperty("hasOwnProperty") // true
对象原型在所有对象之间共享,这意味着如果我们修改它,它将影响使用它的所有对象。
const x = {y:42}
x.y // 42
x.z // undefined
Object.prototype.y = 'hello y'
Object.prototype.z = 'hello z'
x.y // 42
x.z // 'hello z'
在此示例中,x.z 的值为“hello z”,因为属性 z 不存在于 x 中,而是存在于原型中。当涉及到原型污染时,这是一个需要了解的重要方面。如果攻击者可以修改原型,则可能会影响从原型继承的所有对象,这可能导致危险和意外的后果
让我们看一个易受攻击的代码示例:
const config = {
//allowCMD: true
}
const users = {
"guest": {name: "guest"},
}
function updateUser(username, prop, value){
users[username][prop] = value
}
app.route("/update", (req) => {
const {name, prop, value} = req.query
updateUser(name, prop, value)
})
app.route("/eval", (req) => {
const {code} = req.query
if (!config.allowEval){
return req.status(403)
}
eval(code)
})
在此示例中,提交请求将使用 allowEval=true 更新用户对象的原型。由于 config 和 users 对象共享相同的原型,因此下次我们尝试访问 /eval 路由时,我们将被允许运行任意代码。name=__proto__&prop=allowEval&value=true
/update 路由容易受到原型污染的影响,而 /eval 路由就是我们所说的小工具。小工具本质上是对对象的未定义属性的访问。由于原型污染攻击允许在任何未设置的对象中写入自定义属性,因此这些未定义的属性可能允许攻击者访问源代码的关键部分并更改应用程序的行为。
如何查找小工具
开发原型污染分为两部分。首先,必须找到写入 Object.prototype 的方法,然后必须识别可以使用的小工具。这类似于 PHP 反序列化小工具。虽然这些小工具本身不是漏洞,但如果以不安全的方式使用反序列化,它们可用于危害目标。 如何查找小工具 寻找原型污染小工具依赖于阅读和理解大量的JavaScript代码,这可能很耗时。例如,以下小工具非常容易查找和阅读:
let cmd = "echo 'Test: '"
if (obj.additionalArgs) { // additionalArgs is a gadget
cmd += " " + obj.additionalArgs.map(a => `'${a}'`).join(" ");
}
cmd += ";";
await exec(cmd);
但是,以下示例更难识别:
let cmd = "echo 'Test: '"
function processArgs(baseCommand, { additionalArgs }) {
if (additionalArgs) {
return baseCommand + " " + additionalArgs.map(a => `'${a}'`).join(" ");
}
return baseCommand;
}
cmd = processArgs(cmd, obj);
cmd += ";";
await exec(cmd);
在这种情况下,代码使用 JavaScript 的解构语法从对象访问可选的 additionalArgs 字段。这与以前相同,但在代码审查期间很容易错过。
在 JavaScript 中还有其他几种访问属性的方法,包括:
obj.k
obj[k]
const {k} = obj
const {k: {z}} = obj;
("key" in obj)
(({k}) => (0))(obj)
[...]
PP 查找器 PP Finder 是一个工具,可以简化在 JavaScript 代码库中查找原型污染小工具的任务。它旨在通过分析给定目录中存在的所有 JavaScript 文件并生成突出显示代码中潜在易受攻击部分的检测版本来促进这些漏洞的检测。
该工具通过使用 TypeScript 解析器为每个文件生成抽象语法树 (AST) 来工作。然后,它通过注入将检测属性是否被访问的钩子来修改此树。修改完成后,原始文件将替换为包含检测挂钩的修改版本。
通过修改 AST,PP Finder 能够检测各种可能难以发现的原型污染小工具。
钩子过程完成后,您可以像以前一样运行代码,PP Finder 将报告任何潜在的小工具并提供代码相关部分的位置。使用此信息,您可以分析小工具代码并确定如何利用它来实现任意代码执行或其他恶意行为。 工具地址:https://github.com/yeswehack/pp-finder
使用这个工具,我们继续在流行的js库中寻找有用的小工具。
检测服务器端污染示例
在无法访问应用程序源代码的情况下检测原型污染可能具有挑战性。以前的研究集中在污染方法上,如toString或valueOf来触发崩溃。虽然这可能是检查原始污染的一种方法,但导致崩溃可能不是每个人的最佳解决方案。
在pp-finder的帮助下,我们瞄准了两个最受欢迎的Web服务器节点,以便找到一种在不崩溃的情况下检测污染的方法。以下是我们的发现。
Express (4.18.2)
Express 是迄今为止最流行的 nodejs http 服务器。默认情况下,显式使用一些基本的缓存机制,这些机制可能会滥用以检查PP。
当我们发送带有“if-none-match”标头的请求时,我们希望收到 304 未修改的响应。但是,我们可以尝试通过添加 cache-control='no-cache' 来污染 Object 原型。
为了验证污染攻击是否成功,我们可以重新发送初始请求。如果我们收到 304 响应,则表明我们的漏洞利用不起作用。另一方面,如果我们收到 200 响应并且 etag 匹配,则意味着该网站容易受到攻击。
Fastify (4.13.0)
Fastify是另一种流行的HTTP服务器,专注于编写API服务器,大多数时候使用json作为数据编码。
这是正常的输出/
现在我们尝试添加:
"Content-Type":"application/json; polluted=true"
如果漏洞利用成功,我们应该在响应内容类型中看到污染成功。 ⚠️ 如果应用程序需要 JSON 以外的其他内容作为输入,这可能会使应用程序无法使用。
为了利用我们检测服务器端原型污染的能力,我们现在必须创建可用于利用流行JavaScript库中漏洞的有效负载。
总结
总之,原型污染是一个危险的漏洞,可以通过各种方式利用它来执行任意代码或控制 Web 应用程序。我们的 pp-finder 工具可以帮助检测代码库中潜在的原型污染问题,并且我们已经演示了如何在流行的 Node.js HTTP 服务器(如 Express 和 Fastify)中检测服务器端原型污染。有了这些知识,我们就可以开始制作有效载荷,可以利用流行的JavaScript库来执行原型污染攻击。
原文始发于微信公众号(红云谈安全):服务器端原型污染,如何检测和利用
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论