Host 是 HTTP/1.1 里唯一必填的请求首部。缺了它、重复它、或者往里塞非法字符,都该被拒。
5.1 讲虚拟主机时已经用过它一次,这一节专门讲它的合法性校验——这是安全上很关键的一环。
一、为什么它是必填的
因为请求行里只有路径,没有域名。没有 Host,服务端不知道这个请求要交给哪个站点处理。
HTTP/1.0 里它是可选的(那时一个 IP 一个站),HTTP/1.1 把虚拟主机变成常态,于是把它升成了必填。
Demo 里六种情形:正常、缺失、重复、带斜杠、带 at 号、带端口。结果只有第一种和最后一种通过。
二、三种必须拦的情况
缺失:回 400。这是硬性要求,没有商量的余地。
重复:回 400。
这是经典的走私手法——两个解析器可能各取一个,代理把请求送到 A,源服务器却认为目标是 B,
两边的鉴权和缓存判断全部错位。
含非法字符:回 400。/、@、#、?、空格都不该出现在 Host 里。
原因很实际:有人会拿 Host 去拼 URL,http:// 加 Host 加路径,一旦 Host 里有 / 或 @,
拼出来的地址就指向别处了。
带端口是合法的(www.example.com:8080),因为端口是主机的一部分。
三、一个真实的攻击面:Host 头注入
最典型的场景是密码重置。
代码里常见这种写法:生成一个重置链接 https:// 加 Host 加 /reset?token=xxx。
攻击者只要在自己发起的请求里把 Host 改成自己的域名,服务端就会生成一个指向攻击者服务器的链接,
再把这个链接发给受害者(比如通过「找回密码」的邮件)。受害者点开,令牌就交出去了。
防御很直接:配置里写死允许的域名白名单,对不上就拒。绝不能拿 Host 去拼任何东西。
这也是为什么现代框架都要求配置 ALLOWED_HOSTS 或者类似的设置——不是形式主义,
是补一个真实存在的洞。
四、动手 Demo:六种 Host,哪些该被拒
服务端按三条规矩校验:必须有、只能有一个、不许含非法字符。 客户端依次发六种形态的请求,看每一种的判定与状态码。
names = [ln.split(u':', 1)[0].strip().lower()
for ln in lines[1:] if u':' in ln]
if names.count(u'host') == 0:
return reply(u'HTTP/1.1 400 Bad Request', [], b'must have Host')
if names.count(u'host') > 1:
return reply(u'HTTP/1.1 400 Bad Request', [], b'duplicate Host')
bad = [c for c in host if c in u' /\\@#?']
if bad:
return reply(u'HTTP/1.1 400 Bad Request', [],
(u'bad char: ' + bad[0]).encode(u'utf-8'))
实跑输出:
--------------------------------------------------------------------
Host 的合法性:缺失、重复、带非法字符
--------------------------------------------------------------------
【正常】
状态 HTTP/1.1 200 OK
正文 Host 合法:www.example.com
【没有 Host】
状态 HTTP/1.1 400 Bad Request
正文 HTTP/1.1 必须带 Host
【Host 出现两次】
状态 HTTP/1.1 400 Bad Request
正文 Host 出现了不止一次
【Host 里带斜杠】
状态 HTTP/1.1 400 Bad Request
正文 Host 里有非法字符:/
【Host 里带 at 号】
状态 HTTP/1.1 400 Bad Request
正文 Host 里有非法字符:@
【Host 带端口】
状态 HTTP/1.1 200 OK
正文 Host 合法:www.example.com:8080
为什么这几个要拦:
**缺 Host** 是 HTTP/1.1 的硬性要求,缺了就没法分派站点。
**重复 Host** 是经典的走私手法——两个解析器可能各取一个,
于是代理把请求送到 A,源服务器却认为目标是 B。
**带斜杠、at 号**是为了防止有人把 Host 当 URL 用,
拼出 `http://` 加 Host 加路径这种结构,把请求指向别处。
正常:200,正文「Host 合法:www.example.com」。
没有 Host:400,正文「HTTP/1.1 必须带 Host」。
Host 出现两次:400,正文「Host 出现了不止一次」。
带斜杠:400,明确报出「Host 里有非法字符:/」。带 at 号:同理,报出 @。
带端口 www.example.com:8080:200 通过。端口是主机的一部分,合法。
30 秒实验
- 发一个
Host: .(一个点),想想该不该拒。 - 发一个超过 255 字符的 Host,看服务端该怎么处理。
- 想一想:如果代码里用 Host 拼重置链接,攻击者能怎么利用?
常见误区
- 「Host 是可选的」——HTTP/1.1 里必填,缺了回 400。
- 「多个 Host 取第一个就行」——必须拒,否则代理和源服务器会理解不一致。
- 「Host 可以放心用来拼 URL」——这是 Host 头注入的来源。
自测三问(点开对答案)
1. 为什么 HTTP/1.1 把 `Host` 定为必填? —— 因为虚拟主机成了常态,请求行里没有域名,只能靠它分派站点。 2. 重复的 `Host` 为什么危险? —— 不同解析器可能各取一个,导致代理和源服务器对目标的理解不一致,形成走私。 3. `Host` 头注入的典型后果是什么? —— 用 Host 拼出来的链接指向攻击者域名,比如密码重置邮件里的令牌被劫走。基础字段讲完了。下面一大串都是同一个主题:条件请求。
- ☐ (本节完,下一节 6.4.9 If-Match)