diff --git a/src/PhpEnvironment/RemoteAddress.php b/src/PhpEnvironment/RemoteAddress.php index b714d46df4..4941b5dca6 100644 --- a/src/PhpEnvironment/RemoteAddress.php +++ b/src/PhpEnvironment/RemoteAddress.php @@ -101,7 +101,7 @@ public function getIpAddress() if ($ip) { return $ip; } - + // direct IP address if (isset($_SERVER['REMOTE_ADDR'])) { return $_SERVER['REMOTE_ADDR']; @@ -113,16 +113,18 @@ public function getIpAddress() /** * Attempt to get the IP address for a proxied client * + * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2 * @return false|string */ protected function getIpAddressFromProxy() { - if (!$this->useProxy) { + if (!$this->useProxy + || !in_array($_SERVER['REMOTE_ADDR'], $this->trustedProxies) + ) { return false; } $header = $this->proxyHeader; - if (!isset($_SERVER[$header]) || empty($_SERVER[$header])) { return false; } @@ -133,14 +135,18 @@ protected function getIpAddressFromProxy() $ips = array_map('trim', $ips); // remove trusted proxy IPs $ips = array_diff($ips, $this->trustedProxies); - + // Any left? if (empty($ips)) { return false; } - // Return right-most - $ip = array_pop($ips); + // Since we've removed any known, trusted proxy servers, the right-most + // address represents the first IP we do not know about -- i.e., we do + // not know if it is a proxy server, or a client. As such, we treat it + // as the originating IP. + // @see http://en.wikipedia.org/wiki/X-Forwarded-For + $ip = array_pop($ips); return $ip; } diff --git a/test/PhpEnvironment/RemoteAddressTest.php b/test/PhpEnvironment/RemoteAddressTest.php new file mode 100644 index 0000000000..219c454815 --- /dev/null +++ b/test/PhpEnvironment/RemoteAddressTest.php @@ -0,0 +1,130 @@ +originalEnvironment = array( + 'post' => $_POST, + 'get' => $_GET, + 'cookie' => $_COOKIE, + 'server' => $_SERVER, + 'env' => $_ENV, + 'files' => $_FILES, + ); + + $_POST = array(); + $_GET = array(); + $_COOKIE = array(); + $_SERVER = array(); + $_ENV = array(); + $_FILES = array(); + + $this->remoteAddress = new RemoteAddr(); + } + + /** + * Restore the original environment + */ + public function tearDown() + { + $_POST = $this->originalEnvironment['post']; + $_GET = $this->originalEnvironment['get']; + $_COOKIE = $this->originalEnvironment['cookie']; + $_SERVER = $this->originalEnvironment['server']; + $_ENV = $this->originalEnvironment['env']; + $_FILES = $this->originalEnvironment['files']; + } + + public function testSetGetUseProxy() + { + $this->remoteAddress->setUseProxy(false); + $this->assertFalse($this->remoteAddress->getUseProxy()); + } + + public function testSetGetDefaultUseProxy() + { + $this->remoteAddress->setUseProxy(); + $this->assertTrue($this->remoteAddress->getUseProxy()); + } + + public function testSetTrustedProxies() + { + $result = $this->remoteAddress->setTrustedProxies(array( + '192.168.0.10', '192.168.0.1' + )); + $this->assertTrue($result instanceOf RemoteAddr); + } + + public function testGetIpAddress() + { + $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; + $this->assertEquals('127.0.0.1', $this->remoteAddress->getIpAddress()); + } + + public function testGetIpAddressFromProxy() + { + $this->remoteAddress->setUseProxy(true); + $this->remoteAddress->setTrustedProxies(array( + '192.168.0.10', '10.0.0.1' + )); + $_SERVER['REMOTE_ADDR'] = '192.168.0.10'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 10.0.0.1'; + $this->assertEquals('8.8.8.8', $this->remoteAddress->getIpAddress()); + } + + public function testGetIpAddressFromProxyRemoteAddressNotTrusted() + { + $this->remoteAddress->setUseProxy(true); + $this->remoteAddress->setTrustedProxies(array( + '10.0.0.1' + )); + // the REMOTE_ADDR is not in the trusted IPs, possible attack here + $_SERVER['REMOTE_ADDR'] = '1.1.1.1'; + $_SERVER['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 10.0.0.1'; + $this->assertEquals('1.1.1.1', $this->remoteAddress->getIpAddress()); + } + + /** + * Test to prevent attack on the HTTP_X_FORWARDED_FOR header + * The client IP is always the first on the left + * + * @see http://tools.ietf.org/html/draft-ietf-appsawg-http-forwarded-10#section-5.2 + */ + public function testGetIpAddressFromProxyFakeData() + { + $this->remoteAddress->setUseProxy(true); + $this->remoteAddress->setTrustedProxies(array( + '192.168.0.10', '10.0.0.1', '10.0.0.2' + )); + $_SERVER['REMOTE_ADDR'] = '192.168.0.10'; + // 1.1.1.1 is the first IP address from the right not representing a known proxy server; as such, we + // must treat it as a client IP. + $_SERVER['HTTP_X_FORWARDED_FOR'] = '8.8.8.8, 10.0.0.2, 1.1.1.1, 10.0.0.1'; + $this->assertEquals('1.1.1.1', $this->remoteAddress->getIpAddress()); + } +}