mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-08 08:56:23 +08:00
Fix #17573: Request::getUserIP() security fix for the case when Request::$trustedHost and Request::$ipHeaders are used
This commit is contained in:
committed by
Alexander Makarov
parent
ce0c7ad096
commit
c87855b31c
@ -4,7 +4,7 @@ Yii Framework 2 Change Log
|
|||||||
2.0.28 under development
|
2.0.28 under development
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
- no changes in this release.
|
- Bug #17573: `Request::getUserIP()` security fix for the case when `Request::$trustedHost` and `Request::$ipHeaders` are used (kamarton)
|
||||||
|
|
||||||
|
|
||||||
2.0.27 September 18, 2019
|
2.0.27 September 18, 2019
|
||||||
|
|||||||
@ -247,7 +247,7 @@ class IpValidator extends Validator
|
|||||||
*
|
*
|
||||||
* @property array the IPv4 or IPv6 ranges that are allowed or forbidden.
|
* @property array the IPv4 or IPv6 ranges that are allowed or forbidden.
|
||||||
* See [[setRanges()]] for detailed description.
|
* See [[setRanges()]] for detailed description.
|
||||||
* @param array $ranges the IPv4 or IPv6 ranges that are allowed or forbidden.
|
* @param array|string $ranges the IPv4 or IPv6 ranges that are allowed or forbidden.
|
||||||
*
|
*
|
||||||
* When the array is empty, or the option not set, all IP addresses are allowed.
|
* When the array is empty, or the option not set, all IP addresses are allowed.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -290,6 +290,23 @@ class Request extends \yii\base\Request
|
|||||||
* @since 2.0.13
|
* @since 2.0.13
|
||||||
*/
|
*/
|
||||||
protected function filterHeaders(HeaderCollection $headerCollection)
|
protected function filterHeaders(HeaderCollection $headerCollection)
|
||||||
|
{
|
||||||
|
$trustedHeaders = $this->getTrustedIpHeaders();
|
||||||
|
|
||||||
|
// remove all secure headers unless they are trusted
|
||||||
|
foreach ($this->secureHeaders as $secureHeader) {
|
||||||
|
if (!in_array($secureHeader, $trustedHeaders)) {
|
||||||
|
$headerCollection->remove($secureHeader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trusted Ip headers according to the [[trustedHosts]].
|
||||||
|
* @return array
|
||||||
|
* @since 2.0.28
|
||||||
|
*/
|
||||||
|
protected function getTrustedIpHeaders()
|
||||||
{
|
{
|
||||||
// do not trust any of the [[secureHeaders]] by default
|
// do not trust any of the [[secureHeaders]] by default
|
||||||
$trustedHeaders = [];
|
$trustedHeaders = [];
|
||||||
@ -310,13 +327,7 @@ class Request extends \yii\base\Request
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return $trustedHeaders;
|
||||||
// filter all secure headers unless they are trusted
|
|
||||||
foreach ($this->secureHeaders as $secureHeader) {
|
|
||||||
if (!in_array($secureHeader, $trustedHeaders)) {
|
|
||||||
$headerCollection->remove($secureHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1131,15 +1142,62 @@ class Request extends \yii\base\Request
|
|||||||
*/
|
*/
|
||||||
public function getUserIP()
|
public function getUserIP()
|
||||||
{
|
{
|
||||||
foreach ($this->ipHeaders as $ipHeader) {
|
foreach($this->getTrustedIpHeaders() as $ipHeader) {
|
||||||
if ($this->headers->has($ipHeader)) {
|
if ($this->headers->has($ipHeader)) {
|
||||||
return trim(explode(',', $this->headers->get($ipHeader))[0]);
|
$ip = $this->getUserIpFromIpHeader($this->headers->get($ipHeader));
|
||||||
|
if ($ip !== null) {
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->getRemoteIP();
|
return $this->getRemoteIP();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return user IP's from IP header.
|
||||||
|
*
|
||||||
|
* @param string $ips comma separated IP list
|
||||||
|
* @return string|null IP as string. Null is returned if IP can not be determined from header.
|
||||||
|
* @see $getUserHost
|
||||||
|
* @see $ipHeader
|
||||||
|
* @see $trustedHeaders
|
||||||
|
* @since 2.0.28
|
||||||
|
*/
|
||||||
|
protected function getUserIpFromIpHeader($ips)
|
||||||
|
{
|
||||||
|
$ips = trim($ips);
|
||||||
|
if ($ips === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$ips = preg_split('/\s*,\s*/', $ips, -1, PREG_SPLIT_NO_EMPTY);
|
||||||
|
krsort($ips);
|
||||||
|
$validator = $this->getIpValidator();
|
||||||
|
$resultIp = null;
|
||||||
|
foreach ($ips as $ip) {
|
||||||
|
$validator->setRanges('any');
|
||||||
|
if (!$validator->validate($ip) /* checking IP format */) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$resultIp = $ip;
|
||||||
|
$isTrusted = false;
|
||||||
|
foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
|
||||||
|
if (!is_array($trustedCidrOrHeaders)) {
|
||||||
|
$trustedCidr = $trustedCidrOrHeaders;
|
||||||
|
}
|
||||||
|
$validator->setRanges($trustedCidr);
|
||||||
|
if ($validator->validate($ip) /* checking trusted range */) {
|
||||||
|
$isTrusted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$isTrusted) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $resultIp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the user host name.
|
* Returns the user host name.
|
||||||
* The HOST is determined using headers and / or `$_SERVER` variables.
|
* The HOST is determined using headers and / or `$_SERVER` variables.
|
||||||
|
|||||||
@ -725,6 +725,38 @@ class RequestTest extends TestCase
|
|||||||
$this->assertSame('default', $request->getBodyParam('unexisting', 'default'));
|
$this->assertSame('default', $request->getBodyParam('unexisting', 'default'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function trustedHostAndInjectedXForwardedForDataProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'emptyIPs' => ['1.1.1.1', '', ['10.10.10.10'], '1.1.1.1'],
|
||||||
|
'invalidIp' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, apple', ['10.10.10.10'], '1.1.1.1'],
|
||||||
|
'invalidIp2' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, 300.300.300.300', ['10.10.10.10'], '1.1.1.1'],
|
||||||
|
'invalidIp3' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, 10.0.0.0/26', ['10.0.0.0/24'], '1.1.1.1'],
|
||||||
|
'invalidLatestIp' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2, apple, 2.2.2.2', ['1.1.1.1', '2.2.2.2'], '2.2.2.2'],
|
||||||
|
'notTrusted' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', ['10.10.10.10'], '1.1.1.1'],
|
||||||
|
'trustedLevel1' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', ['1.1.1.1'], '2.2.2.2'],
|
||||||
|
'trustedLevel2' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', ['1.1.1.1', '2.2.2.2'], '8.8.8.8'],
|
||||||
|
'trustedLevel3' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', ['1.1.1.1', '2.2.2.2', '8.8.8.8'], '127.0.0.1'],
|
||||||
|
'trustedLevel4' => ['1.1.1.1', '127.0.0.1, 8.8.8.8, 2.2.2.2', ['1.1.1.1', '2.2.2.2', '8.8.8.8', '127.0.0.1'], '127.0.0.1'],
|
||||||
|
'trustedLevel4EmptyElements' => ['1.1.1.1', '127.0.0.1, 8.8.8.8,,,, , , 2.2.2.2', ['1.1.1.1', '2.2.2.2', '8.8.8.8', '127.0.0.1'], '127.0.0.1'],
|
||||||
|
'trustedWithCidr' => ['10.0.0.2', '127.0.0.1, 8.8.8.8, 10.0.0.240, 10.0.0.32, 10.0.0.99', ['10.0.0.0/24'], '8.8.8.8'],
|
||||||
|
'trustedAll' => ['10.0.0.2', '127.0.0.1, 8.8.8.8, 10.0.0.240, 10.0.0.32, 10.0.0.99', ['0.0.0.0/0'], '127.0.0.1'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider trustedHostAndInjectedXForwardedForDataProvider
|
||||||
|
*/
|
||||||
|
public function testTrustedHostAndInjectedXForwardedFor($remoteAddress, $xForwardedFor, $trustedHosts, $expectedUserIp)
|
||||||
|
{
|
||||||
|
$_SERVER['REMOTE_ADDR'] = $remoteAddress;
|
||||||
|
$_SERVER['HTTP_X_FORWARDED_FOR'] = $xForwardedFor;
|
||||||
|
$request = new Request([
|
||||||
|
'trustedHosts' => $trustedHosts,
|
||||||
|
]);
|
||||||
|
$this->assertSame($expectedUserIp, $request->getUserIP());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @testWith ["POST", "GET", "POST"]
|
* @testWith ["POST", "GET", "POST"]
|
||||||
* ["POST", "OPTIONS", "POST"]
|
* ["POST", "OPTIONS", "POST"]
|
||||||
|
|||||||
Reference in New Issue
Block a user