[ss-2016-003] Hostname, IP and Protocol Spoofing through HTTP Headers

This commit is contained in:
Ingo Schommer 2016-02-17 23:19:19 +13:00 committed by Damian Mooyman
parent e2c77c5a8f
commit faa94d51d5
5 changed files with 68 additions and 53 deletions

View File

@ -507,28 +507,28 @@ class Director implements TemplateGlobalProvider {
*/ */
public static function is_https() { public static function is_https() {
$return = false; $return = false;
// See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
// See https://support.microsoft.com/?kbID=307347
$headerOverride = false;
if(TRUSTED_PROXY) {
$headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null;
if(!$headers) {
// Backwards compatible defaults
$headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS');
}
foreach($headers as $header) {
$headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
if(!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
$headerOverride = true;
break;
}
}
}
if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) { if ($protocol = Config::inst()->get('Director', 'alternate_protocol')) {
$return = ($protocol == 'https'); $return = ($protocol == 'https');
} else if( } else if($headerOverride) {
TRUSTED_PROXY
&& isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'
) {
// Convention for (non-standard) proxy signaling a HTTPS forward,
// see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
$return = true;
} else if(
TRUSTED_PROXY
&& isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https'
) {
// Less conventional proxy header
$return = true;
} else if(
isset($_SERVER['HTTP_FRONT_END_HTTPS'])
&& strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) == 'on'
) {
// Microsoft proxy convention: https://support.microsoft.com/?kbID=307347
$return = true; $return = true;
} else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) { } else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
$return = true; $return = true;

View File

@ -655,14 +655,27 @@ class SS_HTTPRequest implements ArrayAccess {
* @return string * @return string
*/ */
public function getIP() { public function getIP() {
if (TRUSTED_PROXY && !empty($_SERVER['HTTP_CLIENT_IP'])) { $headerOverrideIP = null;
//check ip from share internet if(TRUSTED_PROXY) {
return $_SERVER['HTTP_CLIENT_IP']; $headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null;
} elseif (TRUSTED_PROXY && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { if(!$headers) {
//to check ip is pass from proxy // Backwards compatible defaults
return $_SERVER['HTTP_X_FORWARDED_FOR']; $headers = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR');
}
foreach($headers as $header) {
if(!empty($_SERVER[$header])) {
$headerOverrideIP = $_SERVER[$header];
break;
}
}
}
if ($headerOverrideIP) {
return $headerOverrideIP;
} elseif(isset($_SERVER['REMOTE_ADDR'])) { } elseif(isset($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR']; return $_SERVER['REMOTE_ADDR'];
} else {
return null;
} }
} }

View File

@ -179,10 +179,13 @@ if(!isset($_SERVER['HTTP_HOST'])) {
/** /**
* Fix HTTP_HOST from reverse proxies * Fix HTTP_HOST from reverse proxies
*/ */
if (TRUSTED_PROXY && isset($_SERVER['HTTP_X_FORWARDED_HOST'])) { $trustedProxyHeader = (defined('SS_TRUSTED_PROXY_HOST_HEADER'))
? SS_TRUSTED_PROXY_HOST_HEADER
: 'HTTP_X_FORWARDED_HOST';
if (TRUSTED_PROXY && !empty($_SERVER[$trustedProxyHeader])) {
// Get the first host, in case there's multiple separated through commas // Get the first host, in case there's multiple separated through commas
$_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ','); $_SERVER['HTTP_HOST'] = strtok($_SERVER[SS_TRUSTED_PROXY_HOST_HEADER], ',');
} }
} }

View File

@ -163,26 +163,25 @@ class ParameterConfirmationToken {
// Are we http or https? Replicates Director::is_https() without its dependencies/ // Are we http or https? Replicates Director::is_https() without its dependencies/
$proto = 'http'; $proto = 'http';
if( // See https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
TRUSTED_PROXY // See https://support.microsoft.com/?kbID=307347
&& isset($_SERVER['HTTP_X_FORWARDED_PROTO']) $headerOverride = false;
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https' if(TRUSTED_PROXY) {
) { $headers = (defined('SS_TRUSTED_PROXY_PROTOCOL_HEADER')) ? array(SS_TRUSTED_PROXY_PROTOCOL_HEADER) : null;
// Convention for (non-standard) proxy signaling a HTTPS forward, if(!$headers) {
// see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields // Backwards compatible defaults
$proto = 'https'; $headers = array('HTTP_X_FORWARDED_PROTO', 'HTTP_X_FORWARDED_PROTOCOL', 'HTTP_FRONT_END_HTTPS');
} else if( }
TRUSTED_PROXY foreach($headers as $header) {
&& isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) $headerCompareVal = ($header === 'HTTP_FRONT_END_HTTPS' ? 'on' : 'https');
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https' if(!empty($_SERVER[$header]) && strtolower($_SERVER[$header]) == $headerCompareVal) {
) { $headerOverride = true;
// Less conventional proxy header break;
$proto = 'https'; }
} else if( }
isset($_SERVER['HTTP_FRONT_END_HTTPS']) }
&& strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) == 'on'
) { if($headerOverride) {
// Microsoft proxy convention: https://support.microsoft.com/?kbID=307347
$proto = 'https'; $proto = 'https';
} else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) { } else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) {
$proto = 'https'; $proto = 'https';
@ -190,9 +189,6 @@ class ParameterConfirmationToken {
$proto = 'https'; $proto = 'https';
} }
if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) $proto = 'https';
if(isset($_SERVER['SSL'])) $proto = 'https';
$parts = array_filter(array( $parts = array_filter(array(
// What's our host // What's our host
$_SERVER['HTTP_HOST'], $_SERVER['HTTP_HOST'],

View File

@ -580,7 +580,11 @@ server IPs using the SS_TRUSTED_PROXY_IPS define in your _ss_environment.php.
:::php :::php
define('SS_TRUSTED_PROXY_IPS', '127.0.0.1,192.168.0.1'); define('SS_TRUSTED_PROXY_IPS', '127.0.0.1,192.168.0.1');
define('SS_TRUSTED_PROXY_HOST_HEADER', 'HTTP_X_FORWARDED_HOST');
define('SS_TRUSTED_PROXY_IP_HEADER', 'HTTP_X_FORWARDED_FOR');
define('SS_TRUSTED_PROXY_PROTOCOL_HEADER', 'HTTP_X_FORWARDED_PROTOCOL');
At the same time, you'll also need to define which headers you trust from these proxy IPs. Since there are multiple ways through which proxies can pass through HTTP information on the original hostname, IP and protocol, these values need to be adjusted for your specific proxy. The header names match their equivalent `$_SERVER` values.
If there is no proxy server, 'none' can be used to distrust all clients. If there is no proxy server, 'none' can be used to distrust all clients.
If only trusted servers will make requests then you can use '*' to trust all clients. If only trusted servers will make requests then you can use '*' to trust all clients.
@ -603,7 +607,6 @@ In a future release this behaviour will be changed to be on by default, and this
variable will be no longer necessary, thus it will be necessary to always set variable will be no longer necessary, thus it will be necessary to always set
SS_TRUSTED_PROXY_IPS if using a proxy. SS_TRUSTED_PROXY_IPS if using a proxy.
## Related ## Related
* [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/) * [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/)