Skip to content

Proxy Server IP header support #722

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

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Sources/ManageServer.php
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading