Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

framework/filters/HostControl.php (2 issues)

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
8
namespace yii\filters;
9
10
use Yii;
11
use yii\base\ActionFilter;
12
use yii\helpers\StringHelper;
13
use yii\web\NotFoundHttpException;
14
15
/**
16
 * HostControl provides simple control over requested host name.
17
 *
18
 * This filter provides protection against ['host header' attacks](https://www.acunetix.com/vulnerabilities/web/host-header-attack),
19
 * allowing action execution only for specified host names.
20
 *
21
 * Application configuration example:
22
 *
23
 * ```php
24
 * return [
25
 *     'as hostControl' => [
26
 *         'class' => 'yii\filters\HostControl',
27
 *         'allowedHosts' => [
28
 *             'example.com',
29
 *             '*.example.com',
30
 *         ],
31
 *     ],
32
 *     // ...
33
 * ];
34
 * ```
35
 *
36
 * Controller configuration example:
37
 *
38
 * ```php
39
 * use yii\web\Controller;
40
 * use yii\filters\HostControl;
41
 *
42
 * class SiteController extends Controller
43
 * {
44
 *     public function behaviors()
45
 *     {
46
 *         return [
47
 *             'hostControl' => [
48
 *                 'class' => HostControl::class,
49
 *                 'allowedHosts' => [
50
 *                     'example.com',
51
 *                     '*.example.com',
52
 *                 ],
53
 *             ],
54
 *         ];
55
 *     }
56
 *
57
 *     // ...
58
 * }
59
 * ```
60
 *
61
 * > Note: the best way to restrict allowed host names is usage of the web server 'virtual hosts' configuration.
62
 * This filter should be used only if this configuration is not available or compromised.
63
 *
64
 * @author Paul Klimov <[email protected]>
65
 * @since 2.0.11
66
 */
67
class HostControl extends ActionFilter
68
{
69
    /**
70
     * @var array|\Closure|null list of host names, which are allowed.
71
     * Each host can be specified as a wildcard pattern. For example:
72
     *
73
     * ```php
74
     * [
75
     *     'example.com',
76
     *     '*.example.com',
77
     * ]
78
     * ```
79
     *
80
     * This field can be specified as a PHP callback of following signature:
81
     *
82
     * ```php
83
     * function (\yii\base\Action $action) {
84
     *     //return array of strings
85
     * }
86
     * ```
87
     *
88
     * where `$action` is the current [[\yii\base\Action|action]] object.
89
     *
90
     * If this field is not set - no host name check will be performed.
91
     */
92
    public $allowedHosts;
93
    /**
94
     * @var callable a callback that will be called if the current host does not match [[allowedHosts]].
95
     * If not set, [[denyAccess()]] will be called.
96
     *
97
     * The signature of the callback should be as follows:
98
     *
99
     * ```php
100
     * function (\yii\base\Action $action)
101
     * ```
102
     *
103
     * where `$action` is the current [[\yii\base\Action|action]] object.
104
     *
105
     * > Note: while implementing your own host deny processing, make sure you avoid usage of the current requested
106
     * host name, creation of absolute URL links, caching page parts and so on.
107
     */
108
    public $denyCallback;
109
    /**
110
     * @var string|null fallback host info (e.g. `http://www.yiiframework.com`) used when [[\yii\web\Request::$hostInfo|Request::$hostInfo]] is invalid.
111
     * This value will replace [[\yii\web\Request::$hostInfo|Request::$hostInfo]] before [[$denyCallback]] is called to make sure that
112
     * 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.
113
     * Default value is empty string (this will result creating relative URLs instead of absolute).
114
     * @see \yii\web\Request::getHostInfo()
115
     */
116
    public $fallbackHostInfo = '';
117
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 11
    public function beforeAction($action)
123
    {
124 11
        $allowedHosts = $this->allowedHosts;
125 11
        if ($allowedHosts instanceof \Closure) {
126 2
            $allowedHosts = call_user_func($allowedHosts, $action);
127
        }
128 11
        if ($allowedHosts === null) {
129 1
            return true;
130
        }
131
132 10
        if (!is_array($allowedHosts) && !$allowedHosts instanceof \Traversable) {
133 1
            $allowedHosts = (array) $allowedHosts;
134
        }
135
136 10
        $currentHost = Yii::$app->getRequest()->getHostName();
137
138 10
        foreach ($allowedHosts as $allowedHost) {
139 10
            if (StringHelper::matchWildcard($allowedHost, $currentHost)) {
140 10
                return true;
141
            }
142
        }
143
144
        // replace invalid host info to prevent using it in further processing
145 6
        if ($this->fallbackHostInfo !== null) {
146 6
            Yii::$app->getRequest()->setHostInfo($this->fallbackHostInfo);
0 ignored issues
show
The method setHostInfo() does not exist on yii\console\Request. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

146
            Yii::$app->getRequest()->/** @scrutinizer ignore-call */ setHostInfo($this->fallbackHostInfo);
Loading history...
147
        }
148
149 6
        if ($this->denyCallback !== null) {
150 2
            call_user_func($this->denyCallback, $action);
151
        } else {
152 4
            $this->denyAccess($action);
153
        }
154
155 2
        return false;
156
    }
157
158
    /**
159
     * Denies the access.
160
     * The default implementation will display 404 page right away, terminating the program execution.
161
     * You may override this method, creating your own deny access handler. While doing so, make sure you
162
     * avoid usage of the current requested host name, creation of absolute URL links, caching page parts and so on.
163
     * @param \yii\base\Action $action the action to be executed.
164
     * @throws NotFoundHttpException
165
     */
166 4
    protected function denyAccess($action)
167
    {
168 4
        $exception = new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
169
170
        // use regular error handling if $this->fallbackHostInfo was set
171 4
        if (!empty(Yii::$app->getRequest()->hostName)) {
0 ignored issues
show
Bug Best Practice introduced by
The property hostName does not exist on yii\console\Request. Since you implemented __get, consider adding a @property annotation.
Loading history...
172 1
            throw $exception;
173
        }
174
175 3
        $response = Yii::$app->getResponse();
176 3
        $errorHandler = Yii::$app->getErrorHandler();
177
178 3
        $response->setStatusCode($exception->statusCode, $exception->getMessage());
179 3
        $response->data = $errorHandler->renderFile($errorHandler->errorView, ['exception' => $exception]);
180 3
        $response->send();
181
182 3
        Yii::$app->end();
183
    }
184
}
185