错误的对象级授权
API 通常公开用于访问资源的对象。当这些端点上没有正确实施访问控制时,攻击者可以查看或操作他们不应该访问的资源。此漏洞影响所有类型的 API 架构,包括 SOAP、REST 和 GraphQL。
让我们看一个例子!假设 API 允许用户根据其用户 ID 检索有关其付款方式的详细信息:
https://api.example.com/v1.1/users/payment/show?user_id=12
在这里,如果应用程序不需要在 API 调用中提供额外的身份证明,而只是将请求的数据返回给任何人,那么应用程序就会将敏感信息暴露给攻击者。攻击者可以猜测、泄露或暴力破解受害者的 ID,并通过 API 端点窃取他们的支付信息。
错误的用户身份验证
API 很难进行身份验证。通常,在 API 调用期间提示输入用户凭据或使用多因素身份验证是不可行的。因此,API 系统中的身份验证通常使用访问令牌来实现:令牌嵌入到各个 API 调用中以对用户进行身份验证。如果身份验证没有正确实施,攻击者可以利用这些错误配置来伪装其他人。
无需身份验证的 API
首先,API 可能完全没有身份验证机制。有时,API 开发人员假设 API 端点只会被授权的应用程序访问,而不会被其他任何人发现。因此,任何知道其端点和查询结构的人都可以使用该 API。在这种情况下,任何人都可以通过 API 自由地请求数据或执行操作,只要他们能够弄清楚其查询结构。
身份验证的错误实现
大多数情况下,错误的用户身份验证是由错误的访问令牌设计或实现引起的。
一个常见的错误是没有正确生成访问令牌。首先,如果令牌短、简单或可预测,攻击者可能能够暴力破解令牌。当令牌生成时熵不足或使用弱加密或散列算法从用户信息中导出时,就会发生这种情况。例如,以下 API 令牌有什么问题?
access_token=dmlja2llbGk=
令牌只是用户名“vickieli”的 base64 编码!
长寿命令牌
即使正确生成令牌,不当的令牌失效也会造成麻烦。在许多 API 实现中,长寿命令牌是一个巨大的安全问题。
API 令牌应定期过期,并在退出、更改密码、帐户注销和帐户删除等敏感操作后过期。如果访问令牌没有正确失效,攻击者可以在窃取令牌后无限期地保持对系统的访问。
令牌泄漏
有时,开发人员会不安全地传输访问令牌,例如在 URL 中或通过未加密的流量。
如果令牌通过 URL 传输,任何通过浏览器扩展或浏览历史访问 URL 的人都可以窃取令牌。
https://api.example.com/v1.1/users/payment/show?user_id=12&access_token=360f91d065e56a15a0d9a0b4e170967b
如果令牌通过未加密的流量传输,攻击者可以发起中间人 (MITM) 攻击来拦截受害者的流量并窃取 API 令牌。
过度数据暴露
让我们考虑一个简单的 API 用例。Web 应用程序使用 API 服务检索信息,然后使用该信息填充网页以显示给用户的浏览器。
对于许多 API 服务,API 客户端应用程序无法挑选和选择 API 调用中返回哪些数据字段。假设应用程序从 API 检索用户信息以填充用户配置文件。检索用户信息的 API 调用如下所示:
https://api.example.com/v1.1/users/show?user_id=12
API 服务器将响应整个相应的用户对象:
{
"id": 6253282,
"username": "vickieli7",
"screen_name": "Vickie",
"location": "San Francisco, CA",
"bio": "Infosec nerd. Hacks and secures. Creates god awful infographics.",
"api_token": "8a48c14b04d94d81ca484e8f32daf6dc",
"phone_number": "123-456-7890",
"address": "1 Main St, San Francisco, CA, USA"
}
你会注意到,除了有关用户的基本信息之外,此 API 调用还会返回该用户的 API 令牌、电话号码和地址。由于此调用用于检索数据以填充用户的个人资料页面,因此应用程序只需将用户名、展示名称、位置和个人简介发送到浏览器。
一些应用程序开发人员认为,如果他们不在网页上显示敏感信息,用户就看不到它。因此,他们依次将整个 API 响应发送到用户的浏览器,而无需先过滤掉敏感信息,而是依靠客户端代码过滤掉隐私信息。发生这种情况时,任何访问个人资料页面的人都将能够拦截此 API 响应并读取有关该用户的敏感信息!
攻击者还可能通过访问某些泄露信息的端点或执行 MITM 攻击来窃取发送给受害者的 API 响应来读取敏感数据。
缺乏资源和速率限制
缺乏资源和速率限制是指 API 不限制来自特定 API 客户端的请求数量或频率。所以一个 API 客户端每秒可以进行数千甚至更多的 API 调用,或者一次请求成百上千条数据记录,服务器仍然会尝试满足这些请求。
缺乏速率限制会影响 API 服务器的性能,并允许攻击者发起 DoS 攻击。当单个客户端或多个客户端同时发出过多请求时,来自这些客户端的请求可能会压垮服务器处理请求的能力,进而导致其他用户无法使用该服务或服务变慢。
另一个问题是,缺乏速率限制会导致对身份验证端点和具有错误对象级别授权的端点进行暴力攻击。例如,如果用户可以提交登录请求的次数没有限制,恶意攻击者就可以通过尝试使用不同的密码登录来暴力破解用户的密码,直到成功为止。另一方面,如果应用程序遭受错误对象级别授权,攻击者可以使用非速率限制端点来暴力破解指向敏感数据的 ID。
错误的功能级授权
功能级授权失效是指应用程序未能将敏感功能限制给授权用户。
例如,当一个用户可以修改另一个用户的帐户或普通用户可以访问站点上的管理功能时。这些问题是由缺少或错误配置的访问控制引起的。它们可以通过多种方式表现出来,所以今天让我们看几个例子。
示例1:删除别人的帖子
假设一个 API 允许其用户通过发送一个 GET 请求来检索博客文章,如下所示:
GET /api/v1.1/user/12358/posts?id=32
此请求将导致 API 返回用户 12358 的帖子 32。由于此平台上的所有帖子都是公开的,因此任何用户都可以提交此请求以访问其他人的帖子。但是,由于只有用户自己应该修改博客帖子,因此只有用户 12358 可以提交 POST 请求来修改或编辑帖子。
如果 API 没有对使用 PUT 和 DELETE HTTP 方法发送的请求设置相同的限制怎么办?在这种情况下,恶意用户可能会使用不同的 HTTP 方法修改或删除其他用户的帖子。此请求删除另一个用户的帖子。
DELETE /api/v1.1/user/12358/posts?id=32
示例2:假装是管理员
该网站还允许平台管理员修改或删除任何人的帖子。因此,如果从管理员帐户发送这些请求,这些请求都会成功。
DELETE /api/v1.1/user/12358/posts?id=32
POST /api/v1.1/user/12358/posts?id=32
PUT /api/v1.1/user/12358/posts?id= 32
但是该站点通过请求中的特殊头部确定谁是管理员:
Admin: 1
在这种情况下,任何恶意用户都可以简单地将此头部添加到他们的请求中并获得对这个特殊管理功能的访问权限!访问控制缺失和访问控制执行不当都可能导致功能级授权失效。
批量赋值
“批量赋值”是指一次为多个变量或对象属性赋值的做法。但是这个功能怎么会导致安全漏洞呢?让我们通过查看一个示例对象来进行探索。
对象属性
应用程序对象通常具有许多描述对象的属性。例如,假设“用户”对象用于在你的应用程序中存储用户信息。它包含描述用户的属性,如用户 ID、姓名、位置等。
{
"id": 12345,
"name": "Vickie",
"location": "San Francisco, CA",
"admin": false,
"group_membership": [121, 322, 457]
}
在这种情况下,用户应该能够修改存储在其对象中的某些属性,例如位置和名称属性。但是这个用户对象的其他部分应该对用户进行限制,例如“admin”属性,表示用户是否是管理员,以及“group_membership”属性,记录用户属于哪些用户组。
批量赋值
当应用程序自动将用户输入分配给多个程序变量或对象时,就会发生批量赋值漏洞。这是许多旨在简化应用程序开发的应用程序框架中的功能。
但此功能有时允许攻击者随意覆盖、修改或创建新的程序变量或对象属性。例如,假设该站点允许用户通过像这样的 PUT 请求更改他们的姓名。此请求会将用户 12345 的名称从“Vickie”更新为“Vickie Li”。
PUT /api/v1.1/user/12345
{
"name": "Vickie Li"
}
现在,如果恶意用户提交了这个请求呢?
PUT /api/v1.1/user/12345
{
"name": "Vickie Li",
"admin": true
}
日志记录和监控不足
在攻击者获得对你系统的初始访问权限后,他们将开始利用最初的立足点来探索和利用网络。他们可能会寻找更多可以在机器上利用的漏洞,执行侦察以发现系统上的其他机器,利用他们在系统上发现的新漏洞,建立对系统的持久访问,或通过网络移动以从中窃取数据等等。
网络攻击不会在几小时甚至几天内发生。攻击者通常需要时间来探索网络并构建合适的策略来充分利用系统并窃取其中包含的数据。攻击者在不被发现的情况下访问系统的时间越长,攻击者就越有可能找到利用系统、窃取数据并对应用程序及其用户造成广泛损害的方法。这就是为什么拥有能够尽快检测恶意活动的日志记录和监控系统至关重要的原因。
许多组织只进行基础设施日志记录,如记录网络事件和服务器登录,而缺乏 API 或特定于应用程序的监控基础设施。你需要一个 API 日志系统来负责监控 API 的异常使用情况。你可以记录事件,例如输入验证失败、身份验证和授权成功与失败、应用程序错误以及处理敏感功能(如支付、账户设置等)的任何其他 API 事件。随着时间的推移,你将能够了解 API 的正常使用情况,并检测可能是攻击的可疑活动。
资产管理不当
尽管“资产管理不当”听起来很复杂,但本质上是这样的:不跟踪你的 API 端点。这可能是由于 API 文档不完整,或者完全缺乏 API 文档。但是为什么缺少 API 文档是一个问题?
一个 API 通常有许多不同的版本、功能、端点和许多影响该端点行为的参数。如果你不跟踪所有这些功能,你就不会意识到隐藏在未知端点中的安全漏洞。你无法确保你不知道的东西。
除了不完整或缺失的文档,不准确的文档也是一个安全问题。即使你有详细说明 API 端点的文档,它是否会告诉你每个端点的作用?是否存在文档中未记录的端点行为,例如接受备用 HTTP 方法?是否有任何未记录的参数会影响端点的功能?不准确的文档可能会让你认为端点是安全的,但实际上它的行为并不像你认为的那样。
例如,假设你在 API 端点上发现了敏感信息泄漏。但是你不知道具有相同信息泄漏漏洞的旧版本 API 也可供公众使用。所以你没有缓解旧版本中的漏洞,攻击者仍然可以通过旧 API 来利用该漏洞。
或者,你可能希望将某些敏感端点的访问权限限制为站点管理员。但是如果没有每个端点及其功能的详细记录,你就无法决定应该限制哪些端点。
安全配置错误
安全配置错误是对 API 和非 API 应用程序的持续威胁。
详细的错误消息
首先,最常见的安全配置错误之一是向用户发送详细的错误消息。这些详细的错误消息可能包含堆栈跟踪、有关系统的信息(例如服务器版本或底层数据库结构),并让用户深入了解应用程序的工作原理。在这种情况下,恶意用户可以强制应用程序发送错误消息(通过提供格式错误或非法的输入)来收集有关服务器的信息。
错误配置的 HTTP 头部
另一种常见的配置是滥用或丢失 HTTP 头部。有许多 HTTP 安全头部有助于增强应用程序的安全性。如果它们没有正确配置,攻击者通常可以找到安全漏洞,允许他们窃取数据,或对应用程序的用户执行常见的 Web 攻击。
例如,Content-Security-Policy (CSP) 头部控制允许浏览器为页面加载哪些资源。它应该设置为禁止来自随机域的脚本、内联脚本和事件处理 HTML 属性,以防止 XSS(跨站点脚本)攻击。
CORS(跨源资源共享)配置错误也是 HTTP 头部配置错误引起的问题。跨域资源共享 (CORS) 是放松同源策略 (SOP) 的一种安全方式。它允许服务器通过 Access-Control-Allow-Origin 头部明确指定允许访问其资源的源列表。Access-Control-Allow-Origin 应配置为仅允许来自受信任站点的跨域通信。错误配置的 CORS 策略允许攻击者通过干扰跨域通信来窃取机密数据。
不安全的默认配置
许多第三方依赖项(如数据库和 Web 框架)在默认情况下是不安全的,需要开发人员通过自定义配置来加强安全性。例如,旧版本的 MongoDB 可以通过 Internet 访问,并且默认情况下不需要身份验证。如果开发人员不更改默认配置,这将导致数据库暴露给公众。
注入
注入是SQL注入、OS命令注入、XML注入等大量漏洞的底层问题。总之,注入占现实世界应用程序和 API 中发现的漏洞的很大一部分。
注入是如何发生的
简而言之,当应用程序无法正确区分不受信任的用户数据和代码时,就会发生注入。
不受信任的用户数据可以是 HTTP 请求参数、HTTP 头部和 cookie。它们也可以来自用户可以修改的数据库或存储的文件。如果应用程序在将不受信任的用户数据插入命令或查询之前没有正确处理它,程序的解释器会将用户输入混淆为命令或查询的一部分。在这种情况下,攻击者可以通过改变其命令含义的方式向应用程序发送数据。
例如,在 SQL 注入攻击中,攻击者注入数据来操纵 SQL 命令。在命令注入攻击中,攻击者注入数据来操纵托管服务器上的操作系统命令的逻辑。任何将用户数据与编程命令或代码相结合的程序都有潜在的漏洞。
注入漏洞也会影响 API 系统,因为 API 只是不受信任的用户输入可以进入应用程序的另一种方式。让我们来看看注入漏洞是如何出现在 API 中的。
示例 1:检索博客文章
假设一个 API 允许其用户通过发送这样的 GET 请求来检索博客文章:
GET /api/v1.1/posts?id=12358
这个请求会导致API返回 post 12358。服务器会通过SQL查询从数据库中检索对应的博文,其中 post_id是用户通过URL传入的。
SELECT * FROM posts WHERE post_id = 12358
现在,如果用户从 API 端点请求这个呢?
GET /api/v1.1/posts?id=12358; DROP TABLE users
SQL 服务器会将 id 分号后面的部分解释为单独的 SQL 命令。所以SQL引擎会像往常一样首先执行这个命令来检索博客文章:
SELECT * FROM posts WHERE post_id = 12358;
然后,它会执行该命令删除该 users 表,导致应用程序丢失该表中存储的数据。
示例2:读取系统文件
假设该站点允许用户读取他们通过 API 端点上传的文件:
GET /api/v1.1/files?id=1123581321
此请求将导致服务器通过系统命令检索用户的文件:
cat /var/www/html/users/tmp/1123581321
在这种情况下,用户可以通过在分号后添加其他命令将新命令注入操作系统系统命令。
GET /api/v1.1/files?id=1123581321; rm -rf /var/www/html/users
此命令将强制服务器删除文件夹 /var/www/html/users,该文件夹是应用程序存储用户信息的位置。
针对上述安全问题,具体的防范措施参考: