fix: 终审修复——网关代理主机不可位移(堵 SSRF/令牌外泄)+onError 统一错误形状
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,8 +11,14 @@ const STRIP_RESPONSE_HEADERS = ['content-encoding', 'content-length', 'transfer-
|
||||
|
||||
/** 验签后转发:Authorization 原样带上(下游自己再验一遍——纵深防御,指南 5.6) */
|
||||
export async function forward(c: Context, target: string, stripPrefix: string): Promise<Response> {
|
||||
const search = new URL(c.req.url).search;
|
||||
const path = c.req.path.slice(stripPrefix.length) || '/';
|
||||
// 主机不可位移(C1):绝不能 new URL(路径, target)——剥前缀后若余下 `//evil.example.com/x`
|
||||
// 这种协议相对路径,URL 解析只保留 target 的 scheme 并把 host 整个换掉,调用方的
|
||||
// Authorization 与请求体就发去攻击者指定的主机,响应还原样回传(SSRF+令牌外泄)。
|
||||
// 改成在 target 上赋 pathname:赋 pathname 只动路径,永远动不了 host/port。
|
||||
const url = new URL(target);
|
||||
const sub = c.req.path.slice(stripPrefix.length) || '/';
|
||||
url.pathname = url.pathname.replace(/\/$/, '') + (sub.startsWith('/') ? sub : `/${sub}`);
|
||||
url.search = new URL(c.req.url).search;
|
||||
const headers = new Headers(c.req.raw.headers);
|
||||
for (const h of STRIP_HEADERS) headers.delete(h);
|
||||
const method = c.req.method;
|
||||
@@ -23,7 +29,7 @@ export async function forward(c: Context, target: string, stripPrefix: string):
|
||||
}
|
||||
let res: Response;
|
||||
try {
|
||||
res = await fetch(new URL(path + search, target), init);
|
||||
res = await fetch(url, init);
|
||||
} catch {
|
||||
// 下游没起/连不上:给统一错误形状的 502,并点名是哪个下游
|
||||
return c.json(
|
||||
|
||||
Reference in New Issue
Block a user