阅读需 8 分钟

HTTP 方法覆盖会击穿只看方法的审核

HTTP 方法覆盖能让获批的 POST 实际执行 DELETE 或 PATCH。本文说明如何测试隧道动词,并让审核判断真正执行的操作。

HTTP 方法覆盖会击穿只看方法的审核

当审核者只看请求行时,按方法审核并不安全。请求可以用 POST 到达,携带 X-HTTP-Method-Override: DELETE,随后由应用中间件改写方法并进入删除处理器。如果审批界面、网关规则、授权检查或审计记录把它归类为普通 POST,它审核的只是外壳,漏掉了真正的操作。

这种行为不是所有服务器都理解的 HTTP 协议技巧。它是一种应用约定,因此很容易被忽略。是否支持它取决于框架、中间件顺序、路由和部署。正确做法不是假定每个 POST 都暗藏破坏性,而是找出哪些位置接受覆盖,在任何安全判断前解析覆盖,并拒绝有歧义的请求。

线上方法和实际方法是两个不同事实

支持覆盖的技术栈需要记录两个方法:HTTP 请求行中的方法,以及应用最终用于路由的方法。我把它们称为线上方法和实际方法。它们通常一致。危险发生在中间层按线上方法审批或过滤,而后续组件按实际方法路由时。

RFC 9110 规定,方法令牌是请求语义的主要来源。它把 POST 定义为针对资源的处理,把 DELETE 定义为要求源服务器移除目标资源与当前功能之间的关联。RFC 并未标准化 X-HTTP-Method-Override。这个标头属于一组兼容性约定,当年用于只能处理 GET 和 POST 的客户端或中间层。

普通软件至今仍实现这些约定。Express 的 method-override 中间件默认读取 POST 上的 X-HTTP-Method-Override。它接受值后会修改 req.method,并把旧值保存在 req.originalMethod。ASP.NET Core 也提供 HTTP 方法覆盖中间件,默认标头同样是 X-HTTP-Method-Override。Spring 的 HiddenHttpMethodFilter 走另一条路:它读取 POST 表单中的 _method 参数,并允许 PUT、DELETE 和 PATCH。

这些细节把团队经常混为一谈的概念分开了。“网关允许 POST”描述传输处理,“应用执行 DELETE”描述实际行为。审批或访问决定需要后者。如果做决定的组件无法确定实际方法,就必须拒绝覆盖信号,不能按外层动词静默分类。

框架支持本身不能证明系统暴露了问题。Express 应用必须安装中间件并正确排序,ASP.NET Core 应用必须添加中间件,Spring 应用必须启用并放置过滤器。最终结果由实际部署配置决定,其中还包括反向代理和按路由配置的中间件。检查源码可以找到候选点,感知状态的测试才能提供证据。

获批的 POST 可以进入 DELETE 处理器

当不同组件对操作的权威表示意见不一时,隧道化写入就会溜过去。设想代理要向项目 API 发出以下请求:

POST /v1/projects/42 HTTP/1.1
Host: api.example.test
Authorization: Bearer [injected outside the agent]
X-HTTP-Method-Override: DELETE
Content-Length: 0

审批层把它标为“POST /v1/projects/42”,并套用允许本会话使用 POST 的规则。反向代理转发陌生标头。RFC 9110 通常要求代理转发无法识别的字段,除非配置明确阻止或转换它们。在应用内部,覆盖中间件先改写方法,路由器随后选择 DELETE 处理器,项目就消失了。

这种故障不需要有缺陷的 DELETE 授权检查。应用可能正确确认该身份有删除权限。缺陷出现在更早处:人工或自动审核器忽略了一个受支持的输入,因此批准了实质上不同的操作。同样的分歧还会影响 Web 应用防火墙、限速器、CSRF 控制、请求指标和访问日志。

中间件顺序决定哪些控制能看到哪个事实。Express 文档对此说得很直接:方法覆盖必须放在所有需要了解请求方法的中间件之前。对单个进程内的路由和 CSRF 逻辑,这个建议是对的。但它无法修复进程外的网关,因为网关早已根据请求行做出了基于方法的决定。

还有一种更隐蔽的故障。边缘日志记 POST,应用日志记 DELETE,事件调查者却把它们当成无关请求。共享请求标识符可以关联记录,但前提是两端都保留它,而且团队知道需要比较方法字段。统一的规范化操作记录更不容易出错。

不要仅凭 DELETE 这个词推断影响。RFC 9110 指出,DELETE 移除的是资源与其当前功能之间的关联;是否回收存储、是否销毁内容由应用决定。DELETE 端点可能执行归档、停用、排队或删除数据。审核实际路由后果,不要只看通用动词定义。

用受控的方法矩阵测试端点

可靠测试要在一次性资源上,对比原生方法、覆盖标头和参数约定造成的状态结果。请在预发布环境中运行,或使用专为破坏性测试准备的夹具。测试身份只应拥有真实调用者的权限,而且每个用例前都要重新创建夹具,避免一次成功删除污染后续结果。

先发送不含覆盖的基线 POST,记录状态码、响应正文和夹具状态。然后发送原生 DELETE,以确认路由确实存在,而且测试身份可以执行它。最后对每种约定重复 POST。最小标头探测如下:

BASE='https://staging.example.test'
ID='override-probe-17'

curl -sS \n  -D response.headers \n  -o response.body \n  -w 'case=x-http-method-override outer=POST status=%{http_code} bytes=%{size_download}
' \n  -X POST "$BASE/v1/projects/$ID" \n  -H 'Authorization: Bearer test-token' \n  -H 'X-HTTP-Method-Override: DELETE'

curl -sS \n  -o state.body \n  -w 'verify=read-after-request status=%{http_code} bytes=%{size_download}
' \n  -H 'Authorization: Bearer test-token' \n  "$BASE/v1/projects/$ID"

输出刻意采用便于 CI 解析的稳定格式:

case=x-http-method-override outer=POST status=204 bytes=0
verify=read-after-request status=404 bytes=71

这个示例表示覆盖确实删除了夹具,并不代表所有系统都应返回同一状态。有些 API 返回带结果文档的 200,排队删除时返回 202,也有系统保留仍可读取的软删除表示。应按自己应用的契约定义成功。

测试要用矩阵,而不是随手拼接步骤。控制组是不含覆盖的 POST,必须产生普通 POST 行为。原生组是不含覆盖的 DELETE,用来确定文档所述的删除行为。随后分别发送带 X-HTTP-Method-Override: DELETEX-HTTP-Method: DELETEX-Method-Override: DELETE 的 POST。最后测试查询中的 ?_method=DELETE 以及表单正文中的 _method=DELETE。每个覆盖用例都必须符合你明确设计的隧道方法行为,否则就应失败且不改变状态。

OWASP Web Security Testing Guide 列出了上述三个标头变体,并建议在受限方法被拒绝时用覆盖标头重放请求。我认为只比较状态码还不够,因为差异可能来自验证、路由或中间层。每次探测后都要检查资源、资源版本和任何排队任务。

如果你控制整个技术栈,就在每一跳抓取观察结果:边缘请求日志、审批事件、应用请求日志、所选路由和数据结果。目标是找出不一致。安全结果可以是一致拒绝,也可以是有意规范化后,对它执行与原生 DELETE 相同的授权和审批。

状态码只是线索,状态变化才是证据

单凭 HTTP 状态码无法判断覆盖是否执行。204 之后夹具消失,是强证据。200 可能是普通 POST 响应、删除回执,也可能是错误地用成功码包装的自定义错误。405 可能由覆盖中间件之前的边缘层返回,而另一条路径或内容类型仍能到达中间件。

断言应围绕带唯一标识符和已知初始版本的金丝雀资源建立。探测前先获取资源,保留版本标记或表示摘要。探测后再次读取,并检查 API 返回的操作记录。测试更新时,选择无害且一眼可辨的字段;测试删除时,先弄清产品采用硬删除、软删除还是延迟清理。

响应比较仍有帮助。保存状态码、选定响应标头、正文大小和正文摘要。把覆盖请求同时与两个控制组比较:普通 POST 和原生 DELETE。如果覆盖响应像 DELETE,状态也与 DELETE 一致,结论就很有说服力。如果响应像 POST,状态却按 DELETE 改变,日志层或响应层可能仍在使用外层方法。

重定向需要单独检查。客户端在跟随 301、302、303、307 或 308 时可能改变请求行为,不同工具保留方法和正文的规则也不同。先禁用重定向运行并记录 Location 字段,再有意跟随并捕获每一跳。不要把重定向行为和覆盖行为混进一个看不透的结果。

异步 API 需要更长观察窗口,但这不是猜测的理由。如果响应提供操作标识符,就按文档规定的测试契约轮询。如果没有持久句柄,就在预发布环境中查看队列或应用日志。无法确定的用例应标为无法确定,而不是安全。

还要验证负面状态。被拒绝的覆盖不应创建、更新、归档、排队、发送邮件或触发高权限 Webhook。整齐的 4xx 很容易误导团队:后面的组件可能已提交副作用,前面的组件随后才拒绝响应路径。测试夹具的监控必须足以发现这种顺序。

标头变体和歧义应放在同一测试中

把运行和每次调用分开
Sessions 跟踪代理进程,Activity 则记录每个 POST 或 DELETE 请求。

只测标准拼写会漏掉兼容代码和解析器分歧。常见标头名是 X-HTTP-Method-OverrideX-HTTP-MethodX-Method-Override,但应用也能定义自有读取器。查询字符串和表单正文可携带 _method,旧集成有时还使用与厂商或框架相关的名称。

标头名大小写不是另一种约定。RFC 9110 规定字段名不区分大小写,因此 x-http-method-overrideX-HTTP-Method-Override 指同一字段。只匹配首字母大写写法的安全过滤器有缺陷,即使应用库会规范化标头名。

重复和冲突值暴露了更难处理的问题。字段定义允许时,RFC 9110 允许接收方把重复字段行合并为逗号分隔值,而且提醒实现者,即便字段预期只有一个值也要考虑重复情况。覆盖标头不是具有统一解析规则的标准单值字段。Express 中间件文档称它使用重复标头的第一个值,而安装多个覆盖处理器还会在不同标头名之间形成优先级。

基本矩阵之后加入大小写变体 x-http-method-override: DELETE,它必须和标准大小写结果一致。发送两个相同 DELETE 值,要求得到一种有文档说明的结果或明确拒绝。先发 PUT 后发 DELETE,再在两个覆盖标头名中放入不同值,这两类冲突都应因歧义被拒绝。还要拒绝 PUT, DELETE 这种逗号值、原生 PATCH 配覆盖 DELETE(除非契约明确规定),以及 DELETE 标头配 _method=PATCH

不要只停在 DELETE。还要探测 PUT 和 PATCH,因为审核系统可能对创建、替换、局部更新和删除有不同分类。用一个不支持的令牌作为负面对照。除非测试环境和范围明确允许,否则不要测试 TRACE 和 CONNECT;它们会触发不同的基础设施行为,却不会让覆盖结论更可靠。

空白和取值大小写也能暴露规范化不一致。在客户端允许时发送 deleteDELETE 和空值。安全规则很简单:只规范化文档约定允许的形式,按明确方法集合验证,其余全部拒绝。悄悄选择第一个可解析值,只会在代理或库变化后让绕过再次出现。

代理生成的请求更容易触发盲点

AI 代理不会创造方法覆盖弱点,但会让不完整审核变得更危险。代理可以任意组合标头、复用文档示例,还可能在原生 DELETE 失败后把它改成 POST 隧道重试,却不理解第二种形式跨过了审批边界。查看精简审批卡片的人很可能只盯着显眼的动词和路径。

把代理生成的每个请求组成部分都视为不可信操作输入,其中也包括看似兼容性元数据的标头。在模型外注入凭据可以减少秘密暴露,却不能让请求的操作变得无害。请求仍会把代理选择的路径、方法、标头、查询和正文交给一个可能联合解释它们的系统。

工具模式能在请求产生前减少歧义。允许自由填写标头的通用 HTTP 工具,给了调用者请求覆盖所需的全部词汇。面向具体路由的删除工具可以直接说明破坏性操作,并省去覆盖字段。第二种设计更容易审核,但前提是实现没有通过隐藏出口继续传入额外标头。

如果必须使用通用 HTTP 工具,就在展示给人工审批前解析拟议请求。解析器应与执行器使用同一套覆盖清单和优先级规则。审批后冻结规范化操作,防止执行器稍后解析出不同结果。如果任何中间层会在审批后更改标头,就把确定性转换纳入被审核操作,或者重新解析并要求再次审批。

重试同样需要关注。POST 天生不安全,也不保证幂等。RFC 9110 对 POST 的定义很宽,即针对资源的处理;许多 POST 端点无需任何覆盖就会创建或修改数据。代理工具不能因为外层方法是 POST 就判断可以安全重试,也不能把所有 POST 都升级为破坏性审批。它需要结合路由语义和实际方法。

评估代理行为时,要分开测试身份和生产身份。权限过宽的生产凭据会让每个探测都成功,从而掩盖授权差异。使用代理实际会持有的受限角色,确认原生与隧道形式得到相同决定,并记录人工审批实际展示了什么。只有把拟议操作、展示操作、执行操作和结果状态串成一条链,才能证明这是审核不一致。

最早的控制必须识别实际操作

记录每一次覆盖探测
Sallyport 在 Activity 日志中记录每次代理 HTTP 调用,包括对同一端点的重复探测。

所有基于方法的决定都必须放在一个统一解析器之后,否则就要拒绝全部覆盖输入。人工审批、授权、CSRF 防护、路由限制、限速和审计分类都适用。让每个控制各自重复临时解析,必然产生分歧。

要明确表示解析结果:

{
  "transport_method": "POST",
  "effective_method": "DELETE",
  "override_source": "header:x-http-method-override",
  "override_value": "DELETE",
  "target": "/v1/projects/42",
  "ambiguous": false
}

解析器应列出所有受支持标头和参数,收集提供的全部值,只要出现一个以上不同候选值就拒绝请求。它通常只应接受来自 POST 等允许外层方法的覆盖。它要按应用有意支持的方法验证取值,再为下游控制生成不可变的操作记录。

把授权放在解析之后,并绑定路由与实际方法。“这个身份能否向此路径 POST”在 POST 充当隧道时太弱。应问“这个身份在当前条件下能否对该资源执行 DELETE”。无论客户端发原生 DELETE 还是受支持的隧道形式,都应使用同一处理器授权。

审批界面应先显示后果,并在两种表示不同时同时展示。DELETE /v1/projects/42 via POST override 这种紧凑标签既给出实际操作,也保留传输细节。如果只把覆盖标头藏在可展开的原始请求面板里,就等于要求审核者在时间压力下自己找出危险事实。

如果上游网关无法复现应用的解析规则,不要只教它理解其中一小部分。要么在审批前拒绝任何已知覆盖信号,要么把审批点移到可信的规范化之后。网关理解一个标头而应用理解三个,只会制造虚假的覆盖感。

解析要和业务语义分开。解析器可以得出实际方法是 DELETE,路由则负责决定它代表归档、撤销、解绑还是擦除。高影响操作的审批系统需要了解路由的操作描述。规范化方法是必要条件,但一个动词无法描述所有效果。

只剥离一个标头并不能修好问题

只有在服务不打算支持任何覆盖路径时,在代理处移除 X-HTTP-Method-Override 才安全。这个做法很受欢迎,因为简单,而且常能阻止第一个概念验证。但如果应用还接受别的标头、_method 参数、直达上游的路由或后续中间件,它就会失败。

Microsoft 的 YARP 标头指南列出 X-HTTP-Method-OverrideX-HTTP-MethodX-Method-Override,说明代理默认会转发它们,并建议在运营方需要防止绕过时使用请求标头移除转换。这是实用的边缘加固,却不能证明目标应用忽略查询或正文覆盖,也不覆盖绕过 YARP 直达目标的流量。

有两种可靠设计可选。如果目前没有客户端需要兼容隧道,就在应用中禁用中间件,并在边缘拒绝全部已知信号。如果客户端确实依赖隧道,就只记录一种接受的输入,移除或拒绝其他形式,在所有控制之前规范化,并把原生与隧道请求作为同一个操作测试。

选择前先盘点代码和配置。搜索框架中间件、自定义请求包装器、_method、三个常见标头名,以及任何给请求方法赋值的代码。检查路由组,因为中间件可能只应用于某个 API 前缀。还要检查框架升级后保留下来的生成式 API 网关和旧兼容模块。

接着检查网络路径。内容分发层、负载均衡器、服务网格、反向代理、应用服务器和框架,可能用不同方式规范化或记录标头。确认没有公开路由能绕过执行拒绝的组件。对内部代理流量,也不要因为网络是私有的就信任调用者可控标头。

剥离可能破坏合法客户端,所以团队常常推迟。移除前先记录覆盖信号是否出现,但不要记录秘密或完整正文。宣布弃用,在测试环境明确失败,并同时移除服务器支持和边缘规则。让无文档兼容行为永久存在,成本往往高于迁移仍在使用它的客户端。

日志必须同时记录请求行和所选操作

每次使用敏感密钥都暂停
为 API 密钥启用逐次审批,每个请求都需要点击或 Touch ID。

审计记录应保留传输方法、实际方法、覆盖来源、规范化值、所选路由、认证身份、目标资源、决定、响应状态和确认结果。缺少两个方法中的任何一个,审核者都无法重建 POST 为何进入 DELETE 处理器;缺少结果,记录只能说明意图,不能说明效果。

有歧义而被拒绝的请求也要记录。冲突覆盖值即使从未进入处理器,也与安全相关。只存标头名和规范化方法令牌,不要存授权值或无关请求正文。遇到无效值时,保存长度受限且经过转义的表示,防止控制字符伪造日志行。

用第一个可信节点生成的请求标识符关联各层记录。不要直接接受外部标识符,除非先替换或加命名空间。边缘层、解析器、审批服务、应用和异步工作进程都应传递可信标识符,让调查者无需依赖时间戳就能追踪一个操作。

方法字段也要准确命名。通用的 method 属性会诱使各组件写入不同含义。用 transport_method 表示请求行,用 effective_method 表示路由操作。如果中间件暴露 originalMethod,应明确映射,不要假定这个名称在所有框架里含义相同。

Sallyport 执行代理的 HTTP 调用,不向代理暴露凭据,并在 Activity 日志中记录调用。这种隔离不会让覆盖变安全,审核仍须在执行前识别实际方法。

自动运行结束后,防篡改证据很重要。只能追加的哈希链记录可以暴露静默修改,但完整性无法补回缺失语义。应在做决定时记录规范化操作,不要让后续任务尝试从访问日志推断。

审核面板应直接显示不一致。按服务和覆盖来源统计 transport_method != effective_method,能很快发现意外兼容流量。新出现的来源、有歧义的请求,以及审批事件只记录外层方法的破坏性实际方法,都应触发告警。

回归测试必须以拒绝为默认

持久修复需要一项贯穿实际部署路径的契约测试,只要未获批准的覆盖改变状态就失败。解析器单元测试有帮助,却无法发现代理规则消失、中间件被移到授权之前,或某条路由通过另一入口变得可达。

为矩阵每一行写明预期行为。如果禁用隧道,每个覆盖信号都应返回有文档说明的 4xx,而且夹具不变。如果支持一种约定,其 DELETE 结果应在授权、审批分类、路由和审计字段上与原生 DELETE 一致。冲突和未知令牌必须在产生副作用前失败。

部署路径测试应包括以下断言:

  • 审批事件明确写出实际方法;
  • 原生和隧道形式得到相同授权决定;
  • 被拒绝输入不会改变金丝雀版本或副作用计数;
  • 日志在同一可信请求标识符下包含两个方法字段;
  • 直达上游的访问无法跳过规范化。

当代理配置、框架中间件、认证、审批代码、路由或代理工具变化时运行测试套件。每夜运行可发现基础设施漂移,发布门禁可发现有意变更。夹具要保持隔离,确保重复执行永不触碰真实人员数据。

即使框架称其为兼容功能,新接受一种约定也应按安全变更处理。它需要明确负责人、客户端需求、优先级规则、允许的外层方法、允许的实际方法集合以及移除条件。否则,一行中间件配置就会悄悄扩展控制必须理解的操作词汇。

干净的系统从入口到结果只使用一份操作描述。如果线上写 POST,应用里执行 DELETE,这个差异必须在任何人或组件说“同意”前明确出现。再晚一步,就只能做事件重建。

常见问题

什么是 HTTP 方法覆盖?

HTTP 方法覆盖是一种应用约定,它把 DELETE 等实际动词放进通常以 POST 发送的另一种请求中。框架或中间件读取标头或参数,再改变路由使用的方法。

X-HTTP-Method-Override 是标准 HTTP 标头吗?

不是。RFC 9110 定义了 HTTP 方法语义,但没有标准化 X-HTTP-Method-Override。支持来自特定框架、中间件、API 或自定义代码,因此应测试实际部署的服务。

应该测试哪些方法覆盖标头?

测试 X-HTTP-Method-OverrideX-HTTP-MethodX-Method-Override。还要检查 _method 等查询和表单约定,以及应用配置的自定义读取器。

POST 请求真的可以删除资源吗?

可以,前提是目标接受 X-HTTP-Method-Override: DELETE 等覆盖并按 DELETE 路由。用一次性夹具和状态检查证明行为,不能只看响应状态。

代理是否应该阻止所有方法覆盖标头?

没有合法客户端需要隧道时,应阻止并拒绝它们。如果服务有意支持覆盖,就在审批和授权前规范化唯一有文档的约定,并拒绝所有冲突形式。

标头大小写会影响方法覆盖吗?

不应影响,因为 HTTP 字段名不区分大小写。只阻止一种大小写写法的过滤器,可能与会规范化标头名的框架产生分歧。

重复覆盖标头应如何处理?

把重复或冲突覆盖值视为歧义并拒绝。不同代理和库选择或合并重复值的方式可能不同,因此静默选择第一个值并不安全。

405 响应足以证明覆盖已禁用吗?

不足。405 可能来自某一层,而另一条路由、标头、参数或内容类型仍接受覆盖。要沿调用者使用的同一部署路径验证每种约定的状态与日志。

审批界面应如何显示隧道请求?

先显示实际操作,例如 DELETE /v1/projects/42 via POST override。同时保留外层方法和覆盖来源,让审核者既看到后果,也看到传输方式。

方法覆盖审计日志应包含什么?

在同一可信请求标识符下记录传输方法、实际方法、覆盖来源、所选路由、身份、目标、决定、响应和结果。记录歧义拒绝时不要复制凭据或无限长度的请求数据。

Sallyport

Sallyport 替你的 AI 智能体执行 API 调用和 SSH 命令。密钥留在你 Mac 上的本地密钥库里;每次运行由你批准,每个操作都落入一份密封的审计日志。

© 2026 Sallyport · 依据 Apache-2.0 开源 · Oleg Sotnikov