Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, PHP, and whatever else I happen to feel like hacking at.

Override the Host: header when using PHP’s readfile

Jerry Stratton, February 21, 2011

I ran into an odd issue today using readfile() in PHP. One server needed to contact another server via a URL; but the hostname of the second server wasn’t available via the DNS servers that the first server was attached to. This is by design—it’s necessary for the load balancer we use. The second server itself could be contacted, either by IP or by a different hostname. But because of the way virtual hosts work, either of those options meant a response from the wrong virtual host.

Name-based virtual hosts allow one computer with one IP address to respond with different sites to multiple hostnames1. For example, I have http://www.hypocritae.com/ and http://itisntmurder.com/; two hostnames, but if you look them up using “dig” you’ll see that they have the same IP address2.

The client converts the hostname into an IP address and then requests a page. The server doesn’t get to see which hostname was used to contact it; all it sees is the IP address. It knows which site the client wants because the client tells it which hostname in the “Host:” header.

This is the basis for the “Host:” exercises I used in HTTP headers. But readfile() is meant for reading files; it just happens to also support reading via URL. Is it possible to manipulate the http headers it sends when it makes a request?

Yes, although it’s a bit obscure. Most of PHP’s file functions, such as readfile() and file_get_contents(), accept a context parameter. You create contexts using stream_context_create, and they can include custom headers.

Here’s a simple PHP script that gets the title of a web page:

[toggle code]

  • <?
    • $page = 'http://www.hypocritae.com/';
    • $html = file_get_contents($page);
    • $html = str_replace('&', '&amp;', $html);
    • $document = simplexml_load_string($html);
    • echo $document->head->title, "\n";
  • ?>

Save this as “showTitle.php” and you can run this on the command line, if you have PHP installed, using “php showTitle.php”.

  • solis$ php showTitle.php
  • The Walkerville Weekly Reader
  • solis$

If, however, we want to get the site of a different host on that server, but we don’t have the ability to use that hostname for whatever reason, we can specify it in a context:

[toggle code]

  • <?
    • $page = 'http://www.hypocritae.com/';
    • $headers = array(
      • 'http'=>array(
        • 'header'=>"Host: itisntmurder.com\r\n"
      • )
    • );
    • $context = stream_context_create($headers);
    • $html = file_get_contents($page, false, $context);
    • $html = str_replace('&', '&amp;', $html);
    • $document = simplexml_load_string($html);
    • echo $document->head->title, "\n";
  • ?>

Run that, and see the results:

  • solis$ php showTitle.php
  • February 27, 1993: It Isn’t Murder If They’re Yankees
  • solis$

Even though it’s using the hostname “www.hypocritae.com” it’s getting the site for “itisntmurder.com”. Specifying the right host when you have to use a different hostname or a raw IP address really is as simple as that: the server looks at the “Host:” http header.

  1. Note that name-based virtual hosting doesn’t work with secure servers: secure servers need to have their own dedicated IP address (or, more specifically, there can only be one secure web site on an IP address).

  2. At least at the time I’m writing this. It may change in the future, of course.

  1. <- Custom ForeignKey managers
  2. Stable PHP sort ->