运行在Cloud Run上的Django应用Invalid HTTP_HOST header问题排查
问题分析与解决思路
一、如何复现该异常请求
你之前直接发送无效HOST请求被负载均衡器拦截,是因为Cloud Armor和LB的主机头校验规则在生效。要触发这个问题,得用更隐蔽的畸形请求构造,绕过前端校验:
- 构造包含URL编码特殊字符的请求:比如在合法主机头
example.net后拼接%7d(即}的URL编码),同时在请求路径中加入全角句号。(编码后为%E3%80%82) - 尝试利用LB对HTTP头的宽松解析漏洞:比如通过
X-Forwarded-Host头注入畸形值,因为部分LB配置会优先使用这个头覆盖原始Host,而Cloud Armor可能未校验该字段。用curl构造示例:curl -H "Host: example.net" -H "X-Forwarded-Host: example.net%7d" "https://example.net//example%E3%80%82com" - 注意:扫描机器人用的是更隐蔽的混合编码或换行注入方式,所以你的直白测试请求会被拦截,而机器人的请求能绕过。
二、请求值差异的核心原因:LB与Cloud Run的日志/转发逻辑不同
负载均衡器和Cloud Run的日志不一致,并非请求在传输中被篡改,而是两者的处理逻辑有区别:
- LB的日志与转发逻辑:LB会先做基础合法性校验,日志里记录的是它“修正”后的合法请求格式(比如把
//example。com解析为/),但实际上它识别到请求畸形后,仍错误地将原始畸形内容转发给了Cloud Run,同时返回400状态码。 - Cloud Run的日志逻辑:Cloud Run会原样记录收到的所有请求内容,包括LB转发过来的畸形Host头和路径,所以日志里能看到
example.net%7d和//example%E3%80%82com这类原始畸形值。
三、与Django的关联:错误是Django抛出,但值并非它添加
这个报错是Django的内置安全机制触发的,但畸形值不是Django生成的:
- 即使
ALLOWED_HOSTS = ["*"]跳过了主机白名单校验,Django仍会对Host头做格式合法性校验,example.net%7d包含不符合RFC 1034/1035规范的字符,因此触发报错。 - 你看到的
Request URL是Django根据收到的HTTP_HOST和PATH_INFO拼接出来的结果,只是如实反映了收到的畸形请求数据,并非请求被修改。
临时修复方向
- 在Cloud Armor中新增规则,拦截包含URL编码特殊字符(如
%7d、%E3%80%82)的Host头或请求路径; - 给Django添加自定义中间件,提前过滤非法Host头,不符合规范则直接返回400,避免生成报错日志;
- 升级Django到3.2.x最新补丁版本或更高主版本,新版本对畸形Host头的处理更完善,能直接拦截而非抛出详细报错。
内容的提问来源于stack exchange,提问作者oklymeno
相关产品推荐
相关产品推荐

