为什么$_SERVER['HTTP_HOST']有时是不可控的

最近在看一套程序的时候, 看到一段代码。
如果SERVRT['HTTP_HOST']可以控制的话, 可以造成一个漏洞,
本地是测试成功了, 但是在测试demo的时候就失败掉了。
所以这里来谈谈SERVER['HTTP_HOST']为什么有时会不可控。


1
2
3
4
5
6
GET /123.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate

平时我们在抓包的时候, 都会发现包里会有个Host header,

一些同学会以为HTTP的联机就是靠包里的HOST header来连接的,
所以会认为如果修改掉包里的HOST, 那么就连接不到目标服务器,
所以是不可控的。

其实HTTP的联机与包里的HOST并没有啥关系, HTTP的联机是TCP/IP建立的, 所以修改掉HOST HEADER并不会把包丢到另外一个服务器上。

那么为什么有时候修改HOST HEADER的时候, 还是会请求失败掉。
其实包里的HOST HEADER, 主要是针对虚拟主机来使用的,
在一个ip上搭多个域名的时候,需要使用HOST HEADER来分辨交给哪个virtual host来处理。


测试一下
在httpd.conf里添加了

<VirtualHost *:80>
    DocumentRoot "/tmp/web/ep1"
    ServerName www.example.com
    <Directory "/tmp/web/ep1">
       Options FollowSymLinks
       AllowOverride None
       Require all granted
    </Directory>
</VirtualHost>
<VirtualHost _default_:80>
    DocumentRoot "/tmp/web/ep2"
    ServerName www.example2.com
    <Directory "/tmp/web/ep2">
       Options FollowSymLinks
       AllowOverride None
       Require all granted
    </Directory>
</VirtualHost>

/tmp/ep2/ep2.php www.example2.com
/tmp/ep1/ep1.php www.example.com

<?php
var_dump($_SERVER['HTTP_HOST']);

在修改了HOST之后就404了。因为这个时候Host header 与 ServerName www.example2.com 匹配不上了, 所以现在并不是到目录/tmp/web/ep2了, 并不存在ep2.php

那么是不是虚拟主机的HTTP_HOST一定就没办法控制呢, 读一下文档

The asterisks match all addresses, so the main server serves no requests. Due to the fact that the virtual host with ServerName www.example.com is first in the configuration file, it has the highest priority and can be seen as the default or primary server. That means that if a request is received that does not match one of the specified ServerName directives, it will be served by this first .

也就是说, 在配置文件里, 第一个配置的virtualhost, 具有最高优先级, 如果HOST HEADER里的与配置文件里的所有vitualhost里的ServerName都匹配不上的话, 那就会是第一个来服务, 如果恰巧你测试的站是第一个的话…… 。

这里的文件为ep1.php, 这里的HOST HEADER, 与ServerName 都匹配不上, 那么还是交给第一个来处理了, 所以ep1.php 能访问到, 然后修改掉了HTTP_HOST。


不过这种漏洞也已经很鸡肋了, 在最新版本的apache(2.4.25)中, 已经不允许HOST HEADER中出现一些特殊字符了。不过在低版本中, 还是能引入单引号空格之类的字符的。

对请求行和请求头强制实施与RFC7230相对应的HTTP请求语法,以防止恶意客户端或下游代理对响应分裂和缓存污染