背景介绍
Detectify Crowdsource 是一个邀请制的漏洞赏金社区,今天这篇文章来自该社区的黑客 Hakluke 和 Farah Hawa,他们在本文中详细探讨了黑客和防御者如何(安全)破解 API,以帮助使互联网更安全。
10 年前,Web 应用程序基本都遵循着一种单一模式,即大部分应用程序向用户呈现内容前都由服务器端生成,所需的任何数据都由生成 UI 的同一台服务器直接从数据库中收集。
许多现代 Web 应用程序基本都遵循着 SPA(单页应用程序)的不同模型,在该模型中,通常有一个 API 后端、一个 JavaScript UI 和数据库, API 只是充当 Web 应用程序和数据库之间的接口,对 API 的所有请求均直接从 Web 浏览器发出。
这通常是一个更好的解决方案,因为它更易扩展,并且允许更专业的开发人员处理该项目,即前端开发人员处理前端,后端开发人员处理 API,这些应用程序往往速速也更快,因为并非每个请求都需要页面加载。
相反,同一页面的不同组件也会神奇地更新,给人一种本地应用程序类似的感觉,这种模式也变得更加流行,因此不同的前端 JavaScript 框架(React、Vue 和 Angular 等)也层出不穷。
所有这些都是为了说明——现在 API 无处不在,所以我们应该知道如何破解和保护它们。
设置测试API
Postman 是一个方便的应用程序,使 API 安全测试变得更加轻松,可以从其官方网站下载获得,本质上,Postman 只是另一个 HTTP 客户端,可用于轻松修改 API 并向 API 发送请求。
如果你的文件中有 API 请求的集合,则可以通过单击应用程序左上角的“导入”按钮将它们导入 Postman:
导入集合后,你将看到 Postman 中加载的 API 调用,如下所示:
通过单击单个 API 调用,完整的 API 请求将显示在右侧,此外请求的不同部分将被分解为参数、授权、标头、请求正文等部分,这可以让你轻松地使用或修改每个部分。
你可以按照与 BurpSuite 中相同的方式修改请求标头和正文,要分析对测试用例的响应,你只需点击右上角的“发送”按钮即可。
Postman 中的响应视图如下所示,响应也分为不同的部分,如正文、Cookies、标头等,因此你可以仔细分析每个部分。
由于 Postman 专门用于 API,因此你有很多内置选项来测试 API 中主要存在的功能,如,通过单击请求方法旁边的下拉箭头,可以看到大量不同的请求‘方式’来进行测试:
对于 GET 请求,你可以通过“参数”选项卡添加/删除以及编辑参数,当选中/取消选中参数时,可以看到它们相应地出现在 URI 字段中。
在添加 Authorization values 时,Postman 也提供了多种选项可供选择,可以根据目标 API 处理授权的方式进行选择。
还可以按照以下步骤将 Authorization values 添加到整个集合或子集合:
- 单击 collection/sub-collection 名称侧面的三个点,然后选择“编辑”选项:
-
转到 Authorization 选项卡,选择身份验证类型并添加其值:
-
最后,转到单个 API 请求并选择从父级继承身份验证选项:
这将省去为每个请求设置 Authorization values 的麻烦。
与Burp配合
Postman 的另一方便功能是它允许用户使用 BurpSuite 代理 API 请求,为了进行设置,需要执行以下步骤:
-
单击右上角下拉菜单中的“Setting”选项
-
转到 Proxy 选项卡并执行以下操作:
- 关闭 使用系统代理
- 打开添加自定义代理配置
- 设置代理服务器 IP 地址和端口以匹配 Burp Suite 代理设置。(默认值为 127.0.0.1 和 8080)
- 要代理 HTTPS 请求而不出现任何错误,需要在“General”选项卡下关闭 SSL 证书验证:
API漏洞类型
API 有多种类型,攻击 API 的方法将根据这些类型的不同而有很大差异,在一篇文章中不可能涵盖所有攻击类型,本文将尽可能覆盖更多的类型!
API暴露
与 Web 应用程序非常相似,API 可以具有不同级别的可见性,有些可以通过互联网访问,而另一些则只能在内部使用,最基本的 API 黑客攻击之一就是简单地获取对本应无法访问的 API ,这可以通过多种方法来实现,包括:
强制浏览:如果幸运的话,供内部使用的 API 可能会意外暴露在互联网上,要么是由于配置错误,要么只是因为假设没有人能够找到它, API 位置可以通过多种方式发现,包括分析 JavaScript 文件、分析公开的源代码、观察主机名(例如 api.internal.example.com)和 Google dorking。
Pivoting:在外部主机上发现诸如 SSRF 之类的漏洞可能会让你成功进入内部 API。
缓解措施:
有很多最佳实践可以帮助缓解无意暴露 API 的情况,包括实施严格的部署实践、通过 IAM 和网络分段强制执行最小权限原则。
缓存配置错误
对于需要身份验证的 API,返回的数据通常是动态的,并且范围仅限于每个 API 密钥,例如,以 Bob 身份访问 /api/v1/userdetails
应返回 Bob 的详细信息,而以 Jane 身份访问同一端点应返回 Jane 的详细信息。
当 API 不使用标准 Authorization 标头,而是使用 X-API-Key 等自定义标头时,会发生常见的错误配置,缓存服务器可能无法将此识别为经过身份验证的请求,并有可能缓存它。
如果是这种情况,并且没有 Cache-Control 或 Pragma 标头,则仅访问 /api/v1/userdetails
可能会泄露其他用户的信息。
缓解措施:
解决此问题的方法是实现 Cache-Control 或 Pragma 标头并使用标准 Authorization 标头。
Token暴露
通过任何方式发现 API 密钥都可以为你提供对 API 的访问权限,更糟糕的是,供内部使用的 API 通常不需要实现复杂的身份验证流程,因此可以实现静态令牌作为其身份验证,Secret tokens 可能会在代码存储库、客户端 JavaScript、拦截流量等中被发现。
缓解措施:
在 DevOps 中实施代码扫描通常会在 API 密钥部署到不应部署的地方之前捕获它们,一些代码库提供商(包括 GitHub)还能够在推送 API 密钥之前检测它们。
JWT弱点
如果 API Token是由两个点 (.) 分隔的三个 base64 blob,则它可能是 JSON Web 令牌 (JWT),与许多事情一样,这些Token理论上是安全的,但有很多方法可以扰乱它,从而引入安全问题。在深入研究 JWT 攻击之前,先来快速浏览一下 JWT 入门知识。
下面是一个 JWT Token示例,为了便于学习特地进行了不同颜色的着色:
有三个以点分隔的 Base64 编码字符串,第一部分(红色)是 header ,第二个(紫色)是 Payload ,第三个(蓝色)是 Signature 。
如果我们解码第一部分(也被称为标头),将看到以下内容:
{
"alg": "HS256",
"typ": "JWT"
}
显示了正在使用的算法 (HS256) 和Token类型 (JWT),如果之前没有使用过 JWT,你可能会问“为什么我们需要算法?”,没关系,继续向下看。
如果我们解码第二部分(也称为有效负载),将看到以下内容:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
此部分可以包含任何内容,但至少需要包含某种用户标识符和超时 (iat)。
第三部分(也被称为签名)使用密钥对前两部分进行签名,在这种情况下,它使用 HS256 算法进行签名,可以通过查看标头中的“alg”值来确定,这个密钥应该只有应用程序的所有者知道,当应用程序收到 JWT 令牌时,它可以通过解密签名并将其与标头和负载中的数据进行比较来验证令牌是否合法,如果数据匹配则数据通过验证,否则数据无效。
那么为什么有人会使用 JWT 呢?这是因为它避免了服务器端会话管理的需要,传统上,当用户登录时,应用程序会向用户分配一个秘密Token,并将相同的Token存储在数据库中,每当用户使用其Token发出请求时,应用程序都需要检查该Token是否在数据库中,如果是,则允许用户继续,否则不允许。当我们使用 JWT 时,会引入了一种从客户端发送信任数据的方法,而不是仅仅信任存储在数据库中的信息,如果应用程序收到可以使用密钥验证的 JWT Token,则应用程序没有理由不信任它。
正如前面所提到的,理论上 JWT Token是完全安全的,问题在于它们的实施方式通常不安全。比如下面有些例子:
-
None algorithm:JWT 的某些实现将允许你指定“None”作为算法,如果算法为“None”,应用程序将不会检查签名的有效性,因此只需将Payload更改为想要的任何内容即可,最直接的利用方式是将用户 ID 更改为另一个用户的 ID。
-
暴力破解:可以暴力破解 JWT Token的密钥,这种攻击的可行性将取决于密钥的强度,可以尝试使用JWT Cracker 破解 JWT Token。具体使用方法参见:https://auth0.com/blog/brute-forcing-hs256-is-possible-the-importance-of-using-strong-keys-to-sign-jwts/
-
简单更改Payloads:在极少数情况下,服务器可能会完全跳过Token验证并信任Payloads中的数据,虽然作者没有亲眼见过此类情况,但它确实在野外发生过!
-
将 RS 切换到 HS:一些较旧的 JWT 库存在一个缺陷,可以欺骗需要使用非对称加密技术签名Token的应用程序接受对称签名的Token,最终使用的对称签名Token实际上是一个公钥,通常可以通过某种方式从其 HTTPS 密钥中获取或重用,具体详见:https://www.nccgroup.com/ae/about-us/newsroom-and-events/blogs/2019/january/jwt-attack-walk-through/
-
不遵守 iat 超时,导致 JWT Token 永远有效。
缓解措施:
针对 JWT 弱点的最佳缓解方法是对所有 JWT 操作使用广泛使用、信誉良好的 JWT 库。
授权问题/IDOR
授权是检查经过身份验证的用户是否有权访问特定用户的过程,一个常见与授权相关的漏洞称为不安全直接对象引用 (IDOR),例如,在发票应用程序的 API 中,我们可能有一个用于获取发票详细信息的端点:
/api/v1/invoices/?id=1234
id 参数是应返回的发票的标识符,如果此端点是安全的,应该只能获取属于我个人发票的详细信息,例如,如果我创建了 ID 为 1234 的发票,那么它应该返回详细信息。如果我尝试访问 /api/v1/invoices/?id=1233
创建的发票,它应该返回错误。
如果能够更改标识符以查看其它用户的发票详细信息,那么这就是一个称为 IDOR 的漏洞。
为了解决 IDOR 问题,如今许多 API 都会使用 UUID 作为对象标识符, UUID 看起来像下面这样:
f1af4910-e82f-11eb-beb2-0242ac130002
值得注意的是,使用 UUID 作为标识符并不是缓解 IDOR 问题的有效方法,事实上,UUID RFC 就特别指出了这一点:
虽然使用 UUID 而不是整数作为对象 ID 是一种很好的做法,但它们永远不应该用作针对 IDOR 攻击的唯一保护。
缓解措施:
授权问题通常很难以自动化方式检测,代码库的结构应该以难以在特定端点上发生授权错误的方式设置。为了实现这一点,授权措施应尽可能在堆栈上层实施,可能在类级别,或使用中间件。
未记录的端点
通常会遇到这样的情况:所攻击的 API 没有文档,带有文档的 API 具有超出文档范围的端点也是相当常见的,有时,这些端点的存在本身可能就是一个安全问题 – 例如,端点可能是出于管理目的而设计的,并允许你作为特权用户执行管理任务,其他时候,这些端点可能会出现漏洞,很多情况是因为它们没有像易发现端点那样经过彻底的安全测试。
可以利用以下几种方法来发现未记录的端点:
- 使用与 API 交互的应用程序并捕获流量,莫过于当今最常用工具– Burp Suite。
- 设置 Burp Suite 来代理应用程序流量
- 使用所有应用程序功能
- 通过查看“Target”选项卡检查所使用的端点
-
观察错误。许多 API 会给出足够详细的错误,这足以枚举未记录的端点和参数。例如,向 /api/v1/randomstring 发送空白 POST 请求可能会导致出现类似于 Invalid route, valid routes are [/users,/invoices,/customers] 的错误。
-
分析客户端 JavaScript,如果你知道某个应用程序与 API 交互,则可以分析该应用程序中的 JavaScript 以收集可访问的 API 端点列表。
-
使用 Kiterunner,一款专为 API 内容发现而设计的工具
https://github.com/assetnote/kiterunner
- 暴力破解端点,Assetnote 网站上有很多优秀的 API 字典
https://wordlists.assetnote.io/
缓解措施:
拥有一个强大、结构化的方法和流程来记录 API 功能可以避免以后出现很多麻烦, Swagger 正是这方面的绝佳标准,此外,最好在编码之前用文档规划 API 功能。
不同版本
当一个组织发布 API 时,它可能会与许多不同的应用程序交互。如果 API 在任何时候更新,都可能会给其中一个或多个应用程序带来重大更改,因此,通常会实现多个 API 版本作为支持旧 API 模式的一种方式,同时也会随着时间的推移为新用户升级 API。
值得测试该 API 的所有版本,旧版本可能依然存在安全问题,这些问题在新版本中得到修复,而较新/前沿/beta版本则可能引入了新的安全问题。
API 版本控制的常见模式是:
/api/v1/
/api/v2/
/api/beta/
检查非生产’路由’名称,如:
qa
devenv
devenv1
devenv2
preprod
pre-prod
test
testing
staging
stage
dev
development
deploy
slave
master
review
prod
uat
prep
Version2
该字典列表取自 DNSCewl 中的示例集。
https://github.com/codingo/DNSCewl/blob/master/sets/non-production-hosts.txt
缓解措施:
API 版本可以支持定义的生命周期。例如,当发布 API 的版本 2 时,可以通知用户版本 1 将达到生命周期 (EoL),并在未来的特定日期被弃用,预生产/测试版本只有在经过彻底的安全问题测试后才应公开访问。
速率限制
大多数时候,API 对用户可以请求它们的次数没有任何保护,这被称为“缺乏速率限制”,当攻击者可以调用 API 数千次以导致某些意外行为时,就会发生此类情况。服务器将尝试满足每个请求,从而导致:
- 通过请求使服务器过载来对服务器进行 DOS
- 允许攻击者快速窃取敏感用户信息,例如:用户 ID、用户名、电子邮件等
- 通过暴力破解登录 API 绕过身份验证
- 通过暴力破解向受害者发送电子邮件/短信的功能,轰炸受害者的收件箱
来看看下面这个攻击场景:
GET /api/v1/user/1234/login/?password=mypassword
通常你不会看到像这样在 GET 请求中发送的密码,但出于本演示的目的,假设你看到了,要暴力破解上述端点中的密码,攻击者可以使用 BurpSuite 的 Intruder 工具,从而定制不同类型的暴力攻击,对于本示例,将向其提供一个简单的密码列表。
在几秒钟内,入侵者将发出数百次 API 请求,并在每个请求上尝试不同的密码。
由于没有速率限制,服务器将响应所有这些请求,并且攻击者可以继续暴破不同的密码,直到找到正确的密码。
缓解措施:
速率限制可以通过多种不同的方式实现,每个帐户、每个 IP 地址、每个端点、整个 API 等,某些反向代理还可以用于实现应用程序范围的限制,而无需额外开发,使用的具体实现将取决于特定应用程序的要求。
竞赛条件
竞争条件是指在同一毫秒内向 API 发送两个或多个请求,当 API 没有处理这种情况的机制时,可能会导致 API 以意外的方式处理请求。
在易受攻击的电子商务应用程序上兑换折扣或促销代码时,可能会出现竞争条件的潜在攻击场景。
BurpSuite 有一个名为 Turbo Intruder 的扩展插件,它允许用户使用内置的 race.py 脚本测试竞争条件。
在 Turbo Intruder 中配置攻击脚本后,攻击者可以向 API 发送多个并发请求来兑换此促销代码。
如果 API 在收到第一个并发请求后没有立即使促销代码失效,则折扣金额可能会增加一倍、三倍等。
缓解措施:
不幸的是,缓解竞争条件问题通常意味着牺牲性能。缓解竞争条件的方法包括利用锁和其他线程安全功能,大多数编程语言都会内置线程安全功能,但通常需要手动指定。
XXE注入
XXE 代表 XML 外部实体,可以在使用 API 处理 XML 数据的任何地方测试此注入漏洞, SOAP API 也可能容易受到 XXE 注入的攻击,因为它同样基于 XML。
使用 XML 的 API 端点如下所示:
XML 文档使用实体来表示单个数据对象,XML 文档类型定义 (DTD) 用于定义要使用的实体、文档的结构等。
XXE 注入是指攻击者注入指定 DTD 之外的自定义外部实体。一旦这些外部实体被 API 解析,攻击者就可以访问应用程序的内部文件、升级到 SSRF、将敏感数据泄露到攻击者控制域或 DOS 服务器。
下面是一个请求示例,其中攻击者注入了名为 xxe 的外部自定义实体,该实体的目的是检索服务器内部文件:
如果 API 允许使用标准 XML 解析器来处理数据,那么这个注入的外部实体将由应用程序处理,并将 /etc/passwd
的内容返回给攻击者。
缓解措施:
确保所使用的 XML 解析器设置为不解析 XML 实体。
切换内容类型
尽管 API 可以使用 JSON 作为数据格式进行通信,但底层服务器/框架仍可以接受其它数据格式,例如 XML,因此,当看到 content-Type 为 application/json 的 API 时,仍可以通过将其值切换为 Content-Type: text/xml 来尝试测试 XXE
例如,假设 API 使用 JSON:
可以修改为以 XML 的方式发送数据:
缓解措施:
这个漏洞真正存在于被设计为接受多种格式的框架上,其中每种格式对应于一种对象类型,在这些情况下,框架通常可以选择将可用格式列入白名单。
HTTP 方法
API通常支持各种类型的HTTP方法,一些常见的有 GET 、 POST 、 PATCH 、 DELETE 和 OPTIONS ,如果是 GET 请求在应用程序期望 POST 请求的位置发送,则这可能会导致应用程序以意外的方式响应。
以 CSRF 为例。 CSRF(跨站请求伪造)是指攻击者诱骗受害者提交请求,从而导致受害者帐户发生状态更改操作,这些更改可以是任何内容,从更改受害者的个人详细信息,在某些情况下,甚至更改受害者的密码,导致帐户被完全接管。
更改受害者个人详细信息的正常请求可能如下所示:
观察上述请求后,我们可能会得出结论,CSRF 攻击是不可能的,因为 csrftoken 值是在请求正文中发送的,但是,如果 API 不限制可用于此请求的 HTTP 方法,则攻击者可能通过使用 GET 方法发送此请求来绕过此保护。
此外,有时服务器根本不验证 CSRF 令牌,如果请求中根本不存在 CSRF 令牌参数则接受请求。
REST API 中的另一个常见漏洞是权限已正确应用到端点,但仅适用于一个 HTTP ‘方式’,例如,你可能无法获取其他人的记录,但却可以使用 PATCH 来编辑他们的记录。
缓解措施:
在‘路由’中手动指定 HTTP 方式,并确保指定的任何端点都对所有‘方式’应用相同的控制,有些框架会自动执行此操作,有些则不会。
注入漏洞
SQL 注入、RCE、命令注入和 SSRF 等服务器端注入漏洞也可能存在于 API 上,就像存在于常规 Web 应用程序上一样。每当任何注入的数据直接传递到后端的解释器时,都会为攻击者发送有针对性的查询和命令来访问内部数据或执行任意代码留下空间。
例如,针对以下 API 请求测试 SSRF:
攻击者可以通过发送如下请求来尝试在 website
字段上进行 SSRF:
如果后端解释器未正确验证 profile_pic_url 值,则可能导致攻击者利用 SSRF 并成功访问内部数据。
同样,如果此请求中的数据验证不正确,攻击者也可以尝试 SQL 注入:
通过查看请求正文,我们可能会得到一个提示:这些参数的值正在发送到后端数据库解释器,因此,可以通过将这些值修改为目标查询来尝试SQL注入:
再次强调,如果解释器没有正确验证这些值,则可能会导致 SQL 注入,攻击者可能会导致数据库出现时间延迟,甚至泄露敏感数据。
缓解措施:
缓解 API 中的注入漏洞本质上与缓解 Web 应用程序中的注入漏洞相同, SAST/DAST 扫描器可以帮助解决此类问题,但安全的开发实践是最好的缓解措施。参数化 SQL 查询,尽可能利用 ORM 和信誉良好的库,另外记住:永远不要相信用户输入!
原文出处:https://labs.detectify.com/2021/08/10/how-to-hack-apis-in-2021/
本文由骨哥进行翻译并整理。