About the Author

Chris Shiflett

Chris Shiflett is an author and speaker who leads the web application security practice at OmniTI.


SERVER_NAME Versus HTTP_HOST

A question was just brought up 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 entry 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 disagrees:

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

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

UseCanonicalName Off
ServerName example.org

The ServerName directive might seem like the only thing that affects $_SERVER['SERVER_NAME'], but this isn't the case. In order to illustrate what affect the Host header has, create index.php in the document root of the default host:

<?php 

echo "HTTP_HOST [{$_SERVER['HTTP_HOST']}]\n";
echo 
"SERVER_NAME [{$_SERVER['SERVER_NAME']}]";

?>

You want to test several different values for Host, and I find telnet to be easy enough for such simple testing:

$ telnet example.org 80

Here are a few of my tests and corresponding results:

1. No Host, HTTP/1.0:

GET / HTTP/1.0

Content:

HTTP_HOST []
SERVER_NAME [example.org]

2. Empty Host, HTTP/1.0:

GET / HTTP/1.0
Host:

Content:

HTTP_HOST []
SERVER_NAME []

3. Empty Host, HTTP/1.1:

GET / HTTP/1.1
Host:

Content:

HTTP_HOST []
SERVER_NAME []

4. XSS Host, HTTP/1.1:

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

Content:

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

5. SQL injection Host, HTTP/1.1:

GET / HTTP/1.1
Host: chris' --

Content:

HTTP_HOST [chris' --] 
SERVER_NAME [chris' --]

As you can see, Zeev is 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 inspect 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."

About This Post

SERVER_NAME Versus HTTP_HOST was posted on Thu, 16 Mar 2006 at 20:02:31 GMT.

13 Comments

1. S's GravatarS said:

I agree that it's risky to treat anything in $_SERVER as anything but tainted, but is there really a way to exploit this?

I can't think of one. You'd have to REALLY mangle a user's DNS to make this work.

The closest I can think of is a site that posts to $_SERVER['SERVER_NAME']/script.php.. and create a host that points there. You'd have to somehow trick the user's browser into using a different IP on the 2nd request (the post).

Am I just missing something? I noticed that a LOT of sites happily embed the $_SERVER['HTTP_USER_AGENT'] without escaping, but it's nearly impossible to trick a user into modifying his/her USER_AGENT string to something exploitable. Isn't this the same?

S

Thu, 16 Mar 2006 at 21:22:25 GMT Link


2. dbevfat's Gravatardbevfat said:

/Usually/ this would be quite hard to explot, because on named hosts you're not even served with the right script if you don't post a proper Host.

Fri, 17 Mar 2006 at 00:05:42 GMT Link


3. S's GravatarS said:

Just talked to Chris.. and cleared a thing or two up.

As far as I can tell, the only real risk lies in a scenario where the user's SERVER_NAME is stored on the server and replayed (without escaping) to a future user.

This is a legit problem.

Same goes for USER_AGENT.

I was thinking first-person.. and that's not possible (again, AFAIK).

S

Fri, 17 Mar 2006 at 01:34:41 GMT Link


4. DerelictMan's GravatarDerelictMan said:

I could see potential for an exploit in a situation where someone is using a wildcard DNS setup (http://en.wikipedia.org/wiki/Wildcard_DNS_entry) as an easy way to support "vanity domains" for users of an application, especially if no validation is done on the host name in the request headers, and that information is used to generate href attributes. Of course, this isn't exactly a common setup, but still...

Fri, 17 Mar 2006 at 01:51:13 GMT Link


5. Chris Shiflett's GravatarChris Shiflett said:

I should have been clearer, but there are actually lots of ways to exploit this sort of thing, and it depends on how you use the tainted data. I think it's better to realize that it's never safe to rely upon an attacker's lack of creativity and err on the side of caution by never doing anything important with tainted data.

I do want to point out that no DNS trickery is required. For example, if I want to exploit your site, and it has an IP of 10.0.0.0, I can simply do this:

telnet 10.0.0.0 80

Once it's connected, I enter my attack:

GET / HTTP/1.1

Host: <script>alert('XSS')

Now, if you only echo it, I'm attacking myself, but hopefully the basic idea is clear. More clever attacks would use JavaScript to trick a victim into sending such a request, combing a few different types of attacks.

Fri, 17 Mar 2006 at 02:01:23 GMT Link


6. DerelcitMan's GravatarDerelcitMan said:

Agreed with the "never do anything important with tainted data" part. However, speaking for me personally, your specific example will never affect me because I use name-based virtual hosts on all my servers. (dbevfat mentioned this in a previous comment.) Since "<script>alert('XSS')" isn't a valid name for any of my virtual hosts, Apache would route a request such as that to the default host for my IP address. In my case, the default IP-only host is basically a black hole; I serve up an empty response and I dump the request into a log file that is basically ignored. I set this up LONG ago mainly as a way to keep the noise from automated worm exploit attempts out of my actual log files.

When I mentioned the whole wildcard DNS thing, I guess I was just assuming that most people were like me and used only name-based hosts, and that the need to support a wildcard DNS setup would be the only legitimate reason to actually place a working application on the default host for a given IP address. I've been using the "default host > /dev/null" approach for so many years that I forgot it isn't necessarily what everyone else does. :) I would definitely expect most people on shared hosting to be on a name-based host though.

At any rate, though, you're right, it will never hurt to treat everything in $_SERVER as tainted, no matter what your setup.

Fri, 17 Mar 2006 at 06:50:26 GMT Link


7. Bigtree's GravatarBigtree said:

There's one important difference that's not mentioned here. If Apache is configured so that a virtual host has domain aliases, and a page is requested for an alias, the requested domain will be in HTTP_HOST. The SERVER_NAME variable will hold the virtual host's main name.

Fri, 17 Mar 2006 at 08:11:52 GMT Link


8. test's Gravatartest said:

<>"'

Test :-)

Fri, 17 Mar 2006 at 12:15:39 GMT Link


9. Mike Willbanks's GravatarMike Willbanks said:

As said before, this would only work on a website that uses an ip for itself, which any ecommerce website with an SSL certificate would.

My only and primative usage of SERVER_NAME is to feed my urls. But I also take care to do strip_tags and htmlspecialchars on all of the $_SERVER related data. So with that I would suppose that helps out a great deal. Since this is only used for output I would assume that there is not going to be many issues.

I suppose an additional check for server name could be a whitelist array:

$allowed_servername = array('host.this.com', 'this.host.com');

if (!in_array($_SERVER['SERVER_NAME'], $allowed_servername)) {

header('HTTP/1.0 400 Bad Request');

exit;

}

Fri, 17 Mar 2006 at 16:04:38 GMT Link


10. Leonid's GravatarLeonid said:

It's very usefull! Thanks, Chris!

Sat, 15 Apr 2006 at 14:06:24 GMT Link


11. Taras's GravatarTaras said:

So, if this BUG (as I thinks it's realyy bug that SERVER_NAME depends on headers ) will be fixed in future versions of PHP?

Mon, 14 Aug 2006 at 05:05:19 GMT Link


12. Paul's GravatarPaul said:

Changing SERVER_NAME behavior could break a lot of scripts. However, its name, and the fact that it is stored in a global array called "_SERVER" leads one to believe it is data that comes from the server and not a mix of the server and the HTTP request. I will would suggest that future versions of PHP should not populate any user touchable data in variables called "_SERVER".

Maybe there should be a "_HEADER" global array where [insert protocol here] headers could be stored and retrieved.

Wed, 27 Jun 2007 at 14:02:53 GMT Link


13. Felix's GravatarFelix said:

it depends on UseCanonicalName

http://httpd.apache.org/docs/1.3/mo...secanonicalname

Wed, 10 Oct 2007 at 14:24:57 GMT Link


Post A Comment

Personal Details and Comment

Style Guide

Line breaks are converted to paragraphs. Also use:

  • <a href="" title="">text</a>1
  • <em>text</em>
  • <blockquote><p>text</p></blockquote>
  • <code>2  <?php  if ($foo) {      $foo = TRUE;  }  ?></code>
  1. Note: <code> can be used inline (e.g. in paragraphs) or in a block as shown. Include whitespace and newlines in blocks.

Please enter Chris (my first name) below. This is a primitive spam prevention technique, and I apologize for the inconvenience.

Preview and Submit

Upcoming Talks

PHP Appalachia

11 - 14 Oct 2008

At Big Bear Lodge, Gatlinburg, Tennessee.

php|works / PyWorks

12 - 14 Nov 2008

At Sheraton Gateway Hotel Atlanta Airport, Atlanta, Georgia.

New Comments

Chris Shiflett wrote:

Miguel, read the post again. PHP 4.4.9 is the final release of PHP 4.

Posted in End of Life for PHP 4
Miguel Palazzo wrote:

I think you're wrong. PHP 4.4 is DEAD, that's so right, because they just released 4.4.9, and you...

Posted in End of Life for PHP 4
alikim wrote:

Hi, Thanks for the article! Tell me please if it's enough to use just session_start(); se...

Posted in
Wayne wrote:

Hi ZX, When taking in data, you should always check to see if magic_quotes is enabled. If it i...

Posted in addslashes() Versus mysql_real_escape_string()
Chris Shiflett wrote:

Thanks, Brandon. I'm glad you liked the talk. Maybe some parts of it would be interesting to some...

Posted in ZendCon

Browse Comments