
如何确定 API 定价的最佳方法?
在 StackHawk,我们收到了多起安全研究人员能够绕过我们的 API 速率限制保护的报告。从高层次来看,我们的速率限制器由客户端 IP 地址控制。我们发现我们的 API 通过标头接受伪造的 IP 地址X-Forwarded-For
。
此标头由用户控制,因此用户为此标头发送的任何值都将位于最前面。用户可以发送任何他们喜欢的内容,从欺骗性的 IP 地址到完全无效的值。由我们来清理和忽略垃圾邮件!
随着我们深入挖掘,我们意识到我们的 API 框架中的错误配置导致它总是选择通过接收的第一个值 X-Forwarded-For
,而没有进行任何验证。
让我们探索这个标头的一些细节,看看一些流行的云提供商、应用程序框架和 Web 服务器如何处理它。
该X-Forwarded-For
标头旨在供网络链中的代理使用,以跟踪发起调用的客户端的 IP 地址。发出 Web 请求时,接收请求的服务器会知道调用该请求的 IP 地址。使用标头时,接收X-Forwarded-For
服务器应将调用客户端的 IP 地址附加到该标头值中的 IP 地址列表中。生成的标头可能如下所示:
'X-Forwarded-For: <client>, <proxy1>, <proxy2>'
下图说明了X-Forwarded-For
标头在通向 API 的过程中是如何构建的:
在完全信任的环境中,列表中的第一个 IP 地址是原始客户端的源 IP 地址。代理添加的第一个 IP 地址是您要选择的 IP 地址。但是,软件开发的世界并不是一个完全信任的环境。您只能信任您能控制的东西。
代理服务器默认不会清除此标头,它们只会向其附加内容。攻击者可以生成包含此标头值的请求,如果您不小心,您的应用程序就会使用这些错误值。
我们可以通过多种方式利用X-Forwarded-For
产品或监控体验中的值。这样做时,拥有准确的值非常重要。
如果我们想要依靠 IP 地址来实现上述任何功能,我们必须验证 IP 地址的选择方式,以确保我们获得准确的值。对于上述任何功能,允许使用伪造或恶意的 IP 地址都会破坏该功能的价值主张。
云提供商遵循标头的规范X-Forwarded-For
,许多云提供商默认采用附加模式。亚马逊网络服务 (AWS)和Google Cloud Platform (GCP)会默认将 IP 地址添加到列表中,而不会对该列表中的现有 IP 地址进行任何验证或验证。需要强调的是,任何现有 IP 地址X-Forwarded-For
都保留在列表的最前面。
您还可以自己运行代理,例如 NGINX,它也允许您配置如何X-Forwarded-For
处理。虽然 NGINX 的配置性很高,但它们合理的默认函数的行为类似于 AWS,它将客户端的远程地址附加到标头值列表中。
重要的是要强调 AWS 和 GCP 在处理这些标头方面的区别。这说明每种情况都是独一无二的,您的情况将取决于处理请求的云提供商、代理服务器和应用程序框架。
AWS 严格将调用客户端的单个 IP 地址附加到X-Forwarded-For
标头。GCP 将附加两个 IP 地址,即调用客户端的 IP 地址,后跟请求通过的负载均衡器的 IP 地址。
AWS:'X-Forwarded-For: <whatever-was-on-the-request-before>, <aws-detected-client-ip>'
地源控制点(GCP): 'X-Forwarded-For: <whatever-was-on-the-request-before>, <gcp-detected-client-ip>, <gcp-load-balancer-ip>'
我们已经可以看到主要云提供商之间的分歧,以及为什么解析此标头以获取准确值会很困难。在这两种情况下,<whatever-was-on-the-request-before>
可能什么都没有,可能是来自其他网络代理的值,也可能是恶意或欺骗的值。唯一的共同点是两者都将 IP 地址附加到原始标头值。
如果您是具有可公开访问的 API 层的服务提供商,则请求会来自您无法控制的层。无论这是您的 UI 还是直接 API 访问,您都无法默认信任该请求。不受信任的客户端可以将他们喜欢的任何 IP 放入X-Forwarded-For
标头中,这会用欺骗或恶意数据污染值。此标头中的值范围从左侧最不受信任的值到右侧最受信任的值。我们只能信任我们能够控制的内容。
许多流行的应用程序框架都提供自动解析此标头的功能,但您的解决方案最终将取决于您的特定环境。解析此标头的一种简单方法是简单地获取列表中的第一个。在完全受信任的世界中,这是原始客户端的 IP 地址。实际上不要在生产中这样做,这是不安全的。
Spring Boot 是一个流行的基于 Java 的应用程序框架,具有许多功能。这些功能之一就是自动处理标X-Forwarded-For
头。
Spring Boot 天真地抓取 X-Forwarded-For 标头列表中的第一个值,没有进行任何验证,也无法自定义选择哪个值。
鉴于我们对 的了解X-Forwarded-Header
,如果请求中存在欺骗性的 IP 地址,Spring Boot 将始终选择该 IP 地址。默认配置容易使用欺骗性的客户端 IP 地址。
Tomcat 是一种流行的基于 Java 的 Web 服务器,能够处理将 Java 应用程序作为 Web 服务运行的低级问题。
Tomcat 有一种更好的解析方法X-Forwarded-For
,它以相反的顺序遍历 IP 地址列表,寻找第一个受信任的 IP 地址,同时提供跳过内部代理 IP 地址的机制。
我们要跳过的内部代理 IP 地址的一个示例就是<gcp-load-balancer-ip>
上面的那个。
Tomcat 会选择更准确的 IP 地址,并且可以配置为选择第一个受信任的 IP 地址,无论它是在列表的末尾还是在中间的某个地方。
如果我们的应用程序前面有多个代理,那么 Tomcat 可以自动过滤掉内部代理 IP,从而选择正确的客户端 IP 地址,而 Spring Boot 则显得太过天真。
我们现在知道 Spring Boot 将选择最不可信的客户端 IP 地址,但 Tomcat 仅在您向其描述内部代理时才会选择正确的客户端 IP 地址。否则,它只会提取列表末尾的 IP 地址。根据网络堆栈的最终形状和代理服务器的行为,这可能是一个内部 IP 地址。
如果您已经验证了您的配置有一分钟X-Forwarded-For
,或者您只是接受了框架的默认实现,那么现在是验证如何使用此标题的最佳时机。
当底层框架或平台提供功能时,人们通常会毫不犹豫地使用其实现细节。
这些框架的性质是它们可能是不久前编写的。它们不可能解释所有使用方式。
这是一个很好的例子,说明我们的框架的默认实现如何让我们面临这种速率限制器绕过漏洞。
如上所述,此标头中的第一个值仅在完全受信任的环境中才是准确的,因此我们的框架的默认实现是不安全的,因为它不考虑该标头中不受信任的值。
这促使我们限制速率限制逻辑,优先使用用户 ID 而不是 IP 地址,这样 IP 地址仅用于未经身份验证的 API 调用。我们的未经身份验证的 API 调用非常少。更重要的是,我们发现我们的应用程序框架盲目地选择了第一个 IP 地址作为X-Forwarded-For
真实来源。
我们修改了此逻辑,使其更加智能地选择这些 IP 地址,并且与 Tomcat 方法类似,我们现在从末尾抓取第一个有效 IP 地址。我们有意提取我们知道来自我们控制的可信来源的值。
这是输入验证的另一个典型示例。当它处于像这样的低级网络层时,它很可能在很大程度上被忽视。
您是否明确验证了您自己的应用程序如何处理X-Forwarded-For
?
更重要的是,您必须通过像攻击者一样解决问题来验证它。检查依赖于此客户端 IP 的功能。速率限制器、审计日志或地理使用情况统计信息,并尝试发送欺骗X-Forwarded-For
地址。