Including security review as a submodule - with patched for Yaffs.
[yaffs-website] / web / modules / contrib / security_review / src / Checks / TrustedHosts.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\security_review\Checks\TrustedHosts.
6  */
7
8 namespace Drupal\security_review\Checks;
9
10 use Drupal\Core\Url;
11 use Drupal\security_review\Check;
12 use Drupal\security_review\CheckResult;
13 use Drupal\security_review\CheckSettings\TrustedHostSettings;
14
15 /**
16  * Checks for base_url and trusted_host_patterns settings in settings.php.
17  */
18 class TrustedHosts extends Check {
19
20   /**
21    * {@inheritdoc}
22    */
23   public function __construct() {
24     parent::__construct();
25     $this->settings = new TrustedHostSettings($this, $this->config);
26   }
27
28   /**
29    * {@inheritdoc}
30    */
31   public function getNamespace() {
32     return 'Security Review';
33   }
34
35   /**
36    * {@inheritdoc}
37    */
38   public function getTitle() {
39     return 'Trusted hosts';
40   }
41
42   /**
43    * {@inheritdoc}
44    */
45   public function run() {
46     $result = CheckResult::FAIL;
47     $base_url_set = FALSE;
48     $trusted_host_patterns_set = FALSE;
49     $findings = [];
50     $settings_php = $this->security()->sitePath() . '/settings.php';
51
52     if (!file_exists($settings_php)) {
53       return $this->createResult(CheckResult::INFO, [], FALSE);
54     }
55
56     if ($this->settings()->get('method', 'token') === 'token') {
57       // Use tokenization.
58       $content = file_get_contents($settings_php);
59       $tokens = token_get_all($content);
60
61       $prev_settings_line = -1;
62       foreach ($tokens as $token) {
63         if (is_array($token)) {
64           // Get information about the current token.
65           $line = $token[2];
66           $is_variable = $token[0] === T_VARIABLE;
67           $is_string = $token[0] === T_CONSTANT_ENCAPSED_STRING;
68           $is_settings = $is_variable ? $token[1] == '$settings' : FALSE;
69           $is_base_url = $token[1] == '$base_url';
70           $is_thp = trim($token[1], "\"'") == 'trusted_host_patterns';
71           $is_after_settings = $line == $prev_settings_line;
72
73           // Check for $base_url.
74           if ($is_variable && $is_base_url) {
75             $base_url_set = TRUE;
76             $result = CheckResult::SUCCESS;
77           }
78
79           // Check for $settings['trusted_host_patterns'].
80           if ($is_after_settings && $is_string && $is_thp) {
81             $trusted_host_patterns_set = TRUE;
82             $result = CheckResult::SUCCESS;
83           }
84
85           // If found both settings stop the review.
86           if ($base_url_set && $trusted_host_patterns_set) {
87             // Got everything we need.
88             break;
89           }
90
91           // Store last $settings line.
92           if ($is_settings) {
93             $prev_settings_line = $line;
94           }
95         }
96       }
97     }
98     else {
99       // Use inclusion.
100       include $settings_php;
101       $base_url_set = isset($base_url);
102       $trusted_host_patterns_set = isset($settings['trusted_host_patterns']);
103     }
104
105     if ($result === CheckResult::FAIL) {
106       // Provide information if the check failed.
107       global $base_url;
108       $findings['base_url'] = $base_url;
109       $findings['settings'] = $settings_php;
110       $findings['base_url_set'] = $base_url_set;
111       $findings['trusted_host_patterns_set'] = $trusted_host_patterns_set;
112     }
113
114     return $this->createResult($result, $findings);
115   }
116
117   /**
118    * {@inheritdoc}
119    */
120   public function help() {
121     $paragraphs = [];
122     $paragraphs[] = $this->t('Often Drupal needs to know the URL(s) it is responding from in order to build full links back to itself (e.g. password reset links sent via email). Until you explicitly tell Drupal what full or partial URL(s) it should respond for it must dynamically detect it based on the incoming request, something that can be malicously spoofed in order to trick someone into unknowningly visiting an attacker\'s site (known as a HTTP host header attack).');
123
124     return [
125       '#theme' => 'check_help',
126       '#title' => $this->t('Drupal trusted hosts'),
127       '#paragraphs' => $paragraphs,
128     ];
129   }
130
131   /**
132    * {@inheritdoc}
133    */
134   public function evaluate(CheckResult $result) {
135     global $base_url;
136     if ($result->result() !== CheckResult::FAIL) {
137       return [];
138     }
139
140     $settings_php = $this->security()->sitePath() . '/settings.php';
141
142     $paragraphs = [];
143     $paragraphs[] = $this->t('This site is responding from the URL: :url.', [':url' => $base_url]);
144     $paragraphs[] = $this->t('If the site should be available only at that URL it is recommended that you set it as the $base_url variable in the settings.php file at @file.', ['@file' => $settings_php]);
145     $paragraphs[] = $this->t('If the site has multiple URLs it can respond from you should whitelist host patterns with trusted_host_patterns in settings.php.');
146     $paragraphs[] = $this->l($this->t('Read more about HTTP Host Header attacks and setting trusted_host_patterns.'), Url::fromUri('https://www.drupal.org/node/1992030'));
147
148     return [
149       '#theme' => 'check_evaluation',
150       '#paragraphs' => $paragraphs,
151       '#items' => [],
152     ];
153   }
154
155   /**
156    * {@inheritdoc}
157    */
158   public function getMessage($result_const) {
159     switch ($result_const) {
160       case CheckResult::SUCCESS:
161         return $this->t('Either $base_url or trusted_host_patterns is set.');
162
163       case CheckResult::FAIL:
164         return $this->t('Neither $base_url nor trusted_host_patterns is set.');
165
166       default:
167         return $this->t('Unexpected result.');
168     }
169   }
170
171 }