倾听低语:实际可用的Web计时攻击 | PortSwigger研究
James Kettle 研究总监 @albinowax
发布时间:2024年8月7日 18:10 UTC 更新时间:2024年11月18日 08:32 UTC
网站充满了渴望泄露其最深层秘密的计时预言机。是时候开始倾听它们了。
在本文中,我将发布新颖的攻击概念,以获取服务器秘密,包括被掩盖的错误配置、盲数据结构注入、通往禁区的隐藏路由,以及大量不可见的攻击面。
这不是理论威胁;每种技术都将通过针对不同目标的多个真实案例研究进行说明。前所未有的进步使这些攻击既准确又高效;现在您可以在十秒内可靠地检测亚毫秒级差异,无需任何先前的配置或"实验室条件"。换句话说,我将分享您实际可以使用的计时攻击。
为了帮助您,我将为您提供一套经过实战检验的开源工具,支持免提自动利用和自定义攻击脚本。我还将分享一个小型CTF来帮助您磨练新技能。
想更进一步?我将通过分享在数千个网站上测试无数概念后完善的方法论,帮助您将自己的攻击想法从理论变为现实。我们忽视这种无处不在且极其强大的侧信道太久了。
本研究论文附带了在黑帽美国大会和DEF CON上的演讲。
您也可以以打印友好的PDF格式阅读此白皮书。
大纲
- 背景
- 制作随处可用的计时攻击
- 回答难题
- 使计时攻击"本地化"
- 使计时攻击可行
- 隐藏的攻击面
- 发现过载
- 最困难的问题
- 证明概念
- 服务器端注入
- 盲SQL注入
- 盲JSON注入
- 盲服务器端参数污染
- 反向代理错误配置
- 范围限制的SSRF
- 防火墙绕过
- 隐藏目标
- 前端规则绕过
- 前端冒充
- 研究路线图
- 防御
- 要点
背景
Web计时攻击因两件事而臭名昭著:做出大承诺,但未能兑现。示例通常是理论性的,即使某种技术被称为"实用",每个人都知道一旦您尝试在实验室环境之外应用它,它就会停止工作。
这种声誉可能是为什么我们忽略了一个巨大机会的原因。
我第一次研究计时攻击的结果坚定地属于"理论"范畴。第二次尝试时,我首先回顾了我在野外成功应用的攻击,以及我读到的其他攻击:
从顶部开始,这些是以下示例:
- 跨站搜索
- 用户名枚举
- 潜在竞争条件的探测
- 实验室验证的攻击
- 理论攻击,如果它真的有效,那将非常惊人…
在寻找在野外有效的新技术时,我专注于两个类别之间的巨大鸿沟:
计时攻击研究通常专注于单一目标,但这限制了其真实世界价值。我想要可以应用于任意实时目标的技术。为确保我的新攻击概念符合此标准,我在包含30,000个实时网站的测试平台上验证了它们。基于bbscope和Rapid7的Project Sonar DNS数据库,测试平台是一个20 GB的Burp Suite项目文件,包含每个已知有漏洞赏金计划的网站。
在此研究之前,我亲自利用的最小时间差是30,000μs。现在,是200μs。这是通过计时攻击准确性的大幅进步实现的,并实现了多个强大的新技术。
三个关键攻击技术脱颖而出,在多样化的实时系统上提供了有价值的发现:发现隐藏的攻击面、服务器端注入漏洞和错误配置的反向代理。在本文中,我将深入探讨每一个。
制作随处可用的计时攻击
所有三种技术现在都在Param Miner中可用,所以,如果您愿意,可以停止阅读并立即尝试它们。这项研究的真正价值在于理解它不止于此;这些只是可能性的样本。计时攻击几乎可以带您到任何地方,但要掌握这种潜力,我们需要从头开始。
让我们仔细看看真实世界计时攻击生死攸关的关键因素,以及如何克服它们。在本节中,我将展示如何使计时攻击"本地化"、可移植和可行。
回答难题
很容易假设所有Web计时攻击都是漏洞利用,但这是一个错误,因为它限制了您对潜在应用的思考。在核心上,Web计时攻击只是关于回答难题——那些通过观察服务器响应无法回答的问题。
我通过尝试基于计时的密码重置漏洞利用开始了这项研究。结果很糟糕,但很好地说明了理论与现实之间的差距。许多网站通过在其数据库中存储秘密令牌并在链接中发送令牌到用户的注册电子邮件地址来实现密码重置。当用户点击链接时,网站将用户提供的令牌与数据库中的令牌进行比较。
在底层,字符串比较通常一次比较一个字符,直到它们完成字符串或遇到不匹配的字符对。这意味着匹配的字符越多,比较所需的时间越长:
在此图示中,我们使用两个HTTP请求来提问"数据库是否包含以d7e开头的密码重置令牌?“服务器比较每个字符需要一秒钟,因此通过比较响应时间,攻击者可以判断令牌以"d7e"开头而不是"d7f”。
不幸的是,比较每个字符的实际时间大约在5纳秒或0.000000005秒的范围内。祝你好运利用那个。
噪声与信号
每个计时攻击的成功都归结为两个竞争变量——信号和噪声。信号指的是您想要检测的时间差的大小,噪声指的是影响响应时间的所有其他因素。如果信号相对于背景噪声太安静,您将听不到它:
对于实际有效的攻击,您需要最大化信号并最小化噪声。本节的其余部分专注于如何做到这一点。
请注意,此方程不包括"测量次数"。您可以尝试通过重复测量来抵消噪声,但这种方法扩展性差。一旦噪声严重超过信号,您将很快需要数十亿次测量,导致攻击时间过长,目标可能在完成前就退役了。
您可以将噪声分为两部分——网络噪声(抖动)和服务器噪声(内部抖动):
网络抖动是延迟的变化——数据包到达目标系统并返回所需的时间。它是远程计时攻击的经典克星。当有人看到针对本地系统的计时攻击演示并说"那在远程系统上永远行不通"时,他们基本上是在说网络抖动将使攻击不可能。五年前,这可能是真的。
使计时攻击"本地化"
2020年,《永恒计时攻击》表明您可以使用HTTP/2完全消除测量中的网络抖动。您可以将两个HTTP/2请求放入单个TCP数据包中,确保它们同时到达服务器。然后您可以查看响应到达的顺序,推断哪个在服务器上处理时间更长:
这一单一发现消除了最大的噪声源,并改变了可检测内容的边界。只是有一个小问题。
粘性请求顺序问题
在HTTP/2层,两个请求完全并发,但底层的TLS数据是流,所以一个请求仍然是"第一个",即一个将在另一个之前完全解密。如果您尝试此技术,您会注意到网站显示出显著偏向于先回答第一个请求。这种偏向可能源于多个因素,包括解密第二个请求所需的时间和资源可用性。不幸的是,这可能掩盖您试图检测的延迟:
作者注意到了这个问题,并通过添加虚拟参数来减慢第一个请求的解析来解决它,试图重新同步执行。
使计时攻击可移植
实验室环境已知比真实目标的噪声少,但还有第二个更微妙的问题。专注于单一目标通常会产生需要大量调整才能应用于其他任何地方的目标特定技术。这使得它们对于任何有截止日期的人来说价值显著降低。
不幸的是,虚拟参数填充是这个问题的一个例子——其有效性取决于目标如何实现参数解析,以及系统在那一刻有多少处理容量可用。由于备用处理容量受其他系统影响,参数填充实际上可能最终增加噪声水平。我观察到在单个实验室系统上,十分钟内需要不同数量的参数。
我们真正需要的是解决粘性请求顺序问题的方法,不需要每个目标的配置。单包攻击(我去年为竞争条件发现开发的)为此提供了一个良好的起点。单包攻击分割请求以减少"关键数据包"的大小——完成请求并启动执行的数据包。
它通过在前几个数据包中发送大部分请求来工作,然后使用微小的最终数据包完成请求并触发执行。在此图中,最终的临界数据包用黑色勾勒:
不幸的是,这引入了不同的陷阱——一些服务器在获得标头后立即开始处理HTTP请求,而不等待正文。要解决这个问题,我们需要说服我们的操作系统网络堆栈将标头帧合并到单个数据包中,这样无论服务器在哪个阶段开始处理,两个请求都同时得到处理:
您可能想知道为什么我选择将请求分割成仅两个关键数据包,而不是每个HTTP标头一个数据包。那确实会是理想的,但不幸的是HTTP/2 RFC禁止交错来自单独请求的标头帧,所以它不太可能工作。
实现这种双包同步结果非常容易——只需添加一个额外的ping帧!这种无害的牺牲性数据包确保操作系统合并后续的标头帧。
- 禁用TCP_NODELAY
- 对于每个无正文的请求:
- 发送标头
- 保留一个空数据帧
- 对于每个有正文的请求:
- 发送标头,以及除最后一个字节外的正文
- 保留包含最后一个字节的数据帧
- 等待100毫秒
- 发送一个ping帧
- 发送最终帧
我们一发现这种改进的技术,就将其集成到Burp Suite的内置单包攻击中,所以您可能已经从中受益!我目前正在与开源实现h2spacex的开发人员合作,以将其也包含在其中。
使计时攻击可行
随着网络噪声的消除,我们的下一个目标是服务器噪声。不要低估服务器噪声。它源于众多来源,包括目标服务器上的负载、它交互的其他系统、在同一物理硬件上运行的其他虚拟系统,以及可能的数据中心附近的天气。服务器噪声是我没有对使用增强单包攻击可以检测到什么时间延迟做出任何声称的原因——任何这样的声称都是如此目标特定,以至于实际上毫无意义。
为了最小化服务器噪声,尽可能采用最短的代码路径,并充分利用缓存、对象重用和连接重用等性能特性。坚定的攻击者也可能使用CPDoS和资源消耗等DoS技术来减少来自其他用户的噪声。
为了最大化信号,专注于慢代码路径,并通过使用随机输入避免服务器端缓存、尽可能访问慢资源以及增加工作负载来使其更慢。例如,此请求使用多个具有固定前缀的标头来尝试扩大服务器查找以"X-U"开头的标头所引起的延迟:
|
|
像ORM和GraphQL这样的现代Web技术也特别适合延迟扩展技术。请记住,DoS攻击只是一个非常容易的计时攻击,并采用经典技术,如ReDoS、批处理和递归XML实体。
隐藏的攻击面
漏洞经常潜伏在废弃和被遗忘的功能中,这些功能被开发人员和安全测试人员 alike 忽视。因此,漏洞发现之旅通常始于检测隐藏的参数、cookie或HTTP标头。
在核心上,发现这些隐藏输入涉及猜测可能的参数名称,并观察它们是否改变响应。不改变响应的参数可能保持未被发现,以及任何相关的漏洞。对于我的第一个批量计时攻击,我决定解决这个问题。
方便的是,我是Param Miner的核心开发人员——可能是第一个用于批量参数发现的工具。Param Miner使用"字数"、“状态"和"行数"等属性比较响应。对于这项研究,我只需添加"响应时间"作为额外属性,增加重复计数,然后开始扫描。
我本可以让Param Miner使用单包攻击进行这些测量,但这将涉及重大的重构,并且在研究未经证实的概念时,我采取所有可能的捷径以避免浪费时间,所以我没有费心。
相反,我只是测量从请求的最后一个字节到响应的第一个字节的时间,并比较两组30次计时测量的下四分位数,看看它们是不同的(表示有效参数)还是重叠的。下四分位数是这种比较的理想选择,因为它反映了噪声最少的测量。
发现过载
在包含30,000个实时站点的测试平台上运行时间增强的Param Miner产生了大量的隐藏参数,包括一些非常奇怪的参数。
一个亮点是一个Web服务器,对包含神秘HTTP标头"commonconfig"的请求响应时间延长了5毫秒,除非标头值是有效的JSON:
| 标头 | 响应时间 |
|---|---|
| foo: x | HTTP/1.1 200 OK 50ms |
| commonconfig: x | HTTP/1.1 200 OK 55ms |
| commonconfig: {} | HTTP/1.1 200 OK 50ms |
另一个发现是在一个拒绝响应任何请求的Web服务器上——它总是重置连接。这种极其防御性的行为不足以阻止我的扫描发现它支持某个HTTP标头,因为该标头使其重置连接的时间显著延长!有趣,但不是非常有用。
| 标头 | 响应时间 |
|---|---|
| foo: x | –连接重置– 30ms |
| authorization: x | –连接重置– 50ms |
一个更实用的常见发现是:
| 请求 | 响应时间 |
|---|---|
| GET /?id=random HTTP/1.1 | 200 OK 310ms |
| GET /?foo=random HTTP/1.1 | 200 OK 22ms |
这对响应告诉我们两件有价值的事情。首先,该站点仅将特定参数如’id’包含在缓存键中,因此它高度暴露于基于参数的缓存中毒攻击。其次,我们知道’id’参数被键控,并且这种配置通常是全站范围的。这意味着使用时间分析,Param Miner检测到了适用于不同页面的参数!
最困难的问题
当我尝试这个概念时,我预计会有两个问题。首先,我预计许多技术会完全失败。其次,我怀疑我遇到的任何有效结果都会隐藏在大量误报中。
最大的挑战来自两者都不是。而是计时攻击太强大了。它们可以检测到如此多的东西,以至于很容易误解您检测到的东西。它们非常擅长检测"某物”,但那个某物不一定是您试图检测的东西。这个视频完美地说明了这一点。这个参数检测初看起来像是RCE,结果却是完全不同的东西(但仍然有用)。
这个视频显示,由于"exec"参数导致可见的响应延迟,初看起来像是潜在的远程代码执行漏洞。结果发现这个延迟是WAF对更可疑请求进行额外处理的指标。然后我们看到,当参数重复时,延迟会叠加,除非请求正文超过某个大小阈值。最终这导致发现了一个完整的WAF绕过。这个绕过发现对我来说完全出乎意料,但后来被其他人发现,现在在nowafpls工具中实现。它仍然是时间分析如何揭示目标控制流洞察的一个美丽演示。
那是一个简单的情况——有时您可能永远无法完全理解您检测到的东西。轻松对待您的假设,并尽可能从不同角度测试它们。
证明概念
为了避免被错误的假设误导,我决定专注于特定的参数,这些参数提供明确的安全影响,无需任何耗时的手动调查,并且有直接的方式收集额外的确证证据。
通过HTTP标头的IP地址欺骗完美地满足了这些要求。这是一个相对常见的错误配置,并直接启用各种漏洞利用,包括速率限制绕过、伪造日志,甚至在某些情况下的访问控制绕过。通过将IP地址放在欺骗的前端标头中,您实际上是在冒充前端。我们将在后面更深入地探讨前端冒充攻击。
方便的是,如果您将域放在欺骗的标头中,易受攻击的服务器通常会执行带内DNS查找来解析它,导致容易检测到的延迟。这是一个典型的检测:
| 标头 | 时间 |
|---|---|
| Random-header: xyz.example.com | 65ms |
| True-Client-IP: xyz.example.com | 70ms |
| True-Client-IP: xyz.example.com | 65ms |
第一个响应快速返回,因为它不触发DNS查找。第二个响应触发对xyz.example.com的DNS查找,所以它较慢,第三个响应到达更快,因为DNS响应已被缓存:
我们将在后面重新讨论DNS缓存。总的来说,扫描IP地址欺骗揭示了:
- 375个易受攻击的域
- 其中206个还导致DNS回显
- 217个 visibly 缓存了结果
这可能让您想知道大约170个没有导致DNS回显的易受攻击域——它们是误报吗?这是一个例子:
| 标头 | 时间 |
|---|---|
| Random-header: x.psres.net | 170ms |
| True-Client-IP: x.psres.net | 90ms |
| True-Client-IP: 1.1.1.1 | 170ms |
您认为这里发生了什么?
这是一个线索——在您的登录历史中,网站指定了登录IP地址和位置:
| 时间 | 浏览器 | IP | 位置 |
|---|---|---|---|
| 5分钟前 | Windows中的Chrome | 1.1.1.1 | Cloudflare |
我认为这个系统正在将欺骗的IP地址传递给一个库,该库在将其传递给第三方地理查找服务之前验证格式。提供像’x.psres.net’这样的无效IP地址导致异常,并阻止了慢速的IP查找发生:
所以,我们获得了一种新的参数发现技术,证明了计时攻击可以在野外大规模工作,并且还注意到了一些重要的事情:触发异常的输入可以 shortcut 大的代码路径,并导致显著更快的响应。换句话说,计时攻击特别擅长检测异常。
服务器端注入
触发和发现异常是测试服务器端注入漏洞的基础部分,从SQL注入到OS命令注入。这使得时间分析成为服务器端注入检测的完美匹配。
我试图通过添加"时间"作为响应属性到Backslash Powered Scanner来复制我在Param Miner中的成功,但这失败了。没有单包攻击,我只能检测到主要的时间差异,这些差异主要来自WAF而不是真正的漏洞。此外,工具的复杂性使其难以适应以克服挑战。
对于第二次尝试,我重用了Param Miner的一些代码来构建一个更简单的测试,使用单包攻击。我为每个探测发出最多50个请求对,并记录每对的响应顺序。如果响应顺序至少80%偏向一个有效载荷,我将其报告为有效发现。
完全盲SQL注入
第一个发现是一个完全盲SQL注入,使用经典的有效载荷对检测:
| 请求 | 响应时间 |
|---|---|
| GET /api/alert?mic=’ {} | 162ms |
| GET /api/alert?mic=’’ {} | 170ms |
不幸的是,当我报告它时,结果是一个重复。回想起来,我应该预见到这一点——您可以使用众所周知的’||sleep(5)||‘有效载荷轻松检测相同的漏洞。高级时间分析根本不需要检测您可以注入sleep语句的漏洞。同样,时间对于查找代码注入并不太好,因为您通常可以通过使用OAST技术更好地找到那些。
对于像命令注入、SQL注入和代码注入这样强大的漏洞,基于时间的检测只有在您有WAF或过滤阻止经典检测技术时才有用。让我们看看其他地方。
盲JSON注入
当寻找注入底层时,时间就发挥了作用;允许操作数据结构和格式但停止在完全代码执行之前的漏洞。这包括注入到像JSON、XML、CSV这样的格式,以及服务器端查询参数和HTTP标头。许多这些错误很少被谈论,因为它们很难检测。
它们也很难利用,但有时您可以将时间信息与可见特征结合,以获得对幕后发生的事情的额外洞察。例如,我注意到一个目标,其中无效的JSON转义序列使响应返回快200us(0.2ms):
| 参数 | 响应时间 |
|---|---|
| key=a"bb | “error”: { “message”: “Invalid Key: a"bb”} 24.3ms |
| key=a"\bb | “error”: { “message”: “Invalid Key: a”\bb"} 24.1ms |
您认为服务器端发生了什么?
响应格式中有一个线索——我们注入的无效语法没有改变响应中的格式。我期望JSON格式化程序在运行无效语法时失败,或者至少返回 visibly 不同的输出。
此外,长输入在响应中被编辑:
| 参数 | 响应时间 |
|---|---|
| key=aaa…a"bbb | “error”: { “message”: “Invalid Key: ****bbb”} 24.3ms |
这个特性提供了第二个线索:当我们的无效JSON序列被编辑时,时间差异消失了!综合来看,这 strongly 表明延迟是由于解析发送给我们的响应的组件发生的。我最好的猜测是它是某种错误记录系统。我从0.2ms的时间差中弄清楚了这一点感到相当高兴,但没有明确的利用路径,我决定继续前进。
盲服务器端参数污染
我最富有成果的探测是针对盲服务器端参数污染的。这通过比较保留URI字符如?和#与非保留字符如!的响应时间来工作。
在某些情况下,发送编码的#使响应返回更快:
| 请求 | 响应时间 |
|---|---|
| /path?objectId=57%23 | 无法解析参数 180ms |
| /path?objectId=57%21 | 无法解析参数 430ms |
这可能是由于片段破坏了服务器端路径并从后端获得快速的静态响应,或者应用程序的HTTP客户端 simply 拒绝发送包含原始#的HTTP请求。当然,关键不要假设延迟会落在哪一边——在其他目标上,编码的#使响应到达更慢。
服务器端参数污染是注入发现中最常见的类型,差距巨大,所以我认为这是一个有前途的进一步研究领域。有关此攻击类的更多信息,请查看服务器端参数污染和《攻击Web应用程序中的次要上下文》。
错误替身
正如我们所见,高精度时间非常擅长检测盲注入错误,但它们并不总是容易利用。在分析这些发现时,我经常对服务器端发生的事情有所了解,但在实际利用方面停滞不前。此外,时间往往浮现我们不太熟悉利用的较不知名攻击类。
仅基于时间证据收集足够的信息进行利用通常是棘手和耗时的。测试常规非盲漏洞的每个想法通常涉及单个中继器请求,而对于这些中的许多,您可能要看一个30秒的Turbo Intruder攻击。
这里可以帮助的一件事是"错误替身"——目标错误类的非盲变体。Param Miner将报告这些,它们非常适合在 less 挑战性的环境中学习如何解释和利用这些错误。
错误替身是这项研究中 recurrent 主题的一部分。如果您忽略时间,您将错过,但如果您过于专注于时间,您也将错过。为了成功,使用每个可用的信息通道。
反向代理错误配置
这项研究中最大的突破是当我意识到我可以使用时间来检测一种被广泛忽视的SSRF类型。
早在2017年,我研究了利用错误配置的反向代理进行SSRF并访问内部系统的技术。最常见的漏洞是路由请求到HTTP Host标头中指定域的服务