Entry

HTTP_HOST and SERVER_NAME Security Issues

by: Wes Baker on: 5/26/2016

Many PHP sites rely upon the HTTP_HOST or SERVER_NAME variable to define the domain for any URLs. For example:

<a href="<?=$_SERVER['HTTP_HOST']?>/blog/">Blog</a>

That URL would render as whatever domain you’re on, followed by /blog. That’s a really handy trick when the site runs on multiple environments (e.g. your local install, your co-worker’s local install, the development server, and the live site).

The Problem(s)

That sounds really convenient, but there is a problem. The $_SERVER['HTTP_HOST'] and $_SERVER['SERVER_NAME'] variables can be changed by sending a different Host header when accessing the site:

curl -H "Host: notyourdomain.com" http://yoursite.com/

Doing that, any URLs that used $_SERVER['HTTP_HOST'] or $_SERVER['SERVER_NAME'] would use notyourdomain.com.

Taking this further, imagine an attacker fills out a password reset form with your email and changes the Host header. The password reset email would then direct you to their site. If you’re paying attention to the domain, you’re fine, but normal users don’t and that’s why phishing attacks work.

Another problem that can come from this is cache poisoning. It is common for sites to be cached by a proxy (Varnish, Cloudflare, corporate network, etc.) or by the software. In this attack, the attacker repeatedly accesses the site with a spoofed header. Eventually, the site that’s using those server variables to build URLs will use the attacker’s request for the cached response. With that your top level nav, in-line links, and all other links point to the domain of the attacker’s choosing. If they’ve copied your site, then the visitor won’t have any indication at all that they’re now on the attacker’s site, capturing their input.

“But I’m not building banking web sites”, some say. Attackers don’t care; they aren’t after your site. Most people use the same password everywhere. The end game for many attacks is identify theft, and your site might just be a means to get potential login credentials that can be used elsewhere to get the sensitive data. Cache poisoning via these server headers lets attackers collect your site’s member login attempts with little effort.

Are You Affected?

There are several tools available for ExpressionEngine that automatically configure your URLs using $_SERVER['HTTP_HOST'] or $_SERVER['SERVER_NAME'] such as Focus Lab LLC’S EE Master Config, the older version of Matt Weinberg’s Multi-server Setup for EE 2, and Pilotmade’s ExpressionEngine config file. If you’re using one of these, it’s possible you’re affected.

Even if you aren’t using one of these you could be affected. Look through your config file for $_SERVER['HTTP_HOST'] and $_SERVER['SERVER_NAME']. If those are being used to set your site_url or something similar, you’re affected.

The Resolution

Fixing this is actually pretty simple, if not a little inconvenient. First, if you’re using one of the above packaged solutions, check with the provider first to see if they have updated. We contacted everyone above to let them know about the vulnerability.

If they haven’t yet updated or you’ve created your own solution, you should look through your code and find any uses of $_SERVER['HTTP_HOST'] and $_SERVER['SERVER_NAME'] and make sure that you never display it on your site.

In an ExpressionEngine site, you’re most likely to find $_SERVER['HTTP_HOST'] in a config file, doing something like the following:

$config['site_url'] = 'http://' . $_SERVER['HTTP_HOST'] . '/index.php';
$config['cp_url']   = 'http://' . $_SERVER['HTTP_HOST'] . '/system/index.php';

Simply change any of those instances to your domain:

$domain = 'example.com';
$config['site_url'] = 'http://' . $domain . '/index.php';
$config['cp_url']   = 'http://' . $domain . '/system/index.php';

When dealing with multiple environments, you can use $_SERVER['HTTP_HOST'] and $_SERVER['SERVER_NAME'], but only to check which domain you’re on and then manually set the correct URL. You could keep it simple with an array of valid domains:

$domains = array('domain.com', 'dev.domain.com', 'staging.domain.com', 'localhost');
if (in_array($_SERVER['HTTP_HOST'], $domains))
{
    $domain = $_SERVER['HTTP_HOST'];
}
else 
{
    $domain = 'localhost';
}

Or if you have other values and settings you might make based upon the domain, you could use a switch statement:

switch ($_SERVER['HTTP_HOST']) {
    case 'domain.com':
        $domain = 'domain.com';
        break;
    case 'dev.domain.com':
        $domain = 'dev.domain.com';
        break;
    case 'staging.domain.com':
        $domain = 'staging.domain.com';
        break;
    default:
        $domain = 'local.domain';
        break;
}

Alternatively, if you’re using Master Config you can use the changes we’ve submitted.

More Information

If you’d like more information on why and how this attack works, we’d recommend looking at the following articles:

.(JavaScript must be enabled to view this email address) or share your feedback on this entry with @ellislab on Twitter.

ExpressionEngine News

#eecms, #events, #releases