diff --git a/control/Director.php b/control/Director.php index 07948d631..5c0eeb108 100644 --- a/control/Director.php +++ b/control/Director.php @@ -492,28 +492,28 @@ class Director implements TemplateGlobalProvider { */ public static function is_https() { $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')) { $return = ($protocol == 'https'); - } else if( - 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 + } else if($headerOverride) { $return = true; } else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) { $return = true; diff --git a/control/HTTPRequest.php b/control/HTTPRequest.php index 9b8506baa..21e5db00e 100644 --- a/control/HTTPRequest.php +++ b/control/HTTPRequest.php @@ -653,14 +653,27 @@ class SS_HTTPRequest implements ArrayAccess { * @return string */ public function getIP() { - if (TRUSTED_PROXY && !empty($_SERVER['HTTP_CLIENT_IP'])) { - //check ip from share internet - return $_SERVER['HTTP_CLIENT_IP']; - } elseif (TRUSTED_PROXY && !empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - //to check ip is pass from proxy - return $_SERVER['HTTP_X_FORWARDED_FOR']; + $headerOverrideIP = null; + if(TRUSTED_PROXY) { + $headers = (defined('SS_TRUSTED_PROXY_IP_HEADER')) ? array(SS_TRUSTED_PROXY_IP_HEADER) : null; + if(!$headers) { + // Backwards compatible defaults + $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'])) { return $_SERVER['REMOTE_ADDR']; + } else { + return null; } } diff --git a/core/Constants.php b/core/Constants.php index ad6e45c07..70d698623 100644 --- a/core/Constants.php +++ b/core/Constants.php @@ -177,10 +177,13 @@ if(!isset($_SERVER['HTTP_HOST'])) { /** * 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 - $_SERVER['HTTP_HOST'] = strtok($_SERVER['HTTP_X_FORWARDED_HOST'], ','); + $_SERVER['HTTP_HOST'] = strtok($_SERVER[SS_TRUSTED_PROXY_HOST_HEADER], ','); } } diff --git a/core/startup/ParameterConfirmationToken.php b/core/startup/ParameterConfirmationToken.php index 55851c601..d65c32c4d 100644 --- a/core/startup/ParameterConfirmationToken.php +++ b/core/startup/ParameterConfirmationToken.php @@ -160,26 +160,25 @@ class ParameterConfirmationToken { // Are we http or https? Replicates Director::is_https() without its dependencies/ $proto = 'http'; - if( - 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 - $proto = 'https'; - } else if( - TRUSTED_PROXY - && isset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) - && strtolower($_SERVER['HTTP_X_FORWARDED_PROTOCOL']) == 'https' - ) { - // Less conventional proxy header - $proto = 'https'; - } else if( - isset($_SERVER['HTTP_FRONT_END_HTTPS']) - && strtolower($_SERVER['HTTP_FRONT_END_HTTPS']) == 'on' - ) { - // Microsoft proxy convention: https://support.microsoft.com/?kbID=307347 + // 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($headerOverride) { $proto = 'https'; } else if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) { $proto = 'https'; @@ -187,9 +186,6 @@ class ParameterConfirmationToken { $proto = 'https'; } - if((!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) $proto = 'https'; - if(isset($_SERVER['SSL'])) $proto = 'https'; - $parts = array_filter(array( // What's our host $_SERVER['HTTP_HOST'], diff --git a/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md b/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md index 5d558fac0..c6ee5af94 100644 --- a/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md +++ b/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md @@ -502,7 +502,11 @@ server IPs using the SS_TRUSTED_PROXY_IPS define in your _ss_environment.php. :::php 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 only trusted servers will make requests then you can use '*' to trust all clients. @@ -525,7 +529,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 SS_TRUSTED_PROXY_IPS if using a proxy. - ## Related * [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/)