# API 主机别名能绕过你的目标审核吗？

只有当目标审核检查的是实际接收凭据的名称时，它才有价值。如果团队批准了 `https://api.example.test`，却仍让代理可以使用 `https://api-us.example.test`、`https://gateway.example.test` 或供应商提供的兼容性端点，那么这项批准描述的只是意图，而不是边界。

我见过最平淡不过的失败方式：有人批准了一个熟悉的主机名，部署变量却指向区域别名，同一枚 Bearer 令牌照样可用。根本不需要戏剧化的攻击。团队拥有的目标名称比审核流程承认的更多，仅此而已。

首先要纠正的是概念。CNAME 记录、备用基础 URL、重定向、IP 地址和 HTTP 权威彼此相关，但不能混为一谈。把它们当成同一回事，会得到看似严谨的审核，以及会泄露凭据的控制措施。

## 目标审核只覆盖字面上的主机

审核 `api.example.test` 并不等于批准 `api-eu.example.test`、`proxy.example.test` 或 `api.example.test.evil.invalid`。凭据只能发送到该凭据明确清单中出现、经过规范化处理的精确主机名。

团队常常从一条非正式规则开始，比如“这枚令牌用于 Example 的 API”。这说的是归属，不是目标规则。一家公司可以运营许多域名，供应商可以在多个名称背后迁移流量，第三方也可能承载供应商边缘网络的一部分。HTTP 客户端需要具体的 URL，你的控制措施也一样。

请用 URL 解析器能比较的方式写下边界：

- 协议：通常是 `https`
- 主机名：经过 IDNA 处理后的小写 ASCII 主机名
- 端口：显式端口，或该协议的默认端口
- 路径策略：仅当凭据只限用于特定 API 范围时

不要用后缀测试替代主机名列表。`endsWith("example.test")` 会接受 `notexample.test`。`endsWith(".example.test")` 仍会接受现在和未来的所有子域名。对于所有权单一、签发控制很强的内部服务网格，这或许可以接受。对于能够修改生产数据的凭据，这通常只是偷懒。

棘手的问题是，是否必须在使用每个主机名前由人工批准。对于高权限令牌，答案是肯定的。对于面向发布了大量区域端点的供应商、且权限范围较小的令牌，应批准一份持续维护的列表，并把新增名称作为有意识的变更处理。多一次审核的成本，小于解释为什么令牌到了一个无人记录的主机名的成本。

这也把目标控制和请求内容审核区分开来。审核者可能接受向已批准主机发送 `GET`，却拒绝会更改账单的 `POST`。这是两个独立问题。不要声称主机名检查能决定请求是否安全，它只决定凭据可以发往哪里。

## CNAME 改变路由，不改变 HTTP 主机

CNAME 会改变 DNS 解析，但它本身不会改写 URL 中的主机名、HTTP `Host` 标头，或常规 HTTPS 客户端发送的 TLS 服务器名称指示。

假设代理调用这个 URL：

```text
https://api.example.test/v1/orders
```

DNS 可能返回：

```text
api.example.test.  300  IN  CNAME  api.edge.vendor.test.
api.edge.vendor.test. 300 IN A      203.0.113.42
```

TCP 连接会到达 `203.0.113.42`，那里可能是供应商运营的基础设施。客户端仍应在 TLS 中请求 `api.example.test`，并发送 `Host: api.example.test`。如果服务器未提供对原始名称有效的证书，证书验证应当失败。如果它提供了这样的证书，服务器就被授权终止该名称的流量，至少在当时如此。

这个区别很重要，因为它否定了一种流行但错误的修复方式：把 CNAME 目标当作 API 权威来批准。目标名称是路由证据。它能帮助你了解流量去了哪里，但不能替代对接收授权标头的 URL 主机名进行审核。

RFC 1034 将 CNAME 描述为另一个域名的别名，并要求查询继续到规范名称。这解释的是解析器行为，不是 HTTP 凭据决策。DNS 不知道什么是 Bearer 令牌、API 权限范围或你的变更审批。

CNAME 会在四种实际情况下与安全相关：

1. DNS 名称归另一个团队或供应商所有，因此一项记录变更就能改变已批准权威终止连接的位置。
2. 解析出的地址进入意料之外的网络，例如内部网段或云元数据地址。
3. 控制措施根据 DNS 输出而不是 URL 权威来批准名称，让别名关系替代了真正的授权决策。
4. 应用程序根据解析出的名称、重定向目标或服务发现结果构造第二个 URL，然后转发凭据。

第四种情况会泄露令牌。前三种会削弱审核，并让未来发生泄露的可能性更高。它们需要不同的测试和不同的责任人。

CNAME 也无法保护你免受 DNS 重绑定。如果客户端允许的主机名后来解析到不同地址，客户端可能会向那个地址重新建立连接。对于你控制的目标，应监控记录并限制其可解析到的位置。对于你无法控制的目标，不要以为一次 DNS 查询就能证明它永久安全。

## 备用基础 URL 造成更隐蔽的绕过

备用基础 URL 更常见，因为它们会直接改变 HTTP 权威。它们往往藏在环境变量、SDK 默认值、测试夹具和迁移说明中。

一项服务可能出于正当理由记录了以下所有地址：

```text
https://api.example.test
https://api-us.example.test
https://sandbox-api.example.test
https://gateway.example.test/service-a
https://tenant-42.api.example.test
```

它们今天可能都终止在同一个边缘网络上，但这不表示它们该使用同一份凭据。生产端点可能接受账户级令牌，沙箱端点可能会将请求发送到独立系统，网关路径可能选择另一项服务，租户主机名则可能按客户身份路由。名称编码了 IP 比较掩盖的运行差异。

失败模式很容易预测。代码库为 `API_BASE_URL` 设置了生产默认值。开发者为了区域测试或迁移修改了它。凭据注入层看到一个看起来仍有关联的 URL，便添加了标头。目标审核附着在“Example API”这样的标签上，而不是精确权威，因此没人发现范围扩大了。

先修正数据模型，再修正代码。每个凭据都需要一条独立记录，其中包含四个审核者可检查的字段：

```text
credential: orders-write-prod
allowed authorities:
  https://api.example.test:443
  https://api-us.example.test:443
purpose: create and amend production orders
owner: commerce operations
review trigger: DNS change, new endpoint, scope change
```

这里特意使用“权威”一词。要将协议、主机和端口一起保存。某个主机名可以通过 HTTPS 使用，并不意味着它也自动获准通过非标准端口使用。共享网关用一台主机承载互不相关的 API 时，路径可能很重要，不过路径规则需要谨慎规范化，在权限范围不同时也不应代替独立凭据。

不要把供应商发布的列表当成完成后的清单。供应商文档告诉你可能存在什么。你的代码和部署设置告诉你能调用什么。两者都需要，还要包括迁移后仍留在旧自动化中的名称。

## 凭据的受众才是边界

正确的问题不是“哪些服务器属于这个供应商？”，而是“在这条确切的请求路径中，哪些 HTTP 权威可以接收这份确切的密钥？”

除非签发方强制限制，否则 Bearer 令牌没有内建的受众限制。一旦客户端把它放入 `Authorization` 标头，每个收到标头的接收方都可以尝试使用它。基本认证和自定义 API 密钥标头也面临同样的传输问题。传输加密保护请求在传输中的安全，却不会在应用层收窄受众范围。

OAuth 访问令牌有时包含 `aud` 声明。这有助于资源服务器拒绝原本发给其他对象的令牌，但不要把服务端拒绝和安全的客户端行为混淆。把令牌发送给错误的主机名，仍会让该主机名接触到令牌，并将其写入访问日志、遥测系统或事件处理队列。被拒绝的令牌好过被接受的令牌，但依然是本可避免的泄露。

凭据绑定包含两部分：

- 客户端仅针对经过审核的权威注入凭据。
- 凭据签发方为其设置实际可行的最小权限范围、受众和环境限制。

两者都需要。主机绑定能防止客户端失误把密钥散播到相邻服务。权限范围限制则能在预期主机、其日志或路由配置被攻破时减少损失。

这就是为什么全组织共用一枚 API 密钥是笔糟糕交易。它让配置轻松，却让事件响应难看。如果同一把密钥流向支付 API、分析收集器和预发布网关，你无法撤销某一目标的访问权限而不影响另外两个。独立凭据能把路由错误变成局部轮换，而不是全员参与的故障。

## 从代码、DNS 和供应商文档盘点名称

端点清单只有捕捉到实际使用，而不只是计划架构时，才值得信赖。应从代码、部署配置、DNS 和供应商文档建立清单，再核对其中的差异。

先在仓库中搜索 URL 协议和基础 URL 设置。包括应用代码、Shell 脚本、CI 定义、示例文件、基础设施定义和测试夹具。记录每一个主机名，即使它看起来已经过时。旧名称依然危险，因为定时任务或代理提示词可能仍在调用它们。

然后通过调用机器使用的同一解析器环境，解析每个候选名称。在 macOS 或其他 Unix 系统上，基础检查如下：

```sh
dig +noall +answer api.example.test CNAME A AAAA
```

一种有用的输出形式是：

```text
api.example.test.       300 IN CNAME api.edge.vendor.test.
api.edge.vendor.test.    60 IN A     203.0.113.42
api.edge.vendor.test.    60 IN AAAA  2001:db8::42
```

如果解析器不会在一次回答中返回完整链路，就分别查询 `CNAME`、`A` 和 `AAAA`。记录查询日期和所用解析器，因为分割 DNS 可能让笔记本、CI 运行器和生产主机得到不同答案。不要把得到的 IP 地址复制到互联网 API 的永久允许列表中。边缘网络会经常更换地址。应将其作为审核证据记录，并对异常变化发出告警。

接下来创建清单表，每个权威一行，而不是每个供应商一行。它应无需开会就能回答这些问题：

| 权威 | 使用方 | 凭据 | DNS 所有者 | 预期路由 | 审核状态 |
| --- | --- | --- | --- | --- | --- |
| `https://api.example.test:443` | 生产代理 | orders-write-prod | 供应商 | 公共边缘网络 | 已批准 |
| `https://api-us.example.test:443` | 区域任务 | orders-write-us | 供应商 | 公共边缘网络 | 待审核 |
| `https://gateway.example.test:443` | 旧脚本 | 无 | 内部团队 | 内部网关 | 已退役 |

不要仅因为供应商有一个端点就编造一行。除非代码、配置或已批准的迁移需要它，否则标为未使用。清单应让意外的能力变得可见，而不是记录每一种可能性。

OWASP 的服务端请求伪造防护备忘单提出了相关观点：当应用只与已识别的可信应用通信时，允许列表可行，但仅验证域名并不能解决 DNS 行为。该指南聚焦 SSRF，但其中的运行经验同样适用。只有当你知道谁维护这些名称、它们解析到哪里，以及调用方能否在之后把已审核名称变成另一项请求时，主机名列表才足够可靠。

## 重定向必须单独审核

重定向是一项新的目标决策。自动跟随重定向的客户端可能在首次请求后离开已批准的权威，授权标头绝不能随之而去。

对于携带凭据的 API 调用，先禁用重定向。将 301、302、303、307 或 308 响应视为需要明确决策的响应。如果新 URL 已在该凭据的精确权威列表中，且方法和请求体行为可以接受，再向它发起单独请求。如果不在列表中，就停止。

这些状态码不能互换。303 通常会把请求改为 `GET`，307 和 308 会保留方法与请求体。自动重放带有授权标头的 `POST`，比看似无害的 `GET` 后果严重得多，尤其是新主机不同时。

测试你实际使用的 HTTP 库的行为。有些客户端会在主机变化时移除敏感标头，有些会在你意想不到的条件下保留标头，封装层也可能覆写默认设置。测试应捕获到达你控制的第二台主机的请求，再断言它既没有收到凭据，也没有收到复制的请求体。不要把库的名称当成其重定向策略的证明。

安全调用路径很好描述：

```text
1. Parse the requested URL.
2. Normalize and match its authority against the credential record.
3. Inject the credential only after that match.
4. Send one request with redirects disabled.
5. If a redirect arrives, parse and review the new authority before any new request.
```

这一顺序还可防止一个更隐蔽的错误：在检查目标前，就把标头注入通用客户端。一旦代码将令牌附加到可复用请求对象，之后的 URL 变化就可能把它带往意外位置。应在最后一个应负责的时刻绑定凭据，也就是最终 URL 已确定之后。

## 共享主机名需要独立凭据

单一主机名可以承载多个 API、环境和租户。精确的主机匹配是必要条件，但无法表达共享网关背后的所有安全差异。

以 `https://gateway.example.test` 为例。一个路径可能创建发票，另一个提交遥测数据，第三个管理用户。如果一枚 Bearer 令牌能授权这三者，主机审核提供的保护就十分粗糙。把 `/telemetry` 错改成 `/admin` 的程序错误仍留在已批准主机名上，也依然会成功。

通常正确的做法是按不同权限范围使用独立凭据。即便两个调用前往同一主机，也要给遥测客户端一枚不能管理用户的令牌。如果供应商支持受众或资源指示符，就使用它们。如果它只提供宽泛令牌，应拆分服务账户或选择更安全的集成边界，不要假装路径允许列表能解决授权问题。

路径规则仍有用。它们能捕捉编程错误，让意图可审核。但路径比主机更容易处理不当：百分号编码、重复斜杠、点段、网关重写和版本重定向都会让比较变复杂。应使用发送调用的同一个 URL 解析器和请求库进行规范化。绝不要用手写的子字符串检查做安全决策。

端口也遵循同样的道理。`api.example.test:443` 和 `api.example.test:8443` 是不同的权威。反向代理可能将它们路由到不同服务，测试第二个端口的开发者可能以为第一次审核也涵盖了它。两个都记录，否则两个都不允许。

## 把批准放在人工仍能判断目标的位置

只写着“允许 API 调用”的批准，等于要求审核者开一张空白支票。提示信息需要包含方法、完整且规范化的权威、请求路径、凭据身份和请求进程。否则人工无法实际发现代理从生产 API 切换到了被遗忘的兼容性主机名。

Sallyport 将凭据保存在加密保险库中，并自行执行 HTTP 操作，而不是把密钥交给代理。这很有用，因为批准可以放在操作边界上，目标和请求进程会同时可见。

别把批准界面变成仪式。对于调用一组稳定、已批准权威且持续时间短、范围已知的代理运行，可使用按会话批准。对于能够转移资金、删除数据或触及管理端点的凭据，每次调用都应要求确认。阻力应与一次错误调用的后果相匹配，而不是取决于审核者的耐心。

Sallyport 有意不提供策略语言或规则引擎，因此不应把它当作跳过端点清单的借口。它的保险库闸门和授权控制回答的是一个进程现在能否使用凭据。你的凭据记录仍必须回答哪个权威有资格接收它。

活动记录应保留足够证据，以便之后重建决策：请求的权威、可获取时的解析路由、方法、状态、请求会话以及适用的凭据记录。不要为了完善审计记录而记录密钥或完整的敏感载荷。制造第二个密钥存储的日志并不能改善审计。

## 每次 DNS 和集成变更后重新检查别名

当 DNS、供应商端点或部署设置发生变化时，目标绑定会逐渐失效。让清单审核成为这些变更的一部分，而不是每年一次才发现大量过时名称的例行工作。

当有人新增或编辑 CNAME、修改获准内部名称的 A 或 AAAA 记录、引入区域端点、更换 SDK、变更 API 网关或新增重定向时，应触发审核。审核者应比较旧的和新的权威列表，再决定现有凭据是否可以随该变更继续使用。DNS 所有权变更值得得到与代码所有权变更同等的关注。

对于你控制的名称，当已批准主机名开始解析到私有、回环、链路本地或意外的内部地址时，应发出告警。这既是 SSRF 防护，也是凭据防护。对于公共供应商名称，应在 CNAME 目标变化或地址范围出现重大变化时告警，然后调查，而不是自动拦截每一次 CDN 轮换。

在集成旁保留一个小型回归测试。它应尝试一个已批准 URL、一个未列出的备用基础 URL、一个带误导性后缀的主机、一个显式备用端口，以及一次跨主机重定向。预期结果不只是网络连接失败。客户端必须在任何请求到达未列出的目标之前，就拒绝附加凭据。

最后这项条件才是该坚持的标准。如果代理能先发送密钥，之后才发现目标错误，那么审核在最关键的时候已经失败。
