From cf6766f096261e1d0de59f227f5ee2c73e726051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wrzeszcz?= Date: Tue, 17 Jan 2012 17:43:00 +0100 Subject: [PATCH 1/4] Addres support for proxy-forwarded IPs. --- src/Validator/RemoteAddr.php | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Validator/RemoteAddr.php b/src/Validator/RemoteAddr.php index 5b9207e5..d2022392 100644 --- a/src/Validator/RemoteAddr.php +++ b/src/Validator/RemoteAddr.php @@ -48,9 +48,7 @@ class RemoteAddr implements SessionValidator public function __construct($data = null) { if (empty($data)) { - $data = isset($_SERVER['REMOTE_ADDR']) - ? sprintf('%u', ip2long($_SERVER['REMOTE_ADDR'])) - : 0; + $data = $this->getIpAddress(); } $this->data = $data; } @@ -63,11 +61,27 @@ public function __construct($data = null) */ public function isValid() { - $userAgent = isset($_SERVER['REMOTE_ADDR']) - ? sprintf('%u', ip2long($_SERVER['REMOTE_ADDR'])) - : 0; + return $this->getIpAddress() === $this->getData(); + } + + /** + * Returns client IP address. + * + * @return int IP address (converted to integer). + */ + protected function getIpAddress() + { + // proxy IP address + if (isset($_SERVER['X_FORWORDED_FOR'])) { + return sprintf('%u', ip2long($_SERVER['X_FORWORDED_FOR'])); + } + + // direct IP address + if (isset($_SERVER['REMOTE_ADDR'])) { + return sprintf('%u', ip2long($_SERVER['REMOTE_ADDR'])); + } - return $userAgent === $this->getData(); + return 0; } /** From ca6583c476fe408a7224b99dba61e1cdd7c8e382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wrzeszcz?= Date: Tue, 24 Jan 2012 17:12:26 +0100 Subject: [PATCH 2/4] Fixed typo. --- src/Validator/RemoteAddr.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Validator/RemoteAddr.php b/src/Validator/RemoteAddr.php index d2022392..a54a7951 100644 --- a/src/Validator/RemoteAddr.php +++ b/src/Validator/RemoteAddr.php @@ -72,8 +72,8 @@ public function isValid() protected function getIpAddress() { // proxy IP address - if (isset($_SERVER['X_FORWORDED_FOR'])) { - return sprintf('%u', ip2long($_SERVER['X_FORWORDED_FOR'])); + if (isset($_SERVER['X_FORWARDED_FOR'])) { + return sprintf('%u', ip2long($_SERVER['X_FORWARDED_FOR'])); } // direct IP address From bba0c8df0d99e0927a4cf45377a3871cb62091d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wrzeszcz?= Date: Tue, 31 Jan 2012 12:38:09 +0100 Subject: [PATCH 3/4] Better HTTP proxy handling. Also ability to disable proxy handling. --- src/Validator/RemoteAddr.php | 57 ++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/src/Validator/RemoteAddr.php b/src/Validator/RemoteAddr.php index a54a7951..d861f303 100644 --- a/src/Validator/RemoteAddr.php +++ b/src/Validator/RemoteAddr.php @@ -39,6 +39,18 @@ class RemoteAddr implements SessionValidator */ protected $data; + /** + * Whether to use proxy addresses or not. + * + * As default this setting is disabled - IP address is mostly needed to increase + * security. HTTP_* are not reliable since can easily be spoofed. It can be enabled + * just for more flexibility, but if user uses proxy to connect to trusted services + * it's his/her own risk, only reliable field for IP address is $_SERVER['REMOTE_ADDR']. + * + * @var bool + */ + protected static $useProxy = false; + /** * Constructor - get the current user IP and store it in the session * as 'valid data' @@ -64,24 +76,57 @@ public function isValid() return $this->getIpAddress() === $this->getData(); } + /** + * Changes proxy handling setting. + * + * This must be static method, since validators are recovered automatically + * at session read, so this is the only way to switch setting. + * + * @param bool $useProxy Whether to check also proxied IP addresses. + * @return void + */ + public static function setUseProxy($useProxy = true) + { + self::$useProxy = $useProxy; + } + + /** + * Checks proxy handling setting. + * + * @return bool Current setting value. + */ + public static function getUseProxy() + { + return self::$useProxy; + } + /** * Returns client IP address. * - * @return int IP address (converted to integer). + * @return string IP address. */ protected function getIpAddress() { - // proxy IP address - if (isset($_SERVER['X_FORWARDED_FOR'])) { - return sprintf('%u', ip2long($_SERVER['X_FORWARDED_FOR'])); + if (self::$useProxy) { + // proxy IP address + if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP']) { + $ips = explode(',', $_SERVER['HTTP_CLIENT_IP']); + return trim($ips[0]); + } + + // proxy IP address + if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) { + $ips = explode(',', $_SERVER['X_FORWARDED_FOR']); + return trim($ips[0]); + } } // direct IP address if (isset($_SERVER['REMOTE_ADDR'])) { - return sprintf('%u', ip2long($_SERVER['REMOTE_ADDR'])); + return $_SERVER['REMOTE_ADDR']; } - return 0; + return ''; } /** From 6819f62709514f64352872b6c9527a1b15b33d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Wrzeszcz?= Date: Wed, 1 Feb 2012 18:13:48 +0100 Subject: [PATCH 4/4] Unit tests. --- src/Validator/RemoteAddr.php | 2 +- test/Validator/RemoteAddrTest.php | 130 ++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 test/Validator/RemoteAddrTest.php diff --git a/src/Validator/RemoteAddr.php b/src/Validator/RemoteAddr.php index d861f303..82b62946 100644 --- a/src/Validator/RemoteAddr.php +++ b/src/Validator/RemoteAddr.php @@ -116,7 +116,7 @@ protected function getIpAddress() // proxy IP address if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) { - $ips = explode(',', $_SERVER['X_FORWARDED_FOR']); + $ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); return trim($ips[0]); } } diff --git a/test/Validator/RemoteAddrTest.php b/test/Validator/RemoteAddrTest.php new file mode 100644 index 00000000..b872eb08 --- /dev/null +++ b/test/Validator/RemoteAddrTest.php @@ -0,0 +1,130 @@ +backup = $_SERVER; + unset( + $_SERVER['REMOTE_ADDR'], + $_SERVER['HTTP_X_FORWARDED_FOR'], + $_SERVER['HTTP_CLIENT_IP'] + ); + } + + protected function restore() + { + $_SERVER = $this->backup; + } + + public function testGetData() + { + $validator = new RemoteAddr('0.1.2.3'); + $this->assertEquals('0.1.2.3', $validator->getData()); + } + + public function testDefaultUseProxy() + { + $this->assertFalse(RemoteAddr::getUseProxy()); + } + + public function testRemoteAddrWithoutProxy() + { + $this->backup(); + $_SERVER['REMOTE_ADDR'] = '0.1.2.3'; + $validator = new RemoteAddr(); + $this->assertEquals('0.1.2.3', $validator->getData()); + $this->restore(); + } + + public function testIsValid() + { + $this->backup(); + $_SERVER['REMOTE_ADDR'] = '0.1.2.3'; + $validator = new RemoteAddr(); + $_SERVER['REMOTE_ADDR'] = '1.1.2.3'; + $this->assertFalse($validator->isValid()); + $this->restore(); + } + + public function testIgnoreProxyByDefault() + { + $this->backup(); + $_SERVER['REMOTE_ADDR'] = '0.1.2.3'; + $_SERVER['HTTP_CLIENT_IP'] = '1.1.2.3'; + $validator = new RemoteAddr(); + $this->assertEquals('0.1.2.3', $validator->getData()); + $this->restore(); + } + + public function testHttpXForwardedFor() + { + $this->backup(); + $_SERVER['REMOTE_ADDR'] = '0.1.2.3'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '1.1.2.3'; + RemoteAddr::setUseProxy(true); + $validator = new RemoteAddr(); + RemoteAddr::setUseProxy(false); + $this->assertEquals('1.1.2.3', $validator->getData()); + $this->restore(); + } + + public function testHttpClientIp() + { + $this->backup(); + $_SERVER['REMOTE_ADDR'] = '0.1.2.3'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '1.1.2.3'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '2.1.2.3'; + RemoteAddr::setUseProxy(true); + $validator = new RemoteAddr(); + RemoteAddr::setUseProxy(false); + $this->assertEquals('2.1.2.3', $validator->getData()); + $this->restore(); + } + + public function testMultipleHttpXForwardedFor() + { + $this->backup(); + $_SERVER['REMOTE_ADDR'] = '0.1.2.3'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '2.1.2.3, 1.1.2.3'; + RemoteAddr::setUseProxy(true); + $validator = new RemoteAddr(); + RemoteAddr::setUseProxy(false); + $this->assertEquals('2.1.2.3', $validator->getData()); + $this->restore(); + } +}