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