Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy Server IP header support #722

Merged
merged 4 commits into from Apr 12, 2015
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/ManageServer.php
Expand Up @@ -368,6 +368,9 @@ function ModifyGeneralSecuritySettings($return_config = false)
array('check', 'enableReportPM'),
'',
array('select', 'frame_security', array('SAMEORIGIN' => $txt['setting_frame_security_SAMEORIGIN'], 'DENY' => $txt['setting_frame_security_DENY'], 'DISABLE' => $txt['setting_frame_security_DISABLE'])),
'',
array('select', 'proxy_ip_header', array('disabled' => $txt['setting_proxy_ip_header_disabled'], 'autodetect' => $txt['setting_proxy_ip_header_autodetect'], 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'CF-Connecting-IP')),
array('text', 'proxy_ip_servers'),
);

call_integration_hook('integrate_general_security_settings', array(&$config_vars));
Expand Down
81 changes: 59 additions & 22 deletions Sources/QueryString.php
Expand Up @@ -232,46 +232,69 @@ function cleanRequest()
// Try to calculate their most likely IP for those people behind proxies (And the like).
$_SERVER['BAN_CHECK_IP'] = $_SERVER['REMOTE_ADDR'];

// If we haven't specified how to handle Reverse Proxy IP headers, lets do what we always used to do.
if (!isset($modSettings['proxy_ip_header']))
$modSettings['proxy_ip_header'] = 'autodetect';

// Which headers are we going to check for Reverse Proxy IP headers?
if ($modSettings['proxy_ip_header'] = 'disabled')
$reverseIPheaders = array();
elseif ($modSettings['proxy_ip_header'] = 'autodetect')
$reverseIPheaders = array('HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP');
else
$reverseIPheaders = array($modSettings['proxy_ip_header']);

// Find the user's IP address. (but don't let it give you 'unknown'!)
// @ TODO: IPv6 really doesn't need this.
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && !empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0))
{
// We have both forwarded for AND client IP... check the first forwarded for as the block - only switch if it's better that way.
if (strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.') && '.' . strtok($_SERVER['HTTP_X_FORWARDED_FOR'], '.') == strrchr($_SERVER['HTTP_CLIENT_IP'], '.') && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown)~', $_SERVER['REMOTE_ADDR']) != 0))
$_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])));
else
$_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP'];
}
if (!empty($_SERVER['HTTP_CLIENT_IP']) && (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_CLIENT_IP']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0))
{
// Since they are in different blocks, it's probably reversed.
if (strtok($_SERVER['REMOTE_ADDR'], '.') != strtok($_SERVER['HTTP_CLIENT_IP'], '.'))
$_SERVER['BAN_CHECK_IP'] = implode('.', array_reverse(explode('.', $_SERVER['HTTP_CLIENT_IP'])));
else
$_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_CLIENT_IP'];
}
elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
foreach ($reverseIPheaders as $proxyIPheader)
{
if (isset($modSettings['proxy_ip_servers']))
{
foreach (explode(',', $modSettings['proxy_ip_servers']) as $proxy)
if ($proxy == $_SERVER['REMOTE_ADDR'] || matchIPtoCIDR($_SERVER['REMOTE_ADDR'], $proxy))
continue;
}

// If there are commas, get the last one.. probably.
if (strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',') !== false)
if (strpos($_SERVER[$proxyIPheader], ',') !== false)
{
$ips = array_reverse(explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']));
$ips = array_reverse(explode(', ', $_SERVER[$proxyIPheader]));

// Go through each IP...
foreach ($ips as $i => $ip)
{
// Make sure it's in a valid range...
if (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $ip) != 0 && preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) == 0)
{
if (!isValidIPv6($_SERVER[$proxyIPheader]) || preg_match('~::ffff:\d+\.\d+\.\d+\.\d+~', $_SERVER[$proxyIPheader]) !== 0)
{
$_SERVER[$proxyIPheader] = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER[$proxyIPheader]);

// Just incase we have a legacy IPv4 address.
// @ TODO: Convert to IPv6.
if (preg_match('~^((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|25[0-5])$~', $_SERVER[$proxyIPheader]) === 0)
continue;
}

continue;
}

// Otherwise, we've got an IP!
$_SERVER['BAN_CHECK_IP'] = trim($ip);
break;
}
}
// Otherwise just use the only one.
elseif (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['HTTP_X_FORWARDED_FOR']) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0)
$_SERVER['BAN_CHECK_IP'] = $_SERVER['HTTP_X_FORWARDED_FOR'];
elseif (preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER[$proxyIPheader]) == 0 || preg_match('~^((0|10|172\.(1[6-9]|2[0-9]|3[01])|192\.168|255|127)\.|unknown|::1|fe80::|fc00::)~', $_SERVER['REMOTE_ADDR']) != 0)
$_SERVER['BAN_CHECK_IP'] = $_SERVER[$proxyIPheader];
elseif (!isValidIPv6($_SERVER[$proxyIPheader]) || preg_match('~::ffff:\d+\.\d+\.\d+\.\d+~', $_SERVER[$proxyIPheader]) !== 0)
{
$_SERVER[$proxyIPheader] = preg_replace('~^::ffff:(\d+\.\d+\.\d+\.\d+)~', '\1', $_SERVER[$proxyIPheader]);

// Just incase we have a legacy IPv4 address.
// @ TODO: Convert to IPv6.
if (preg_match('~^((([1]?\d)?\d|2[0-4]\d|25[0-5])\.){3}(([1]?\d)?\d|2[0-4]\d|25[0-5])$~', $_SERVER[$proxyIPheader]) === 0)
continue;
}
}

// Make sure we know the URL of the current request.
Expand Down Expand Up @@ -385,6 +408,20 @@ function expandIPv6($addr, $strict_check = true)
}


/**
* Detect if a IP is in a CIDR address
* - returns true or false
*
* @param string IP address to check
* @param string CIDR address to verify
* @return bool
*/
function matchIPtoCIDR($ip_address, $cidr_address)
{
list ($cidr_network, $cidr_subnetmask) = split('/', $cidr_address);
return (ip2long($ip_address) & (~((1 << (32 - $cidr_subnetmask)) - 1))) == ip2long($cidr_network);
}

/**
* Adds slashes to the array/variable.
* What it does:
Expand Down