跳到内容

REST 安全速查表

简介

REST(即表征性状态传输)是一种架构风格,最早由Roy Fielding在他的博士学位论文《架构风格与基于网络的软件架构设计》中描述。

它随着 Fielding 编写 HTTP/1.1 和 URI 规范而演变,并已被证明非常适合开发分布式超媒体应用程序。尽管 REST 适用范围更广,但它最常用于通过 HTTP 与服务通信的场景。

REST 中信息的核心抽象是资源。REST API 资源通过 URI(通常是 HTTP URL)标识。REST 组件使用连接器,通过使用表征来捕获资源的当前或预期状态并传输该表征,从而对资源执行操作。

主要连接器类型是客户端和服务器,次要连接器包括缓存、解析器和隧道。

REST API 是无状态的。有状态的 API 不符合 REST 架构风格。REST 首字母缩略词中的“状态”指的是 API 访问的资源状态,而不是调用 API 的会话状态。尽管构建有状态 API 可能有充分的理由,但重要的是要认识到会话管理复杂且难以安全地实现。

有状态服务不在本速查表的范围内:将状态从客户端传递到后端,同时使服务在技术上无状态,这是一种反模式,也应避免,因为它容易受到重放和模拟攻击。

为了使用 REST API 实现流程,资源通常被创建、读取、更新和删除。例如,电子商务网站可能提供创建空购物车、向购物车添加商品和结账购物车的方法。这些 REST 调用都是无状态的,并且端点应检查调用者是否被授权执行所请求的操作。

REST 应用程序的另一个关键特性是使用标准的 HTTP 动词和错误代码,旨在消除不同服务之间不必要的差异。

REST 应用程序的另一个关键特性是使用HATEOAS 或超媒体作为应用状态引擎。这使得 REST 应用程序具有自文档化的特性,使开发人员无需事先了解即可更轻松地与 REST 服务交互。

HTTPS

安全的 REST 服务必须只提供 HTTPS 端点。这保护了传输中的认证凭据,例如密码、API 密钥或 JSON Web Tokens。它还允许客户端认证服务并保证传输数据的完整性。

更多信息请参阅传输层安全速查表

考虑使用相互认证的客户端证书,为高权限 Web 服务提供额外保护。

访问控制

非公共 REST 服务必须在每个 API 端点执行访问控制。单体应用中的 Web 服务通过用户认证、授权逻辑和会话管理来实现这一点。这对于由多个遵循 RESTful 风格的微服务组成的现代架构存在几个缺点。

  • 为了最小化延迟并减少服务之间的耦合,访问控制决策应由 REST 端点本地执行
  • 用户认证应集中在身份提供商 (IdP) 中,该身份提供商颁发访问令牌

JWT

安全令牌的格式似乎趋向于使用JSON Web Tokens (JWT)。JWT 是包含一组可用于访问控制决策的声明的 JSON 数据结构。可以使用加密签名或消息认证码 (MAC) 来保护 JWT 的完整性。

  • 确保 JWT 通过签名或 MAC 保护其完整性。不要允许不安全的 JWT:{"alg":"none"}
  • 通常,对于 JWT 的完整性保护,应优先选择签名而不是 MAC。

如果使用 MAC 进行完整性保护,每个能够验证 JWT 的服务也可以使用相同的密钥创建新的 JWT。这意味着所有使用相同密钥的服务必须相互信任。另一个后果是,任何服务的泄露也会危及所有其他共享相同密钥的服务。更多信息请参见此处

依赖方或令牌消费者通过验证其完整性和所包含的声明来验证 JWT。

  • 依赖方必须根据自己的配置或硬编码逻辑来验证 JWT 的完整性。它不能依赖 JWT 头部的信息来选择验证算法。参见此处此处

一些声明已经标准化,并应存在于用于访问控制的 JWT 中。至少应验证以下标准声明

  • iss 或颁发者 - 这是可信的颁发者吗?它是签名密钥的预期所有者吗?
  • aud 或受众 - 依赖方是否是此 JWT 的目标受众?
  • exp 或过期时间 - 当前时间是否在此令牌有效期结束之前?
  • nbf 或不早于时间 - 当前时间是否在此令牌有效期开始之后?

由于 JWT 包含认证实体(用户等)的详细信息,JWT 与用户会话的当前状态之间可能会出现断开,例如,如果会话由于明确注销或空闲超时而早于过期时间终止。当发生明确的会话终止事件时,任何相关 JWT 的摘要或哈希应提交到 API 上的拒绝列表,这将使该 JWT 在令牌过期之前对任何请求都无效。更多详情请参见Java JSON Web Token 速查表

API 密钥

没有访问控制的公共 REST 服务面临被滥用的风险,导致带宽或计算周期的费用过高。API 密钥可用于减轻此风险。它们也常被组织用于 API 变现;客户端可以根据购买的访问计划获得访问权限,而不是阻止高频调用。

API 密钥可以减少拒绝服务攻击的影响。然而,当它们颁发给第三方客户端时,它们相对容易被泄露。

  • 要求对受保护端点的每个请求都提供 API 密钥。
  • 如果请求过快,返回 HTTP 响应码 429 Too Many Requests
  • 如果客户端违反使用协议,则吊销 API 密钥。
  • 不要仅仅依赖 API 密钥来保护敏感、关键或高价值资源。

限制 HTTP 方法

  • 应用允许的 HTTP 方法白名单,例如 GETPOSTPUT
  • 拒绝所有与白名单不匹配的请求,并返回 HTTP 响应码 405 Method not allowed
  • 确保调用者被授权对资源集合、操作和记录使用传入的 HTTP 方法

特别是在 Java EE 中,这可能难以正确实现。有关此常见错误配置的解释,请参见通过 HTTP 动词篡改绕过 Web 认证和授权

输入验证

  • 不要信任输入参数/对象。
  • 验证输入:长度 / 范围 / 格式和类型。
  • 通过使用强类型(例如在 API 参数中使用数字、布尔值、日期、时间或固定数据范围)实现隐式输入验证。
  • 使用正则表达式约束字符串输入。
  • 拒绝意外/非法内容。
  • 在您所使用的特定语言中利用验证/净化库或框架。
  • 定义适当的请求大小限制,并拒绝超过限制的请求,返回 HTTP 响应状态码 413 Request Entity Too Large。
  • 考虑记录输入验证失败。假设每秒执行数百次输入验证失败的人意图不轨。
  • 请查阅输入验证速查表以获得全面解释。
  • 使用安全的解析器来解析传入消息。如果您使用 XML,请确保使用不容易受到XXE 和类似攻击的解析器。

验证内容类型

REST 请求或响应体应与头部中预期的内容类型匹配。否则,这可能导致消费者/生产者端的误解,并导致代码注入/执行。

  • 在您的 API 中记录所有支持的内容类型。

验证请求内容类型

  • 拒绝包含意外或缺失内容类型头部的请求,并返回 HTTP 响应状态码 406 Unacceptable415 Unsupported Media Type。然而,对于 Content-Length: 0 的请求,Content-type 头部是可选的。
  • 对于 XML 内容类型,确保适当的 XML 解析器加固,参见XXE 速查表
  • 通过显式定义内容类型来避免意外暴露非预期内容类型,例如 Jersey (Java) 中的 @consumes("application/json"); @produces("application/json")。例如,这可以避免XXE 攻击向量。

发送安全的响应内容类型

REST 服务通常允许多种响应类型(例如 application/xmlapplication/json),客户端通过请求中的 Accept 头部指定响应类型的首选顺序。

  • 切勿简单地将 Accept 头部复制到响应的 Content-type 头部。
  • 如果 Accept 头部没有明确包含允许的类型之一,则拒绝请求(理想情况下返回 406 Not Acceptable 响应)。

在响应中包含脚本代码(例如 JavaScript)的服务必须特别小心防范头部注入攻击。

  • 确保在响应中发送与您的正文内容匹配的预期内容类型头部,例如 application/json 而不是 application/javascript

管理端点

  • 避免通过互联网暴露管理端点。
  • 如果管理端点必须通过互联网访问,请确保用户必须使用强认证机制,例如多因素认证。
  • 通过不同的 HTTP 端口或主机暴露管理端点,最好在不同的网卡和受限子网上。
  • 通过防火墙规则或使用访问控制列表限制对这些端点的访问。

错误处理

  • 返回通用错误消息 - 避免不必要地泄露故障细节。
  • 不要向客户端传递技术细节(例如调用堆栈或其他内部提示)。

审计日志

  • 在安全相关事件之前和之后写入审计日志。
  • 考虑记录令牌验证错误以检测攻击。
  • 通过事先净化日志数据来防范日志注入攻击。

安全头部

HTTP 响应中可以返回一些安全相关头部,以指示浏览器以特定方式行动。然而,其中一些头部旨在与 HTML 响应一起使用,因此,对于不返回 HTML 的 API 而言,它们可能提供很少或没有安全益处。

以下头部应包含在所有 API 响应中

头部 理由
Cache-Control: no-store 用于指示浏览器缓存的头部。提供 no-store 表示任何类型的缓存(私有或共享)都不应存储包含该头部的响应。每次调用 API 时,浏览器都必须发出新请求以获取最新响应。带有 no-store 值的此头部可防止敏感信息被缓存或存储。
Content-Security-Policy: frame-ancestors 'none' 用于指定响应是否可以在 <frame><iframe><embed><object> 元素中被框架化的头部。对于 API 响应,没有必要在这些元素中的任何一个中被框架化。提供 frame-ancestors 'none' 可防止任何域框架化 API 调用返回的响应。此头部可防止拖放式点击劫持攻击
Content-Type 用于指定响应内容类型的头部。这必须根据 API 调用返回的内容类型来指定。如果未指定或指定不正确,浏览器可能会尝试猜测响应的内容类型。这可能导致 MIME 嗅探攻击。如果 API 响应是 JSON,一个常见的内容类型值是 application/json
Strict-Transport-Security 用于指示浏览器该域只能通过 HTTPS 访问,并且将来任何使用 HTTP 访问它的尝试都应自动转换为 HTTPS 的头部。此头部确保 API 调用通过 HTTPS 进行,并防止伪造证书。
X-Content-Type-Options: nosniff 用于指示浏览器始终使用 Content-Type 头部中声明的 MIME 类型,而不是尝试根据文件内容确定 MIME 类型。带有 nosniff 值的此头部可防止浏览器执行 MIME 嗅探,并防止不恰当地将响应解释为 HTML。
X-Frame-Options: DENY 用于指定响应是否可以在 <frame><iframe><embed><object> 元素中被框架化的头部。对于 API 响应,没有必要在这些元素中的任何一个中被框架化。提供 DENY 可防止任何域框架化 API 调用返回的响应。带有 DENY 值的此头部可防止拖放式点击劫持攻击

以下头部仅旨在在响应以 HTML 形式呈现时提供额外安全性。因此,如果 API 永远不会在响应中返回 HTML,则这些头部可能不是必需的。然而,如果对这些头部的功能或 API 返回(或将来可能返回)的信息类型有任何不确定性,则建议将它们作为纵深防御方法的一部分包含在内。

头部 示例 理由
内容安全策略 Content-Security-Policy: default-src 'none' 大多数 CSP 功能只影响呈现为 HTML 的页面。
Permissions-Policy Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=() 此头部以前名为 Feature-Policy。当浏览器遵守此头部时,它用于通过指令控制浏览器功能。该示例禁用了一些允许的指令名称的功能,并带有空白允许列表。当您应用此头部时,请验证指令是否最新且符合您的需求。请查阅这篇文章,了解如何控制浏览器功能的详细解释。
Referrer-Policy Referrer-Policy: no-referrer 非 HTML 响应不应触发额外请求。

CORS

跨域资源共享 (CORS) 是一项 W3C 标准,用于灵活指定允许哪些跨域请求。通过提供适当的 CORS 头部,您的 REST API 会向浏览器发出信号,指示哪些域(即源)被允许向 REST 服务进行 JavaScript 调用。

  • 如果不支持/不预期跨域调用,则禁用 CORS 头部。
  • 在设置跨域调用的源时,要尽可能具体,必要时尽可能通用。

HTTP 请求中的敏感信息

RESTful Web 服务应小心防止凭据泄露。密码、安全令牌和 API 密钥不应出现在 URL 中,因为这可能会被 Web 服务器日志捕获,从而使其具有内在价值。

  • POST/PUT 请求中,敏感数据应在请求体或请求头部中传输。
  • GET 请求中,敏感数据应在 HTTP 头部中传输。

正确

https://example.com/resourceCollection/[ID]/action

https://twitter.com/vanderaj/lists

错误

https://example.com/controller/123/action?apiKey=a53f435643de32 因为 apiKey 在 URL 中。

HTTP 状态码

HTTP 定义了状态码。在设计 REST API 时,不要仅仅使用 200 表示成功或 404 表示错误。始终为响应使用语义上适当的状态码。

以下是非详尽的安全相关 REST API 状态码选择。使用它以确保您返回正确的代码。

代码 消息 描述
200 正确 对成功的 REST API 操作的响应。HTTP 方法可以是 GET、POST、PUT、PATCH 或 DELETE。
201 已创建 请求已完成,资源已创建。创建资源的 URI 在 Location 头部中返回。
202 已接受 请求已接受处理,但处理尚未完成。
301 永久移动 永久重定向。
304 未修改 当客户端拥有与服务器相同的资源副本时返回的缓存相关响应。
307 临时重定向 资源的临时重定向。
400 错误请求 请求格式错误,例如消息体格式错误。
401 未经授权 提供了错误或未提供认证 ID/密码。
403 禁止 当认证成功但认证用户无权访问请求资源时使用。
404 未找到 请求不存在的资源时。
405 方法不允许 针对意外 HTTP 方法的错误。例如,REST API 预期 HTTP GET,但使用了 HTTP PUT。
406 不可接受 客户端在 Accept 头部中提供了服务器 API 不支持的内容类型。
413 请求实体过大 用它来表示请求大小超出了给定限制,例如文件上传。
415 不支持的媒体类型 REST 服务不支持所请求的内容类型。
429 请求过多 当检测到可能的 DOS 攻击或请求因速率限制而被拒绝时使用此错误。
500 内部服务器错误 意外情况阻止服务器完成请求。请注意,响应不应泄露有助于攻击者的内部信息,例如详细的错误消息或堆栈跟踪。
501 未实现 REST 服务尚未实现所请求的操作。
503 服务不可用 REST 服务暂时无法处理请求。用于通知客户端稍后重试。

有关 REST API 中 HTTP 状态码使用的更多信息,请参见此处此处