SERVER_NAME Versus HTTP_HOST

16 Mar 2006

A question was asked on the New York PHP mailing list concerning $_SERVER['SERVER_NAME'] and $_SERVER['HTTP_HOST']:

Aren't these the same thing?

There were several informative replies within the first few minutes, but there's more to this question than most people realize. In fact, I'm reminded of a blog post from Zeev last year where he warns against $_SERVER['SERVER_NAME']:

And how about $SERVER_NAME ($_SERVER['SERVER_NAME']), which actually depends on the Host header sent by the remote user, and can therefore be spoofed under certain circumstances?

Rasmus disagreed:

No, that isn't affected by the Host header.

If you want to get to the bottom of this debate, follow along with this brief example. Imagine your web server has a default host set up as follows:

  1. UseCanonicalName Off
  2. ServerName example.org

The ServerName directive might seem like the only thing that affects $_SERVER['SERVER_NAME'], but is this a safe assumption? To determine what affect the Host header has, if any, create an index.php in the document root of the default host with the following code:

  1. <?php
  2.  
  3. echo "HTTP_HOST [{$_SERVER['HTTP_HOST']}]\n";
  4. echo "SERVER_NAME [{$_SERVER['SERVER_NAME']}]";
  5.  
  6. ?>

You can test several different values for Host easily enough with telnet:

  1. telnet example.org 80

Here are a few of my tests and corresponding results. For each test, I show the exact request and the content of the response, so please feel free to test this yourself.

1. No Host, HTTP/1.0

Request:

  1. GET / HTTP/1.0

Result:

  1. HTTP_HOST []
  2. SERVER_NAME [example.org]

With no Host, SERVER_NAME is the value from Apache.

2. Empty Host, HTTP/1.0

Request:

  1. GET / HTTP/1.0
  2. Host:

Result:

  1. HTTP_HOST []
  2. SERVER_NAME []

With an empty Host, SERVER_NAME is empty.

3. Empty Host, HTTP/1.1

Request:

  1. GET / HTTP/1.1
  2. Host:

Result:

  1. HTTP_HOST []
  2. SERVER_NAME []

With an empty Host, SERVER_NAME is also empty with HTTP/1.1.

4. XSS Host, HTTP/1.1

Request:

  1. GET / HTTP/1.1
  2. Host: <script>alert('XSS')</script>

Result:

  1. HTTP_HOST [<script>alert('XSS')</script>]
  2. SERVER_NAME [&lt;script&gt;alert('XSS')&lt;/script&gt;]

With a non-empty Host, SERVER_NAME is the HTML-escaped host value.

5. SQL Injection Host, HTTP/1.1

Request:

  1. GET / HTTP/1.1
  2. Host: chris' --

Result:

  1. HTTP_HOST [chris' --]
  2. SERVER_NAME [chris' --]

As you can see by the results, Zeev was right. Under certain circumstances, the Host header can affect $_SERVER['SERVER_NAME']. The ServerName directive is used when the Host header is absent, and apparently $_SERVER['SERVER_NAME'] is escaped with something like htmlentities().

Sometimes, it's hard to tell whether a particular element in $_SERVER can be affected by the HTTP request (ask Sean about PHP_SELF), so I find it easier to treat everything from $_SERVER just as if it were something like $_GET or $_POST.

As Dan Cech points out, the UseCanonicalName directive affects this behavior. From httpd.conf, "With this setting off, Apache will use the hostname:port that the client supplied, when possible. This also affects SERVER_NAME and SERVER_PORT in CGI scripts."