More updates to stop using dev or alpha or beta versions.
[yaffs-website] / web / modules / contrib / security_review / src / Checks / FilePermissions.php
1 <?php
2
3 namespace Drupal\security_review\Checks;
4
5 use Drupal\Core\Link;
6 use Drupal\Core\StreamWrapper\PrivateStream;
7 use Drupal\Core\StreamWrapper\PublicStream;
8 use Drupal\Core\Url;
9 use Drupal\security_review\Check;
10 use Drupal\security_review\CheckResult;
11
12 /**
13  * Check that files aren't writeable by the server.
14  */
15 class FilePermissions extends Check {
16
17   /**
18    * {@inheritdoc}
19    */
20   public function getNamespace() {
21     return 'Security Review';
22   }
23
24   /**
25    * {@inheritdoc}
26    */
27   public function getTitle() {
28     return 'File permissions';
29   }
30
31   /**
32    * {@inheritdoc}
33    */
34   public function getMachineTitle() {
35     return 'file_perms';
36   }
37
38   /**
39    * {@inheritdoc}
40    */
41   public function storesFindings() {
42     return FALSE;
43   }
44
45   /**
46    * {@inheritdoc}
47    */
48   public function run($cli = FALSE) {
49     $result = CheckResult::SUCCESS;
50
51     $file_list = $this->getFileList('.');
52     $writable = $this->security()->findWritableFiles($file_list, $cli);
53
54     // Try creating or appending files.
55     // Assume it doesn't work.
56     $create_status = FALSE;
57     $append_status = FALSE;
58
59     if (!$cli) {
60       $append_message = $this->t("Your web server should not be able to write to your modules directory. This is a security vulnerable. Consult the Security Review file permissions check help for mitigation steps.");
61       $directory = $this->moduleHandler()
62         ->getModule('security_review')
63         ->getPath();
64
65       // Write a file with the timestamp.
66       $file = './' . $directory . '/file_write_test.' . date('Ymdhis');
67       if ($file_create = @fopen($file, 'w')) {
68         $create_status = fwrite($file_create, date('Ymdhis') . ' - ' . $append_message . "\n");
69         fclose($file_create);
70       }
71
72       // Try to append to our IGNOREME file.
73       $file = './' . $directory . '/IGNOREME.txt';
74       if ($file_append = @fopen($file, 'a')) {
75         $append_status = fwrite($file_append, date('Ymdhis') . ' - ' . $append_message . "\n");
76         fclose($file_append);
77       }
78     }
79
80     if (!empty($writable) || $create_status || $append_status) {
81       $result = CheckResult::FAIL;
82     }
83
84     return $this->createResult($result, $writable);
85   }
86
87   /**
88    * {@inheritdoc}
89    */
90   public function runCli() {
91     if (!$this->securityReview()->isServerPosix()) {
92       return $this->createResult(CheckResult::INFO);
93     }
94
95     return $this->run(TRUE);
96   }
97
98   /**
99    * {@inheritdoc}
100    */
101   public function help() {
102     $paragraphs = [];
103     $paragraphs[] = $this->t('It is dangerous to allow the web server to write to files inside the document root of your server. Doing so could allow Drupal to write files that could then be executed. An attacker might use such a vulnerability to take control of your site. An exception is the Drupal files, private files, and temporary directories which Drupal needs permission to write to in order to provide features like file attachments.');
104     $paragraphs[] = $this->t('In addition to inspecting existing directories, this test attempts to create and write to your file system. Look in your security_review module directory on the server for files named file_write_test.YYYYMMDDHHMMSS and for a file called IGNOREME.txt which gets a timestamp appended to it if it is writeable.');
105     $paragraphs[] = new Link(
106       $this->t('Read more about file system permissions in the handbooks.'),
107       Url::fromUri('http://drupal.org/node/244924')
108     );
109
110     return [
111       '#theme' => 'check_help',
112       '#title' => $this->t('Web server file system permissions'),
113       '#paragraphs' => $paragraphs,
114     ];
115   }
116
117   /**
118    * {@inheritdoc}
119    */
120   public function evaluate(CheckResult $result) {
121     if ($result->result() == CheckResult::SUCCESS) {
122       return [];
123     }
124
125     $paragraphs = [];
126     $paragraphs[] = $this->t('The following files and directories appear to be writeable by your web server. In most cases you can fix this by simply altering the file permissions or ownership. If you have command-line access to your host try running "chmod 644 [file path]" where [file path] is one of the following paths (relative to your webroot). For more information consult the <a href="http://drupal.org/node/244924">Drupal.org handbooks on file permissions</a>.');
127
128     return [
129       '#theme' => 'check_evaluation',
130       '#paragraphs' => $paragraphs,
131       '#items' => $result->findings(),
132     ];
133   }
134
135   /**
136    * {@inheritdoc}
137    */
138   public function evaluatePlain(CheckResult $result) {
139     if ($result->result() == CheckResult::SUCCESS) {
140       return '';
141     }
142
143     $output = $this->t('Writable files:') . "\n";
144     foreach ($result->findings() as $file) {
145       $output .= "\t" . $file . "\n";
146     }
147
148     return $output;
149   }
150
151   /**
152    * {@inheritdoc}
153    */
154   public function getMessage($result_const) {
155     switch ($result_const) {
156       case CheckResult::SUCCESS:
157         return $this->t('Drupal installation files and directories (except required) are not writable by the server.');
158
159       case CheckResult::FAIL:
160         return $this->t('Some files and directories in your install are writable by the server.');
161
162       case CheckResult::INFO:
163         return $this->t('The test cannot be run on this system.');
164
165       default:
166         return $this->t('Unexpected result.');
167     }
168   }
169
170   /**
171    * Scans a directory recursively and returns the files and directories inside.
172    *
173    * @param string $directory
174    *   The directory to scan.
175    * @param string[] $parsed
176    *   Array of already parsed real paths.
177    * @param string[] $ignore
178    *   Array of file names to ignore.
179    *
180    * @return string[]
181    *   The items found.
182    */
183   protected function getFileList($directory, array &$parsed = NULL, array &$ignore = NULL) {
184     // Initialize $parsed and $ignore arrays.
185     if ($parsed === NULL) {
186       $parsed = [realpath($directory)];
187     }
188     if ($ignore === NULL) {
189       $ignore = $this->getIgnoreList();
190     }
191
192     // Start scanning.
193     $items = [];
194     if ($handle = opendir($directory)) {
195       while (($file = readdir($handle)) !== FALSE) {
196         // Don't check hidden files or ones we said to ignore.
197         $path = $directory . "/" . $file;
198         if ($file[0] != "." && !in_array($file, $ignore) && !in_array(realpath($path), $ignore)) {
199           if (is_dir($path) && !in_array(realpath($path), $parsed)) {
200             $parsed[] = realpath($path);
201             $items = array_merge($items, $this->getFileList($path, $parsed, $ignore));
202           }
203           $items[] = preg_replace("/\/\//si", "/", $path);
204         }
205       }
206       closedir($handle);
207     }
208
209     return $items;
210   }
211
212   /**
213    * Returns an array of relative and canonical paths to ignore.
214    *
215    * @return string[]
216    *   List of relative and canonical file paths to ignore.
217    */
218   protected function getIgnoreList() {
219     $file_path = PublicStream::basePath();
220     $ignore = ['..', 'CVS', '.git', '.svn', '.bzr', realpath($file_path)];
221
222     // Add temporary files directory if it's set.
223     $temp_path = file_directory_temp();
224     if (!empty($temp_path)) {
225       $ignore[] = realpath('./' . rtrim($temp_path, '/'));
226     }
227
228     // Add private files directory if it's set.
229     $private_files = PrivateStream::basePath();
230     if (!empty($private_files)) {
231       // Remove leading slash if set.
232       if (strrpos($private_files, '/') !== FALSE) {
233         $private_files = substr($private_files, strrpos($private_files, '/') + 1);
234       }
235       $ignore[] = $private_files;
236     }
237
238     $this->moduleHandler()->alter('security_review_file_ignore', $ignore);
239     return $ignore;
240   }
241
242 }