现代API渗透技术
API 有多种不同的形式,攻击 API 的方法会因这些形式的不同而有很大差异。在一篇文章中涵盖所有攻击类型是不可能的,下面我重点介绍其中一部分。
1. API 暴露
与 Web 应用程序非常相似,API 可以具有不同级别的可见性。有些可以通过互联网访问,而有些则只能在内部使用。API 攻击方式之一就是简单地访问你应该无法访问的 API。这可以通过以下方法来达到访问目的,包括:
· 强制浏览:如果幸运的话,仅供内部使用的 API 可能会意外暴露在 Internet 上,这可能是由于配置错误导致,或者仅仅是因为假设没有人能够找到它。在API攻击时可以通过多种方式发现,比如分析 JavaScript 文件、分析暴露的源代码、观察主机名(例如api.internal.example.com)和 Google dorking等。
· 反射或旋转:比如在外部主机上发现像 SSRF 这样的漏洞可能允许你转向内部 API。
预防措施
有许多最佳实践可以帮助你减轻无意暴露 API 的风险,包括实施严格的部署实践、通过 IAM 和网络分段强制执行最小权限原则等。
2. 错误配置的缓存
对于需要身份验证的 API,返回的数据通常是动态生成的,并且范围限定为每个 API 密钥。例如,/api/v1/userdetails以 Bob 身份访问应返回 Bob 的详细信息,而以 Jane 身份访问同一端点应返回 Jane 的详细信息。
当 API 不使用标准Authorization标头,而是使用自定义标头(如X-API-Key. 缓存服务器可能不会将此识别为经过身份验证的请求,并且可能会缓存它。
如果是这种情况,并且没有Cache-Control或Pragma标题,则简单地访问/api/v1/userdetails可能会泄露另一个用户的信息。
预防措施
对此问题的解决方法是实现Cache-Control或Pragma标头,并使用标准Authorization标头。
3. 暴露的令牌
我们不应该忽视最基本的身份验证攻击,通过发现 API 密钥可能会获取 API 的访问权限。更糟糕的是,那些仅供内部使用的 API,通常不需要实现复杂的身份验证流程,通常实现静态令牌作为其身份验证,并将令牌存储在代码存储库、客户端 JavaScript、拦截流量等中。
预防措施
在DevOps 管道中,实施代码扫描检测 API 密钥部署到它们不应该存在的地方,在部署之前捕获它们。另外,一些代码存储库提供者(包括GitHub),在推送 API 密钥之前检测它们。
4. JWT 的弱点
如果你的 API 令牌是由两个点 (.) 分隔的三个 base64blob构成,则它可能是一个JSON Web 令牌 (JWT)。这些令牌在理论上是安全的,但是有很多方法会以引入安全问题的方式来弄乱实现。在我们深入研究 JWT 攻击之前,我们将快速阅读 JWT 入门。
这是一个示例 JWT 令牌,根据你的阅读习惯进行了着色:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9。eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9liwiaWF0IjoxNTE2MjM5MDIyfQ . SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
有三个由点分隔的 base64 编码字符串,第一部分(红色)是header. 第二个(紫色)是payload,第三个(蓝色)是signature。
如果我们解码第一部分,也称为标头,我们将看到以下内容:
{
"alg": "HS256",
"typ": "JWT"
}
这就是令牌所使用的算法 (HS256) 和令牌类型 (JWT),如果你之前没有使用过 JWT,你可能会想 “ 为什么我们需要算法? ”
令牌的第二部分也称为有效载荷,解码后我们将看到以下内容:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
此部分可以包含任何内容,但至少需要包含 用户标识符和超时 (iat)。
令牌的第三部分(也称为签名)用密钥对前两部分进行签名。在这种情况下,它使用 HS256 算法进行签名,它可以通过查看标头中的“alg”值来确定。一般来说私钥应该只为应用程序的所有者知道。当应用程序收到 JWT 令牌时,它可以通过解密签名并将其与标头和有效负载中的数据进行比较来验证令牌是否合法。如果数据匹配则验证数据,否则无效。
那么为什么会有人使用 JWT?这是因为它不需要服务器端会话管理。传统上,当用户登录时,应用程序会为用户分配一个秘密令牌并将该令牌存储在数据库中。每当用户使用他们的令牌发出请求时,应用程序需要检查令牌是否在数据库中。如果是,则允许用户继续,否则不允许。当我们使用 JWT 时,我们引入了一种信任从客户端发送的数据的方法,而不是仅仅信任存储在数据库中的信息。如果应用程序收到可以使用密钥验证的 JWT 令牌,则应用程序没有理由不信任它。
正如我们之前提到的,理论上 JWT 令牌是完全安全的。问题是它们通常以不安全的方式实施。这里有些例子:
· None 算法:JWT 的一些实现将允许你指定“None”作为算法。如果算法为“无”,应用程序将不会检查签名的有效性,因此你可以简单地将有效负载更新为你想要的任何内容。最明显的利用是将用户 ID 更新为另一个用户以控制他们的帐户。
· 暴力破解:可以暴力破解 JWT 令牌的密钥。这种攻击的可行性将取决于密钥的强度。你可以尝试使用此工具破解 JWT 令牌。可以在Auth0 的博客上找到有关该方法的完整文章。
· 简单地改变有效载荷:在极少数情况下,服务器可能会完全跳过令牌验证并信任有效载荷中的数据。虽然我没有亲眼见过这种情况,但我已经读到它发生在野外!
· 将 RS 切换到 HS:一些较旧的 JWT 库存在一个缺陷,你可以在其中欺骗期望使用非对称加密签名的令牌的应用程序接受对称签名的令牌。最终被使用的对称签名令牌实际上是一个公钥,它通常可以以某种方式获得,或者从他们的 HTTPS 密钥中重用。有这种方法的一个伟大的书面记录在这里。
· 在超时未兑现,那么JWT令牌仍然有效,直到永远。
预防措施
JWT 弱点的最佳缓解方法是为所有 JWT 操作使用广泛使用的、信誉良好的 JWT 库。
5. 授权问题 /IDOR
授权是检查经过身份验证的用户是否有权访问特定用户的过程。与授权相关的常见漏洞称为不安全的直接对象引用 (IDOR)。例如,在发票应用程序的 API 中,我们可能有一个端点用于获取发票的详细信息:/api/v1/invoices/?id=1234
该id参数是应返回的发票的标识符,当这个端点是安全的,我应该只能获得属于我的发票的详细信息。例如,如果我创建了一个 ID 为 1234 的发票,那么它应该返回1234的详细信息;如果我尝试访问不是通过浏览创建的发票/api/v1/invoices/?id=1233,它应该返回错误。如果我能够更改标识符并可以查看其他用户(如1233)的发票详细信息,那么这就是一个称为 IDOR 的漏洞。
为了解决 IDOR 问题,当今许多 API 都使用 UUID 作为对象标识符。UUID 如下所示:
f1af4910-e82f-11eb-beb2-0242ac130002
需要注意的是,使用 UUID 作为标识符并不是缓解 IDOR 问题的有效方法。事实上,UUID RFC特地将这一点称为:
不要假设 UUID 很难猜测;例如,它们不应该用作安全功能(仅拥有就可以访问的标识符)。可预测的随机数源会加剧这种情况。
虽然将 UUID 用作对象 ID 而不是整数是一种很好的做法,但它们永远不应用作抵御 IDOR 攻击的唯一保护措施。
预防措施
授权问题通常很难以自动化方式检测。代码库的结构一般难以在特定端点上发生授权错误的方式设置。为实现这一点,应尽可能在堆栈上实施授权措施。比如在类级别或使用中间件级别。
6. 未记录的端点
在API攻击过程中,遇到没有API文档(或至少没有你可以访问的文档)的情况是很常见的。带有文档的 API 具有超出文档内容的其他访问端点也很常见。有时,这些端点的存在本身可能是一个安全问题——例如,端点可能是为管理目的而设计的,并允许你作为弱势用户执行管理任务。其他时候,这些端点可能会出现漏洞,因为它们没有像容易发现的端点那样经过彻底的测试。
我们可以利用以下几种方法来发现未记录的端点:
· 使用与 API 接口的应用程序并捕获流量。比如Burp Suite。
· 设置 Burp Suite 来代理应用程序流量
· 使用所有应用程序功能
· 通过查看目标选项卡检查使用的端点。
· 观察错误。许多 API 会给出足够详细的错误来枚举未记录的端点和参数。例如,向发送一个空白的 POST 请求/api/v1/randomstring可能会导致出现类似Invalid route, valid routes are [/users,/invoices,/customers].
· 分析客户端 JavaScript。如果你知道与 API 交互的应用程序,你可以分析该应用程序中的 JavaScript 以收集可访问的 API 端点列表。
· 使用Kiterunner,这是Assetnote的一个工具,专为 API 上的内容发现而设计
· 暴力猜测端点,Assetnote 网站上有一些 优秀的API词表
预防措施
拥有一个强大的、结构化的方法和流程来记录 API 功能可以避免很多麻烦。Swagger正是这一点的绝佳标准。此外,最好在编码之前用文档规划 API 功能,而不是相反。
7. 不同 API 版本问题
当一个组织发布一个 API 时,它可能会与许多不同的应用程序接口。如果 API 在任何时候更新,它可能会为这些应用程序中的一个或多个API引入重大更改。出于这个原因,多个 API 版本通常被实现为支持旧的API 模式的一种方式,同时也随着时间的推移为新用户升级 API。
测试 API 的所有版本是一个值得投入的攻击方式。旧版本可能仍然存在已在新版本中修复的安全问题,而较新的/前沿/测试版可能引入了新的安全问题。
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 中的示例集。
预防措施
可以通过定义的生命周期支持 API 版本。例如,当你发布 API 的V2 版本时,你可能会通知你的用户V1版本将达到生命周期终止 (EoL) 并在未来的特定日期被弃用。预生产/测试版只有在经过彻底的安全问题测试后才能公开访问。
8. 限速
大多数情况下,API 对用户可以请求它们的次数没有任何保护,这被称为“缺乏速率限制”,当攻击者可以调用 API 数千次以导致一些意外行为时,就会发生这种情况。服务器将尝试满足这些请求中的每一个,这可能会:
· 通过使用请求重载服务器来对服务器进行 DOS 处理
· 允许攻击者快速泄露敏感的用户信息,例如:用户 ID、用户名、电子邮件等。
· 通过强制登录 API 绕过身份验证
· 通过强制向受害者发送电子邮件/短信的功能来淹没受害者的收件箱
让我们看一个攻击场景,其中对检查凭据的 API 端点没有速率限制:
GET/api/v1/user/1234/login/?password=mypassword
通常,你不会看到这样在 GET 请求中发送的密码,但出于演示说明的目的,假设你看到了。为了在上面的端点中暴力破解密码,攻击者可以使用BurpSuite 的 Intruder工具。这个工具允许我们自定义不同类型的蛮力攻击,但在这个例子中,我们将提供一个简单的密码列表。
在几秒钟内,Intruder 将发出数百个 API 请求,对每个请求尝试不同的密码。
GET/api/v1/user/1234/login/?password=notmypassword1
GET/api/v1/user/1234/login/?password=notmypassword2
GET/api/v1/user/1234/login/?password=notmypassword3
.
.
GET/api/v1/user/1234/login/?password=correctpassword!
由于没有速率限制,服务器会响应所有请求,攻击者可以继续使用不同的密码进行暴力破解,直到找到正确的密码。
为了防止速率限制错误,应用程序应该对用户在特定时间范围内请求 API 的频率实施限制。设置的确切限制将取决于该 API 或端点的用例。
预防措施
速率限制可以通过许多不同的方式实现。每个帐户、每个 IP 地址、每个端点、整个 API 等。一些反向代理也可用于实现应用程序范围的节流,而无需额外开发。具体你使用怎样的实现将取决于特定应用程序的要求。
9. 竞争条件
竞争条件是在同一毫秒内向 API 发送两个或多个请求。当 API 没有处理这种竞争情况的机制时,可能会导致 API 以意外的方式处理请求。在易受攻击的电子商务应用程序上兑换折扣或促销码时,可能会出现竞争条件的潜在攻击场景。
POST/api/v1/discount
Target:www.ecommerce.com
Connection:close
{
"code","first10",
"amount":"10"
}
BurpSuite 有一个名为Turbo Intruder的扩展,它允许用户使用内置race.py脚本测试竞争条件。在 Turbo Intruder 中配置攻击后,攻击者可以向 API 发送多个并发请求以兑换此促销码。
POST/api/v1/discount 200OK
POST/api/v1/discount 200OK
POST/api/v1/discount 200OK
POST/api/v1/discount 404NotFound
POST/api/v1/discount 404NotFound
如果 API 在收到第一个并发请求后没有立即使促销码失效,则折扣金额可能会增加一倍、三倍等。
这种攻击被称为“竞争条件”,因为它是攻击者发送修改资源请求的速度与应用程序更新该特定资源的速度之间的竞争。
预防措施
不幸的是,缓解竞争条件问题通常意味着牺牲性能。缓解竞争条件的方法包括利用锁和其他线程安全功能。大多数编程语言都内置了线程安全功能,通常需要手动设置指定。
10. XXE 攻击
XXE 代表XML 外部实体,这个注入漏洞可以在任何使用 API 处理 XML 数据的地方进行测试。SOAP API 也可能容易受到 XXE 注入的影响,因为它们基于 XML。
使用 XML 的 API 端点如下所示:
POST/soap/v2/user HTTP/1.1
Host:example.com
Content-Type:text/xml
<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE dtd[<!ENTITY username SYSTEM"https://example.com/?username">]>
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<getUser>
<id>&username;</id>
</getUser>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
XML 文档使用实体来表示单个数据对象,而 XML 文档类型定义 (DTD) 用于定义要使用的实体、文档的结构等。
XXE 注入是指攻击者注入指定 DTD 之外的自定义外部实体。一旦这些外部实体被 API 解析,攻击者就可以访问应用程序的内部文件,升级到 SSRF,将敏感数据泄漏到攻击者控制的域或 DOS 服务器。
这是一个请求示例,其中攻击者注入了一个名为 xxe 的外部自定义实体,该实体的目的是检索内部文件:
POST/soap/v2/user HTTP/1.1
Host:example.com
Content-Type:text/xml
<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE aa
[<!ENTITY xxe SYSTEM"file:///etc/passwd">]>
<SOAP-ENV:Envelope>
<SOAP-ENV:Body>
<getUser>
<id>&xxe;</id>
</getUser>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
如果 API 允许使用标准 XML 解析器来处理数据,那么这个注入的外部实体将被应用程序处理并将其内容返回/etc/passwd给攻击者。
预防措施
确保使用的 XML 解析器设置为 不解析 XML 实体。
11. 切换内容类型
即使 API使用 JSON 作为数据格式进行通信,底层服务器/框架仍可能接受其他数据格式,如 XML。因此,当你看到带有content-Typeof的 API 时application/json,你仍然可以尝试修改Content-Type: text/xml来测试 XXE
例如,如果 API 使用 JSON:
POST/api/v1/user HTTP/1.1
Host:example.com
Content-Type:application/json
可以修改它以这种方式发送 XML 数据:
POST/soap/v2/user HTTP/1.1
Host:example.com
Content-Type:text/xml
<?xml version="1.0"encoding="UTF-8"?><!DOCTYPE
aa[<!ENTITY xxe SYSTEM"file:///etc/passwd">]>
预防措施
这类错误仅存在于可以接受多种格式的应用程序框架上,每种格式对应一种数据传输对象。一般来说,框架通常可以选择将哪些可用格式列入白名单。 需要重点指出的是,这在 2021 年非常罕见。
12. HTTP 方法
API 通常支持各种类型的 HTTP 方法,常见的有GET,POST,PATCH,DELETE和OPTIONS。如果一个GET请求在其中的应用程序需要一个POST请求点发送,那么这可能会导致应用程序以意想不到的响应。
我们以CSRF为例。CSRF(跨站点请求伪造)是指攻击者诱使受害者提交请求,从而导致受害者帐户发生状态更改操作。这些更改可以是更改受害者的个人详细信息,在某些情况下甚至可以更改受害者的密码,从而导致完全接管帐户。更改受害者个人详细信息的正常请求可能如下所示:
POST/api/v1/user
Target:www.example.com
Content-Type:application/json
{
"name","victim",
"email":"[email protected]",
"location":"AU",
"csrftoken":"76hhs683bki0"
}
观察上述请求后,我们可能会得出结论,CSRF 攻击是不可能的,因为该csrftoken值是在请求正文中发送的。但是,如果 API 不限制可用于此请求的 HTTP 方法,则攻击者可能会通过使用 GET 方法发送此请求来绕过此保护。
GET/api/v1/user?name=hacked&[email protected]
m&location=FR
Target:www.example.com
Connection:close
此外,有时服务器根本不验证 CSRF 令牌,或者如果请求中根本不存在 CSRF 令牌参数,则接受请求。
REST API 中的另一个常见漏洞是在端点上正确应用了权限,但仅适用于一个 HTTP 动词。例如,你可能无法 GET 其他人的记录,但你可以使用 PATCH 动词来编辑他们的记录。
预防措施
在路由中手动指定 HTTP 动词。禁用不必要地使用动词,并确保指定的任何端点确实对所有动词应用了相同的控制。某些框架会自动执行此操作,而其他框架则不会。
13. 注入漏洞
服务器端注入缺陷类问题,如 SQL 注入、RCE、命令注入和SSRF,可以像在常规 Web 应用程序中一样存在于 API 中。每当任何注入的数据直接传递给后端的解释器时,就会为攻击者发送有针对性的查询和命令以访问内部数据或执行任意代码留下空间。
例如,让我们在以下 API 请求上测试 SSRF:
POST/api/v1/user
Target:www.example.com
Content-Type:application/json
{
"name","victim",
"email":"[email protected]",
"profile_pic_url":"https://www.example.com/me.jpg"
}
攻击者可以通过profile_pic_url发送如下请求在现场尝试 SSRF :
POST/api/v1/user
Target:www.example.com
Content-Type:application/json
{
"name","victim",
"email":"[email protected]",
"profile_pic_url":"http://localhost/admin"
}
如果后端解释器没有profile_pic_url正确验证该值,那么这可能导致攻击者利用 SSRF 并成功访问内部数据。
同样,如果此请求中的数据未正确验证,攻击者也可以尝试 SQL 注入:
POST/api/v1/orders
Target:www.example.com
Content-Type:application/json
{
"offset":0,
"limit":10,
"scope":"orders_all",
}
通过查看请求正文,我们可能会提示这些参数的值正在发送到后端数据库解释器。因此,我们可以通过将这些值修改为目标查询来尝试 SQL 注入:
POST/api/v1/orders
Target:www.example.com
Content-Type:application/json
{
"offset":0,
"limit":10,
"scope":"SELECT sleep(10)",
}
再一次,如果解释器没有正确验证这些值,那么这可能会导致 SQL 注入,攻击者可能会导致数据库中的时间延迟,甚至泄露敏感数据。
预防措施
缓解 API 中的注入漏洞本质上与缓解 Web 应用程序中的注入漏洞相同。SAST/DAST 扫描器可以帮助解决这个问题,但安全开发实践是最好的缓解措施。预编译参数化 SQL 查询,尽可能使用 ORM 和信誉良好的库, 不要相信用户输入!