深入浅出原型污染漏洞:从识别到利用
作者:Isaac Burton
引言
自原型污染漏洞被发现以来,人们对其本质和利用方式一直存在困惑。本文将讨论在真实环境中识别原型污染漏洞的最简单方法,这些方法可导致多种利用方式!
原型污染可能存在于服务器端或客户端。服务器端原型污染可用于修改应用程序控制流,利用过程既有趣又有回报。当客户端存在原型污染漏洞时,可被利用来执行跨站脚本(XSS)。在这两种情况下,利用原型污染都高度依赖于上下文。幸运的是,识别这种漏洞通常并不困难。
我们将通过几个示例来展示识别这种漏洞的简易性,然后深入探讨这些漏洞产生的原因,并研究其他识别和利用方法。
发现服务器端原型污染
识别此漏洞的最简单方法之一是向Burp Suite的Repeater发送API方法,并将JSON格式的请求体包装在__proto__对象中。
以下示例显示了一个针对虚构披萨餐厅网站的有效请求。用户提供他们最喜欢的披萨类型和电话号码,以注册奖励计划。
|
|
在后端,API解析请求体并确保所有必需参数都存在。如果我们发送以下请求,将收到400 Bad Request响应。
|
|
我们可以利用这种输入验证。然后将有效的请求体包装在__proto__对象中,如下所示,应用程序返回200 OK响应。
|
|
这看起来可能很奇怪,但这是我们发现此处存在漏洞的第一个迹象。此时,此请求被接受有两个潜在原因:
- API在请求体中搜索有效请求键。
- 正在执行不安全的合并(稍后详述)。
我们可以发送以下请求来排除第一个选项。如果应用程序正在搜索有效键,那么我们应该能够将__proto__对象更改为任何其他内容,应用程序将响应200 OK。例如,我们将__proto__更改为false_positive。
|
|
由于应用程序拒绝了此请求,我们可以假设__proto__对象在应用程序中具有特殊含义。可以尝试变体,如_proto_或__proto,响应都应返回400 Bad Request。
如果仅当对象名称为__proto__时收到200 OK响应,那么恭喜,你刚刚找到了一个服务器端原型污染漏洞!稍后,我们将深入探讨其工作原理以及如何利用它。
测试客户端原型污染
PortSwigger已将自动化原型污染识别和利用功能添加到其浏览器工具DOM Invader中。该工具可以识别接收器(sinks)和小工具(gadgets),甚至创建概念验证利用!
接收器是代码中可以修改原型对象的位置,例如应用程序未安全处理的URL参数。小工具是污染对象可被利用的位置。DOM Invader使查找接收器和小工具变得容易,只需确保拥有最新版本的Burp Suite并按照以下步骤操作:
- 在Burp中打开DOM Invader(Proxy > Intercept > Open Browser)。
- 转到浏览器中的扩展,启用Burp Suite扩展。
- 在扩展中打开DOM Invader和原型污染。
- 重新加载页面并打开Inspector,然后导航到新添加的“DOM Invader”选项卡。
- 如果工具识别出接收器,则重新打开扩展并启用小工具扫描。
- 重新加载并导航回Inspector的“DOM Invader”选项卡。顶部应看到一个进度条。如果为先前找到的接收器识别出任何小工具,则应看到生成概念验证利用的选项,如下所示。
DOM Invader扩展非常强大,能有效搜索客户端代码,这些代码通常被压缩且难以阅读。如果你对手动方法感兴趣,强烈推荐查看PortSwigger Academy的原型污染课程(https://portswigger.net/web-security/prototype-pollution)。
原型污染的工作原理
那么,为什么__proto__对象特殊?如果你熟悉面向对象编程,“继承”一词应该耳熟能详。当创建新对象时,它们从其类以及任何父类获得属性。在JavaScript中,有一个“原型”对象的概念,它本质上是所有对象继承的根父对象。
在JavaScript中,原型对象是可写的,甚至在运行时也是如此。如果任何属性被添加到原型中,那么每个新创建的对象都将具有该属性。这允许我们修改开发人员从未意图(或期望)我们控制的变量!
考虑一个服务器端合并函数,它从一个对象获取属性并在另一个对象中更新它们。你可能希望保存目标对象中存储的一些属性,仅更新源对象中描述的值。此外,包含在其他对象中的对象需要以相同方式复制。以下代码片段可能是此问题的解决方案。
|
|
这里的漏洞可能不明显。如果攻击者包含一个__proto__键,其值设置为一个对象,递归不仅会写入当前对象的原型,还会写入全局对象的原型。这意味着所有新创建的对象都将继承攻击者定义的属性。其后果仅受攻击者想象力的限制。
服务器端原型污染利用
由于无法覆盖所有可能的情况,我们仅看几个如何利用此漏洞的示例。
建立上下文
在利用原型污染时,上下文至关重要。利用服务器端任何东西的困难在于通常无法看到另一侧的情况。首先,尽可能进行发现。使用Gobuster(https://github.com/OJ/gobuster)和SecLists(https://github.com/danielmiessler/SecLists)中的发现词表来查找服务器上的隐藏文件和位置。如果幸运,可能会找到源代码仓库。如果项目本身是开源的,你就可以开始深入挖掘了。
由于全局原型在线程的生命周期内将被污染,你可能能够利用应用程序使用的开源库。尝试通过模糊测试强制服务器返回错误消息。我最喜欢的工具选择是Burp Suite Intruder和wfuzz(https://github.com/xmendez/wfuzz)中提供的词表。攻击完成后,你可以按长度和状态代码对响应进行排序,甚至搜索错误消息关键词。
收到的错误消息可能返回部分(如果不是详细的话)堆栈跟踪,这可以帮助你映射后端源代码。此外,如果能够识别应用程序使用的任何库,你可能能够利用源代码提供的上下文。
示例
作为示例,假设应用程序在收到POST请求时执行以下步骤:
- 服务器检查请求头中提供的会话令牌,并接受或拒绝请求。用户的ID存储在线程内存中供以后使用。
- URL与更新用户配置文件的函数匹配。
- 函数(不安全地)将请求数据合并到一个对象中以保存用户提供的数据。
- 服务器验证所有必需属性是否包含。
- 应用程序从数据库中存储的信息创建用户对象。
- 对用户对象执行权限检查,其中仅对管理用户设置属性。
- 函数通过写回用户数据库并返回200 OK来完成其例程。
在此场景中,攻击者可以简单地将管理属性添加到__proto__对象中,从而提升请求的权限。请记住,在利用原型污染后创建的每个对象都会受到影响。
结论
随着JavaScript API的普及、语言本身的奇特特性以及漏洞的非明显性,我认为我们尚未完全发现其危险性和利用潜力。
最后,我想向你推荐一些资源,以帮助你进一步探索此主题。Olivier Arteau撰写了一篇关于此主题的精彩研究论文,包括从野外JavaScript库中发现的发现和利用示例: https://raw.githubusercontent.com/HoLyVieR/prototype-pollution-nsec18/master/paper/JavaScript_prototype_pollution_attack_in_NodeJS.pdf
另一个关于此主题的精彩文章来自Changhui Xu,他非常清楚地描述了问题并提供了更多示例: https://codeburst.io/what-is-prototype-pollution-49482fc4b638
以上两个来源均被官方CWE引用:不当控制对象原型属性的修改(“原型污染”)。 https://cwe.mitre.org/data/definitions/1321.html
PortSwigger有一个关于服务器端原型污染的新课程(截至本文撰写时),可在其学院网站上找到: https://portswigger.net/web-security/prototype-pollution/server-side
最后,我为上述部分中的示例创建了一个GitHub仓库。该示例大约100行代码,在一个文件中,没有依赖项,可以在Node.js或浏览器控制台中运行。随意将其用作参考或 playground: https://github.com/syscl0ck/Prototype-Pollution
祝黑客愉快, Isaac