Permalink
Browse files

Add Trusted Proxies and LAN Options

* HTTP Option: TRUSTED_PROXIES (default: <none>
To support running osTicket installation on a web servers that sit behind a
load balancer, HTTP cache, or other intermediary (reverse) proxy; it's
necessary to define trusted proxies to protect against forged http headers.

* HTTP Option: LOCAL_NETWORKS (default: 127.0.0.0/24)
When running osTicket as part of a cluster it might become necessary to
white list local/virtual networks that can bypass some authentication
checks.

* Validate CLIENT_IP to make sure it's a valid IP address.
  • Loading branch information...
protich committed Oct 31, 2016
1 parent 2fb47bd commit 4396f91cdc990b7da598a7562eb634b89314b631
View
@@ -50,11 +50,7 @@ static function init() {
}
function https() {
return
(isset($_SERVER['HTTPS'])
&& strtolower($_SERVER['HTTPS']) == 'on')
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https');
return osTicket::is_https();
}
static function defineTables($prefix) {
@@ -335,6 +331,7 @@ function croak($message) {
require(INCLUDE_DIR.'class.osticket.php');
require(INCLUDE_DIR.'class.misc.php');
require(INCLUDE_DIR.'class.http.php');
require(INCLUDE_DIR.'class.validator.php');
// Determine the path in the URI used as the base of the osTicket
// installation
@@ -346,12 +343,7 @@ function croak($message) {
#CURRENT EXECUTING SCRIPT.
define('THISPAGE', Misc::currentURL());
define('DEFAULT_MAX_FILE_UPLOADS',ini_get('max_file_uploads')?ini_get('max_file_uploads'):5);
define('DEFAULT_PRIORITY_ID',1);
define('DEFAULT_MAX_FILE_UPLOADS', ini_get('max_file_uploads') ?: 5);
define('DEFAULT_PRIORITY_ID', 1);
#Global override
if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
// Take the left-most item for X-Forwarded-For
$_SERVER['REMOTE_ADDR'] = trim(array_pop(
explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])));
?>
View
@@ -444,6 +444,37 @@ function getLatestVersion($product='core', $major=null) {
}
}
/*
* getTrustedProxies
*
* Get defined trusted proxies
*/
static function getTrustedProxies() {
static $proxies = null;
// Parse trusted proxies from config file
if (!isset($proxies) && defined('TRUSTED_PROXIES'))
$proxies = array_filter(
array_map('trim', explode(',', TRUSTED_PROXIES)));
return $proxies ?: array();
}
/*
* getLocalNetworkAddresses
*
* Get defined local network addresses
*/
static function getLocalNetworkAddresses() {
static $ips = null;
// Parse local addreses from config file
if (!isset($ips) && defined('LOCAL_NETWORKS'))
$ips = array_filter(
array_map('trim', explode(',', LOCAL_NETWORKS)));
return $ips ?: array();
}
static function get_root_path($dir) {
/* If run from the commandline, DOCUMENT_ROOT will not be set. It is
@@ -488,14 +519,96 @@ static function get_root_path($dir) {
return null;
}
/*
* get_client_ip
*
* Get client IP address from "Http_X-Forwarded-For" header by following a
* chain of trusted proxies.
*
* "Http_X-Forwarded-For" header value is a comma+space separated list of IP
* addresses, the left-most being the original client, and each successive
* proxy that passed the request all the way to the originating IP address.
*
*/
static function get_client_ip($header='HTTP_X_FORWARDED_FOR') {
// Request IP
$ip = $_SERVER['REMOTE_ADDR'];
// Trusted proxies.
$proxies = self::getTrustedProxies();
// Return current IP address if header is not set and
// request is not from a trusted proxy.
if (!isset($_SERVER[$header])
|| !$proxies
|| !self::is_trusted_proxy($ip, $proxies))
return $ip;
// Get chain of proxied ip addresses
$ips = array_map('trim', explode(',', $_SERVER[$header]));
// Add request IP to the chain
$ips[] = $ip;
// Walk the chain in reverse - remove invalid IPs
$ips = array_reverse($ips);
foreach ($ips as $k => $ip) {
// Make sure the IP is valid and not a trusted proxy
if ($k && !Validator::is_ip($ip))
unset($ips[$k]);
elseif ($k && !self::is_trusted_proxy($ip, $proxies))
return $ip;
}
// We trust the 400 lb hacker... return left most valid IP
return array_pop($ips);
}
/*
* Checks if the IP is that of a trusted proxy
*
*/
static function is_trusted_proxy($ip, $proxies=array()) {
$proxies = $proxies ?: self::getTrustedProxies();
// We don't have any proxies set.
if (!$proxies)
return false;
// Wildcard set - trust all proxies
else if ($proxies == '*')
return true;
return ($proxies && Validator::check_ip($ip, $proxies));
}
/**
* is_local_ip
*
* Check if a given IP is part of defined local address blocks
*
*/
static function is_local_ip($ip, $ips=array()) {
$ips = $ips
?: self::getLocalNetworkAddresses()
?: array();
foreach ($ips as $addr) {
if (Validator::check_ip($ip, $addr))
return true;
}
return false;
}
/**
* Returns TRUE if the request was made via HTTPS and false otherwise
*/
function is_https() {
return (isset($_SERVER['HTTPS'])
// Local server flags
if (isset($_SERVER['HTTPS'])
&& strtolower($_SERVER['HTTPS']) == 'on')
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https');
return true;
// Check if SSL was terminated by a loadbalancer
return (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& !strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https'));
}
/* returns true if script is being executed via commandline */
View
@@ -185,23 +185,7 @@ static function is_url($url) {
}
static function is_ip($ip) {
if(!$ip or empty($ip))
return false;
$ip=trim($ip);
# Thanks to http://stackoverflow.com/a/1934546
if (function_exists('inet_pton')) { # PHP 5.1.0
# Let the built-in library parse the IP address
return @inet_pton($ip) !== false;
} else if (preg_match(
'/^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){7,})'
.'((?1)(?>:(?1)){0,5})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){5,})'
.'(?3)?::(?>((?1)(?>:(?1)){0,3}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])'
.'(?>\.(?4)){3}))$/iD', $ip)) {
return true;
}
return false;
return filter_var(trim($ip), FILTER_VALIDATE_IP) !== false;
}
static function is_username($username, &$error='') {
@@ -212,6 +196,100 @@ static function is_username($username, &$error='') {
return $error == '';
}
/*
* check_ip
* Checks if an IP (IPv4 or IPv6) address is contained in the list of given IPs or subnets.
*
* @credit - borrowed from Symfony project
*
*/
public static function check_ip($ip, $ips) {
if (!Validator::is_ip($ip))
return false;
$method = substr_count($ip, ':') > 1 ? 'check_ipv6' : 'check_ipv4';
$ips = is_array($ips) ? $ips : array($ips);
foreach ($ips as $_ip) {
if (self::$method($ip, $_ip)) {
return true;
}
}
return false;
}
/**
* check_ipv4
* Compares two IPv4 addresses.
* In case a subnet is given, it checks if it contains the request IP.
*
* @credit - borrowed from Symfony project
*/
public static function check_ipv4($ip, $cidr) {
if (false !== strpos($cidr, '/')) {
list($address, $netmask) = explode('/', $cidr, 2);
if ($netmask === '0')
return filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
if ($netmask < 0 || $netmask > 32)
return false;
} else {
$address = $cidr;
$netmask = 32;
}
return 0 === substr_compare(
sprintf('%032b', ip2long($ip)),
sprintf('%032b', ip2long($address)),
0, $netmask);
}
/**
* Compares two IPv6 addresses.
* In case a subnet is given, it checks if it contains the request IP.
*
* @credit - borrowed from Symfony project
* @author David Soria Parra <dsp at php dot net>
*
* @see https://github.com/dsp/v6tools
*
*/
public static function check_ipv6($ip, $cidr) {
if (!((extension_loaded('sockets') && defined('AF_INET6')) || @inet_pton('::1')))
return false;
if (false !== strpos($cidr, '/')) {
list($address, $netmask) = explode('/', $cidr, 2);
if ($netmask < 1 || $netmask > 128)
return false;
} else {
$address = $cidr;
$netmask = 128;
}
$bytesAddr = unpack('n*', @inet_pton($address));
$bytesTest = unpack('n*', @inet_pton($ip));
if (!$bytesAddr || !$bytesTest)
return false;
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
$left = $netmask - 16 * ($i - 1);
$left = ($left <= 16) ? $left : 16;
$mask = ~(0xffff >> $left) & 0xffff;
if (($bytesAddr[$i] & $mask) != ($bytesTest[$i] & $mask)) {
return false;
}
}
return true;
}
function process($fields,$vars,&$errors){
$val = new Validator();
@@ -107,6 +107,39 @@
# define('ROOT_PATH', '/support/');
# Option: TRUSTED_PROXIES (default: <none>)
#
# To support running osTicket installation on a web servers that sit behind a
# load balancer, HTTP cache, or other intermediary (reverse) proxy; it's
# necessary to define trusted proxies to protect against forged http headers
#
# osTicket supports passing the following http headers from a trusted proxy;
# - HTTP_X_FORWARDED_FOR => Chain of client's IPs
# - HTTP_X_FORWARDED_PROTO => Client's HTTP protocal (http | https)
#
# You'll have to explicitly define comma separated IP addreseses or CIDR of
# upstream proxies to trust. Wildcard "*" (not recommended) can be used to
# trust all chained IPs as proxies in cases that ISP/host doesn't provide
# IPs of loadbalancers or proxies.
#
# References:
# http://en.wikipedia.org/wiki/X-Forwarded-For
#
define('TRUSTED_PROXIES', '');
# Option: LOCAL_NETWORKS (default: 127.0.0.0/24)
#
# When running osTicket as part of a cluster it might become necessary to
# whitelist local/virtual networks that can bypass some authentication/checks.
#
# define comma separated IP addreseses or enter CIDR of local network.
define('LOCAL_NETWORKS', '127.0.0.0/24');
#
# Session Storage Options
# ---------------------------------------------------
@@ -156,7 +156,7 @@
<td>&nbsp;<a class="tip" href="#log/<?php echo $row['log_id']; ?>"><?php echo Format::htmlchars($row['title']); ?></a></td>
<td><?php echo $row['log_type']; ?></td>
<td>&nbsp;<?php echo Format::daydatetime($row['created']); ?></td>
<td><?php echo $row['ip_address']; ?></td>
<td><?php echo Format::htmlchars($row['ip_address']); ?></td>
</tr>
<?php
} //end of while.
@@ -355,7 +355,7 @@ class="icon-building icon-fixed-width"></i> <?php
echo Format::htmlchars($ticket->getSource());
if (!strcasecmp($ticket->getSource(), 'Web') && $ticket->getIP())
echo '&nbsp;&nbsp; <span class="faded">('.$ticket->getIP().')</span>';
echo '&nbsp;&nbsp; <span class="faded">('.Format::htmlchars($ticket->getIP()).')</span>';
?>
</td>
</tr>
View
@@ -27,6 +27,9 @@
Bootstrap::loadCode();
Bootstrap::connect();
#Global override
$_SERVER['REMOTE_ADDR'] = osTicket::get_client_ip();
if(!($ost=osTicket::start()) || !($cfg = $ost->getConfig()))
Bootstrap::croak(__('Unable to load config info from DB. Get tech support.'));
@@ -54,6 +54,19 @@ function testValidEmail() {
#$this->assert(Validator::is_email('δοκιμή@παράδειγμα.δοκιμή'));
#$this->assert(Validator::is_email('甲斐@黒川.日本'));
}
function testIPAddresses() {
// Validate IP Addreses
$this->assert(Validator::is_ip('127.0.0.1'));
$this->assert(Validator::is_ip('192.168.129.74'));
// Test IP check
$this->assert(Validator::check_ip('127.0.0.1', '127.0.0.0/24'));
$this->assert(Validator::check_ip('192.168.129.42',
['127.0.0.0/24', '192.168.129.0/24']));
$this->assert(!Validator::check_ip('10.0.5.15', '127.0.0.0/24'));
}
}
return 'TestValidation';
?>

0 comments on commit 4396f91

Please sign in to comment.