httpClient = $this->container->get('http_client'); } /** * {@inheritdoc} */ public function getNamespace() { return 'Security Review'; } /** * {@inheritdoc} */ public function getTitle() { return 'Executable PHP'; } /** * {@inheritdoc} */ public function run($cli = FALSE) { global $base_url; $result = CheckResult::SUCCESS; $findings = []; // Set up test file data. $message = 'Security review test ' . date('Ymdhis'); $content = "httpClient->get($base_url . '/' . $file_path); if ($response->getStatusCode() == 200 && $response->getBody() === $message) { $result = CheckResult::FAIL; $findings[] = 'executable_php'; } } catch (RequestException $e) { // Access was denied to the file. } // Remove the test file. if (file_exists('./' . $file_path)) { @unlink('./' . $file_path); } // Check for presence of the .htaccess file and if the contents are correct. $htaccess_path = PublicStream::basePath() . '/.htaccess'; if (!file_exists($htaccess_path)) { $result = CheckResult::FAIL; $findings[] = 'missing_htaccess'; } else { // Check whether the contents of .htaccess are correct. $contents = file_get_contents($htaccess_path); $expected = FileStorage::htaccessLines(FALSE); // Trim each line separately then put them back together. $contents = implode("\n", array_map('trim', explode("\n", trim($contents)))); $expected = implode("\n", array_map('trim', explode("\n", trim($expected)))); if ($contents !== $expected) { $result = CheckResult::FAIL; $findings[] = 'incorrect_htaccess'; } // Check whether .htaccess is writable. if (!$cli) { $writable_htaccess = is_writable($htaccess_path); } else { $writable = $this->security()->findWritableFiles([$htaccess_path], TRUE); $writable_htaccess = !empty($writable); } if ($writable_htaccess) { $findings[] = 'writable_htaccess'; if ($result !== CheckResult::FAIL) { $result = CheckResult::WARN; } } } return $this->createResult($result, $findings); } /** * {@inheritdoc} */ public function runCli() { return $this->run(TRUE); } /** * {@inheritdoc} */ public function help() { $paragraphs = []; $paragraphs[] = $this->t('The Drupal files directory is for user-uploaded files and by default provides some protection against a malicious user executing arbitrary PHP code against your site.'); $paragraphs[] = $this->t('Read more about the risk of PHP code execution on Drupal.org.'); return [ '#theme' => 'check_help', '#title' => $this->t('Executable PHP in files directory'), '#paragraphs' => $paragraphs, ]; } /** * {@inheritdoc} */ public function evaluate(CheckResult $result) { $paragraphs = []; foreach ($result->findings() as $label) { switch ($label) { case 'executable_php': $paragraphs[] = $this->t('Security Review was able to execute a PHP file written to your files directory.'); break; case 'missing_htaccess': $directory = PublicStream::basePath(); $paragraphs[] = $this->t("The .htaccess file is missing from the files directory at @path", ['@path' => $directory]); $paragraphs[] = $this->t("Note, if you are using a webserver other than Apache you should consult your server's documentation on how to limit the execution of PHP scripts in this directory."); break; case 'incorrect_htaccess': $paragraphs[] = $this->t("The .htaccess file exists but does not contain the correct content. It is possible it's been maliciously altered."); break; case 'writable_htaccess': $paragraphs[] = $this->t("The .htaccess file is writable which poses a risk should a malicious user find a way to execute PHP code they could alter the .htaccess file to allow further PHP code execution."); break; } } return [ '#theme' => 'check_evaluation', '#paragraphs' => $paragraphs, '#items' => [], ]; } /** * {@inheritdoc} */ public function evaluatePlain(CheckResult $result) { $paragraphs = []; $directory = PublicStream::basePath(); foreach ($result->findings() as $label) { switch ($label) { case 'executable_php': $paragraphs[] = $this->t('PHP file executed in @path', ['@path' => $directory]); break; case 'missing_htaccess': $paragraphs[] = $this->t('.htaccess is missing from @path', ['@path' => $directory]); break; case 'incorrect_htaccess': $paragraphs[] = $this->t('.htaccess wrong content'); break; case 'writable_htaccess': $paragraphs[] = $this->t('.htaccess writable'); break; } } return implode("\n", $paragraphs); } /** * {@inheritdoc} */ public function getMessage($result_const) { switch ($result_const) { case CheckResult::SUCCESS: return $this->t('PHP files in the Drupal files directory cannot be executed.'); case CheckResult::FAIL: return $this->t('PHP files in the Drupal files directory can be executed.'); case CheckResult::WARN: return $this->t('The .htaccess file in the files directory is writable.'); default: return $this->t('Unexpected result.'); } } }