ALLOWED_HOSTS 常被当成「上线要填的一项」。这篇把它的匹配规则一条条跑出来:端口、大小写、子域名、空列表、非法格式、X-Forwarded-Host,最后看不校验时 Host 头会把什么东西污染掉。
Host: example.com → 200 host=example.com abs=http://example.com/reset/abc/ Host: example.com:8000 → 200 host=example.com:8000 abs=http://example.com:8000/reset/abc/ Host: EXAMPLE.com → 200 host=EXAMPLE.com abs=http://EXAMPLE.com/reset/abc/ Host: www.example.com → 400 [页面标题: Bad Request (400)] Host: example.com.evil.com → 400 [页面标题: Bad Request (400)] Host: evil.com → 400 [页面标题: Bad Request (400)]
Host: example.com → 200 host=example.com abs=http://example.com/reset/abc/ Host: a.example.com → 200 host=a.example.com abs=http://a.example.com/reset/abc/ Host: a.b.example.com → 200 host=a.b.example.com abs=http://a.b.example.com/reset/abc/ Host: evilexample.com → 400 [页面标题: Bad Request (400)] Host: example.com.evil.com → 400 [页面标题: Bad Request (400)]
DEBUG = True
Host: localhost → 200 host=localhost abs=http://localhost/reset/abc/
Host: 127.0.0.1 → 200 host=127.0.0.1 abs=http://127.0.0.1/reset/abc/
Host: [::1] → 200 host=[::1] abs=http://[::1]/reset/abc/
Host: testserver → 400 [页面标题: DisallowedHost
at /]
Host: evil.com → 400 [页面标题: DisallowedHost
at /]
DEBUG = False
Host: localhost → 400 [页面标题: Bad Request (400)]
Host: 127.0.0.1 → 400 [页面标题: Bad Request (400)]
Host: evil.com → 400 [页面标题: Bad Request (400)]Host: exa mple.com → 400 [页面标题: Bad Request (400)] Host: example.com/evil → 400 [页面标题: Bad Request (400)] Host: example.com@evil.com → 400 [页面标题: Bad Request (400)] Host: example.com:abc → 400 [页面标题: Bad Request (400)] Host: → 400 [页面标题: Bad Request (400)]
格式校验与 ALLOWED_HOSTS 是两层:先检查主机名语法,再检查是否在列表里。
[USE_X_FORWARDED_HOST = False] 只有 Host → 200 host=example.com abs=http://example.com/reset/abc/ Host + X-Forwarded-Host: proxy.example.com → 200 host=example.com abs=http://example.com/reset/abc/ Host + X-Forwarded-Host: evil.com → 200 host=example.com abs=http://example.com/reset/abc/ [USE_X_FORWARDED_HOST = True] 只有 Host → 200 host=example.com abs=http://example.com/reset/abc/ Host + X-Forwarded-Host: proxy.example.com → 200 host=proxy.example.com abs=http://proxy.example.com/reset/ab Host + X-Forwarded-Host: evil.com → 400 (400 页面)
只有反向代理真的会设置这个头、并且会清除客户端自己带来的同名头时,才应该开 USE_X_FORWARDED_HOST;这是设计上的提醒,本文没有测试代理。
ALLOWED_HOSTS=["*"] Host: evil.example → 200 host=evil.example abs=http://evil.example/reset/abc/ ALLOWED_HOSTS=["example.com"] Host: evil.example → 400
这是在自己的测试项目里用自己的 Host 头请求得到的现象:Django 用 Host 头拼出绝对 URL,如果它可以被任意指定,密码重置邮件之类的链接就可能指向别的域名。