HostControl.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\filters;
  8. use Yii;
  9. use yii\base\ActionFilter;
  10. use yii\helpers\StringHelper;
  11. use yii\web\NotFoundHttpException;
  12. /**
  13. * HostControl provides simple control over requested host name.
  14. *
  15. * This filter provides protection against ['host header' attacks](https://www.acunetix.com/vulnerabilities/web/host-header-attack),
  16. * allowing action execution only for specified host names.
  17. *
  18. * Application configuration example:
  19. *
  20. * ```php
  21. * return [
  22. * 'as hostControl' => [
  23. * 'class' => 'yii\filters\HostControl',
  24. * 'allowedHosts' => [
  25. * 'example.com',
  26. * '*.example.com',
  27. * ],
  28. * ],
  29. * // ...
  30. * ];
  31. * ```
  32. *
  33. * Controller configuration example:
  34. *
  35. * ```php
  36. * use yii\web\Controller;
  37. * use yii\filters\HostControl;
  38. *
  39. * class SiteController extends Controller
  40. * {
  41. * public function behaviors()
  42. * {
  43. * return [
  44. * 'hostControl' => [
  45. * 'class' => HostControl::className(),
  46. * 'allowedHosts' => [
  47. * 'example.com',
  48. * '*.example.com',
  49. * ],
  50. * ],
  51. * ];
  52. * }
  53. *
  54. * // ...
  55. * }
  56. * ```
  57. *
  58. * > Note: the best way to restrict allowed host names is usage of the web server 'virtual hosts' configuration.
  59. * This filter should be used only if this configuration is not available or compromised.
  60. *
  61. * @author Paul Klimov <klimov.paul@gmail.com>
  62. * @since 2.0.11
  63. */
  64. class HostControl extends ActionFilter
  65. {
  66. /**
  67. * @var array|\Closure|null list of host names, which are allowed.
  68. * Each host can be specified as a wildcard pattern. For example:
  69. *
  70. * ```php
  71. * [
  72. * 'example.com',
  73. * '*.example.com',
  74. * ]
  75. * ```
  76. *
  77. * This field can be specified as a PHP callback of following signature:
  78. *
  79. * ```php
  80. * function (\yii\base\Action $action) {
  81. * //return array of strings
  82. * }
  83. * ```
  84. *
  85. * where `$action` is the current [[\yii\base\Action|action]] object.
  86. *
  87. * If this field is not set - no host name check will be performed.
  88. */
  89. public $allowedHosts;
  90. /**
  91. * @var callable a callback that will be called if the current host does not match [[allowedHosts]].
  92. * If not set, [[denyAccess()]] will be called.
  93. *
  94. * The signature of the callback should be as follows:
  95. *
  96. * ```php
  97. * function (\yii\base\Action $action)
  98. * ```
  99. *
  100. * where `$action` is the current [[\yii\base\Action|action]] object.
  101. *
  102. * > Note: while implementing your own host deny processing, make sure you avoid usage of the current requested
  103. * host name, creation of absolute URL links, caching page parts and so on.
  104. */
  105. public $denyCallback;
  106. /**
  107. * @var string|null fallback host info (e.g. `http://www.yiiframework.com`) used when [[\yii\web\Request::$hostInfo|Request::$hostInfo]] is invalid.
  108. * This value will replace [[\yii\web\Request::$hostInfo|Request::$hostInfo]] before [[$denyCallback]] is called to make sure that
  109. * an invalid host will not be used for further processing. You can set it to `null` to leave [[\yii\web\Request::$hostInfo|Request::$hostInfo]] untouched.
  110. * Default value is empty string (this will result creating relative URLs instead of absolute).
  111. * @see \yii\web\Request::getHostInfo()
  112. */
  113. public $fallbackHostInfo = '';
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function beforeAction($action)
  118. {
  119. $allowedHosts = $this->allowedHosts;
  120. if ($allowedHosts instanceof \Closure) {
  121. $allowedHosts = call_user_func($allowedHosts, $action);
  122. }
  123. if ($allowedHosts === null) {
  124. return true;
  125. }
  126. if (!is_array($allowedHosts) && !$allowedHosts instanceof \Traversable) {
  127. $allowedHosts = (array) $allowedHosts;
  128. }
  129. $currentHost = Yii::$app->getRequest()->getHostName();
  130. foreach ($allowedHosts as $allowedHost) {
  131. if (StringHelper::matchWildcard($allowedHost, $currentHost)) {
  132. return true;
  133. }
  134. }
  135. // replace invalid host info to prevent using it in further processing
  136. if ($this->fallbackHostInfo !== null) {
  137. Yii::$app->getRequest()->setHostInfo($this->fallbackHostInfo);
  138. }
  139. if ($this->denyCallback !== null) {
  140. call_user_func($this->denyCallback, $action);
  141. } else {
  142. $this->denyAccess($action);
  143. }
  144. return false;
  145. }
  146. /**
  147. * Denies the access.
  148. * The default implementation will display 404 page right away, terminating the program execution.
  149. * You may override this method, creating your own deny access handler. While doing so, make sure you
  150. * avoid usage of the current requested host name, creation of absolute URL links, caching page parts and so on.
  151. * @param \yii\base\Action $action the action to be executed.
  152. * @throws NotFoundHttpException
  153. */
  154. protected function denyAccess($action)
  155. {
  156. $exception = new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
  157. // use regular error handling if $this->fallbackHostInfo was set
  158. if (!empty(Yii::$app->getRequest()->hostName)) {
  159. throw $exception;
  160. }
  161. $response = Yii::$app->getResponse();
  162. $errorHandler = Yii::$app->getErrorHandler();
  163. $response->setStatusCode($exception->statusCode, $exception->getMessage());
  164. $response->data = $errorHandler->renderFile($errorHandler->errorView, ['exception' => $exception]);
  165. $response->send();
  166. Yii::$app->end();
  167. }
  168. }