forked from yiisoft/yii2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
created
HostControl
filter to prevent Host header attacks
fixes yiisoft#13050 close yiisoft#13063
- Loading branch information
1 parent
0a9ffc0
commit 7da77c3
Showing
6 changed files
with
296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
<?php | ||
/** | ||
* @link http://www.yiiframework.com/ | ||
* @copyright Copyright (c) 2008 Yii Software LLC | ||
* @license http://www.yiiframework.com/license/ | ||
*/ | ||
|
||
namespace yii\filters; | ||
|
||
use Yii; | ||
use yii\base\ActionFilter; | ||
use yii\web\NotFoundHttpException; | ||
|
||
/** | ||
* HostControl provides simple control over requested host name. | ||
* | ||
* This filter provides protection against ['host header' attacks](https://www.acunetix.com/vulnerabilities/web/host-header-attack), | ||
* allowing action execution only for specified host names. | ||
* | ||
* Application configuration example: | ||
* | ||
* ```php | ||
* return [ | ||
* 'as hostControl' => [ | ||
* 'class' => 'yii\filters\HostControl', | ||
* 'allowedHosts' => [ | ||
* 'example.com', | ||
* '*.example.com', | ||
* ], | ||
* ], | ||
* // ... | ||
* ]; | ||
* ``` | ||
* | ||
* Controller configuration example: | ||
* | ||
* ```php | ||
* use yii\web\Controller; | ||
* use yii\filters\HostControl; | ||
* | ||
* class SiteController extends Controller | ||
* { | ||
* public function behaviors() | ||
* { | ||
* return [ | ||
* 'hostControl' => [ | ||
* 'class' => HostControl::className(), | ||
* 'allowedHosts' => [ | ||
* 'example.com', | ||
* '*.example.com', | ||
* ], | ||
* ], | ||
* ]; | ||
* } | ||
* | ||
* // ... | ||
* } | ||
* ``` | ||
* | ||
* > Note: the best way to restrict allowed host names is usage of the web server 'virtual hosts' configuration. | ||
* This filter should be used only if this configuration is not available or compromised. | ||
* | ||
* @author Paul Klimov <[email protected]> | ||
* @since 2.0.11 | ||
*/ | ||
class HostControl extends ActionFilter | ||
{ | ||
/** | ||
* @var array|\Closure|null list of host names, which are allowed. | ||
* Each host can be specified as a wildcard pattern. For example: | ||
* | ||
* ```php | ||
* [ | ||
* 'example.com', | ||
* '*.example.com', | ||
* ] | ||
* ``` | ||
* | ||
* This field can be specified as a PHP callback of following signature: | ||
* | ||
* ```php | ||
* function (\yii\base\Action $action) { | ||
* //return array of strings | ||
* } | ||
* ``` | ||
* | ||
* where `$action` is the current [[\yii\base\Action|action]] object. | ||
* | ||
* If this field is not set - no host name check will be performed. | ||
*/ | ||
public $allowedHosts; | ||
/** | ||
* @var callable a callback that will be called if the current host does not match [[allowedHosts]]. | ||
* If not set, [[denyAccess()]] will be called. | ||
* | ||
* The signature of the callback should be as follows: | ||
* | ||
* ```php | ||
* function (\yii\base\Action $action) | ||
* ``` | ||
* | ||
* where `$action` is the current [[\yii\base\Action|action]] object. | ||
* | ||
* > Note: while implementing your own host deny processing, make sure you avoid usage of the current requested | ||
* host name, creation of absolute URL links, caching page parts and so on. | ||
*/ | ||
public $denyCallback; | ||
|
||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function beforeAction($action) | ||
{ | ||
$allowedHosts = $this->allowedHosts; | ||
if ($allowedHosts instanceof \Closure) { | ||
$allowedHosts = call_user_func($allowedHosts, $action); | ||
} | ||
if ($allowedHosts === null) { | ||
return true; | ||
} | ||
|
||
if (!is_array($allowedHosts) && !$allowedHosts instanceof \Traversable) { | ||
$allowedHosts = (array)$allowedHosts; | ||
} | ||
|
||
$currentHost = Yii::$app->getRequest()->getHostName(); | ||
|
||
foreach ($allowedHosts as $allowedHost) { | ||
if (fnmatch($allowedHost, $currentHost)) { | ||
return true; | ||
} | ||
} | ||
|
||
if ($this->denyCallback !== null) { | ||
call_user_func($this->denyCallback, $action); | ||
} else { | ||
$this->denyAccess($action); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Denies the access. | ||
* The default implementation will display 404 page right away, terminating the program execution. | ||
* You may override this method, creating your own deny access handler. While doing so, make sure you | ||
* avoid usage of the current requested host name, creation of absolute URL links, caching page parts and so on. | ||
* @param \yii\base\Action $action the action to be executed. | ||
*/ | ||
protected function denyAccess($action) | ||
{ | ||
$response = Yii::$app->getResponse(); | ||
$errorHandler = Yii::$app->getErrorHandler(); | ||
|
||
$exception = new NotFoundHttpException(Yii::t('yii', 'Page not found.')); | ||
|
||
$response->setStatusCode($exception->statusCode, $exception->getMessage()); | ||
$response->data = $errorHandler->renderFile($errorHandler->errorView, ['exception' => $exception]); | ||
$response->send(); | ||
|
||
Yii::$app->end(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
namespace yiiunit\framework\filters; | ||
|
||
use Yii; | ||
use yii\base\Action; | ||
use yii\base\ExitException; | ||
use yii\filters\HostControl; | ||
use yii\web\Controller; | ||
use yiiunit\TestCase; | ||
|
||
class HostControlTest extends TestCase | ||
{ | ||
protected function setUp() | ||
{ | ||
parent::setUp(); | ||
|
||
$_SERVER['SCRIPT_FILENAME'] = "/index.php"; | ||
$_SERVER['SCRIPT_NAME'] = "/index.php"; | ||
|
||
$this->mockWebApplication(); | ||
} | ||
|
||
/** | ||
* @return array test data. | ||
*/ | ||
public function dataProviderFilter() | ||
{ | ||
return [ | ||
[ | ||
['example.com'], | ||
'example.com', | ||
true | ||
], | ||
[ | ||
['example.com'], | ||
'domain.com', | ||
false | ||
], | ||
[ | ||
['*.example.com'], | ||
'en.example.com', | ||
true | ||
], | ||
[ | ||
['*.example.com'], | ||
'fake.com', | ||
false | ||
], | ||
[ | ||
function () { | ||
return ['example.com']; | ||
}, | ||
'example.com', | ||
true | ||
], | ||
[ | ||
function () { | ||
return ['example.com']; | ||
}, | ||
'fake.com', | ||
false | ||
], | ||
]; | ||
} | ||
|
||
/** | ||
* @dataProvider dataProviderFilter | ||
* | ||
* @param mixed $allowedHosts | ||
* @param string $host | ||
* @param bool $allowed | ||
*/ | ||
public function testFilter($allowedHosts, $host, $allowed) | ||
{ | ||
$_SERVER['HTTP_HOST'] = $host; | ||
|
||
$filter = new HostControl(); | ||
$filter->allowedHosts = $allowedHosts; | ||
|
||
$controller = new Controller('id', Yii::$app); | ||
$action = new Action('test', $controller); | ||
|
||
if ($allowed) { | ||
$this->assertTrue($filter->beforeAction($action)); | ||
} else { | ||
ob_start(); | ||
ob_implicit_flush(false); | ||
|
||
$isExit = false; | ||
|
||
try { | ||
$filter->beforeAction($action); | ||
} catch (ExitException $e) { | ||
$isExit = true; | ||
} | ||
|
||
ob_get_clean(); | ||
|
||
$this->assertTrue($isExit); | ||
$this->assertEquals(404, Yii::$app->response->getStatusCode()); | ||
} | ||
} | ||
} |