Ghost HTTP 方法:HTTP 动词变异如何绕过现代 WAF 跨中间件层

35 阅读3分钟

官网:http://securitytech.cc/

Ghost HTTP 方法:HTTP 动词变异如何绕过现代 WAF 跨中间件层

引言

大多数 Web 应用防火墙(WAF)和 API 网关都自豪地宣传 “基于方法的保护”。 它们会阻止来自不可信来源的破坏性 HTTP 动词,如 DELETEPUTPATCH,认为这样就足以防止恶意操作。

但是,如果请求在 WAF 检查之后被修改,会发生什么呢? 当你的边缘 WAF 认为这是一个无害的 POST,但后台在几毫秒后把它改写成 DELETE 时,会发生什么?


图:攻击流程


元凶:HTTP 方法覆盖

为了支持只能发送 GETPOST 的老旧浏览器,许多 Web 框架引入了 Method Override(方法覆盖)。 它允许客户端通过以下方式发送替代的 HTTP 动词:

  • 使用查询参数,例如 _method=DELETE
    • 或使用请求头,例如 X-HTTP-Method-Override: DELETE

示例:

POST /users/42?_method=DELETE HTTP/1.1
Host: example.com
Content-Type: application/json

当这个请求到达应用时,中间件(如 Express 的 method-override、Rails 的 Rack::MethodOverride 或 Spring 的 HiddenHttpMethodFilter)会内部修改请求:

原始请求: POST /users/42
中间件处理后: DELETE /users/42

对于处在边缘的 WAF,这只是一个普通的 POST。 但对于后台,它却是一个破坏性的 DELETE


核心问题

这种 不同层之间的理解分歧——边缘和应用——产生了所谓的 幽灵方法(Ghost Method)

图:问题示意

在实际泄露案例中,调查人员可能在 WAF 日志中只看到无害的 POST 请求,却不知道应用内部数据已经被删除。


现实存在

在几个框架中,方法覆盖默认是 启用的

图:受影响的技术


概念验证:传输中变异方法

我们用 Express.js 写了一个简单的 POC 服务器,使用中间件模拟,只允许用户用 GET 和 POST 查询数据库。

// server.js
const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());

// --- 方法覆盖 (手动,无依赖) ---
app.use((req, res, next) => {
const fromBody = req.body && req.body._method;
const fromHeader = req.headers["x-http-method-override"];
const override = fromBody || fromHeader;
if (override) req.method = String(override).toUpperCase();
next();
});

// --- 模拟 WAF ---
app.use((req, res, next) => {
req.originalMethod = req.method;
if (req.method === "DELETE")
return res.status(405).send("WAF: DELETE blocked");
next();
});

const USERS = new Map([["42", { id: "42", name: "alice" }]]);

app.delete("/users/:id", (req, res) => {
const existed = USERS.delete(req.params.id);
res.json({
action: "DELETE",
existed,
edgeMethod: req.originalMethod,
appMethod: req.method,
});
});

app.get("/users/:id", (req, res) =>
res.json({ user: USERS.get(req.params.id) || null })
);

app.listen(3000, () => console.log("Vuln on :3000"));

服务器在 3000 端口运行:

图:服务器运行中


我们可以查询用户:

curl -s http://localhost:3000/users/42 | jq

图:用户存在检查

如果直接发送 DELETE 方法,会被 WAF 阻止:

curl -s -X DELETE http://localhost:3000/users/42

图:DELETE 请求被阻止

但可以通过 POST + 覆盖 绕过(边缘看到 POST,应用删除数据),方法如下:

  1. 在 URL 末尾添加 _method=DELETE 参数
    1. 或在 POST 请求中添加请求头 X-HTTP-Method-Override: DELETE
curl -s -X POST 'http://localhost:3000/users/42?_method=DELETE' | jq

图:使用 _method=DELETE 覆盖

图:使用 HTTP 头覆盖

验证用户是否存在,可以看到 Alice 已被删除:

图:用户已删除


防御策略

1. 禁用公共接口的方法覆盖

如果不需要,完全移除:

app.use(require('method-override')());

2. 在边缘剥离覆盖标识

配置 WAF 或反向代理,丢弃包含以下内容的请求:

  • _method=DELETE|PUT|PATCH
    • X-HTTP-Method-Override 请求头

3. 记录原始和实际方法

总是同时记录 原始方法覆盖后的方法

req.originalMethod // 边缘
req.method         // 覆盖后

总结 HTTP 方法覆盖是一个遗留的便利功能,却成为现代的盲点。当边缘和后台对请求的理解不一致时,安全控制形同虚设。检查你的框架,尽可能禁用 _method,确保 WAF 和应用使用相同的逻辑——因为一个看似 POST 实则 DELETE 的请求,不是功能,而是潜在漏洞。

公众号:安全狗的自我修养

vx:2207344074

Gitee:gitee.com/haidragon

GitHub:github.com/haidragon

Bilibili:haidragonx