class ExecutablePhp in Security Review 8
Checks if PHP files written to the files directory can be executed.
Hierarchy
- class \Drupal\security_review\Check uses DependencySerializationTrait, StringTranslationTrait
- class \Drupal\security_review\Checks\ExecutablePhp
Expanded class hierarchy of ExecutablePhp
1 file declares its use of ExecutablePhp
- security_review.module in ./
security_review.module - Site security review and reporting Drupal module.
File
- src/
Checks/ ExecutablePhp.php, line 14
Namespace
Drupal\security_review\ChecksView source
class ExecutablePhp extends Check {
/**
* Drupal's HTTP Client.
*
* @var \Drupal\Core\Http\Client
*/
protected $httpClient;
/**
* {@inheritdoc}
*/
public function __construct() {
parent::__construct();
$this->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 = "<?php\necho '" . $message . "';";
$file_path = PublicStream::basePath() . '/security_review_test.php';
// Create the test file.
if ($test_file = @fopen('./' . $file_path, 'w')) {
fwrite($test_file, $content);
fclose($test_file);
}
// Try to access the test file.
try {
$response = $this->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 = FileSecurity::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 <a href="https://drupal.org/node/615888">risk of PHP code execution on Drupal.org</a>.');
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.');
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Check:: |
protected | property | The configuration storage for this check. | |
Check:: |
protected | property | The service container. | |
Check:: |
protected | property | Settings handler for this check. | |
Check:: |
protected | property | The State system. | |
Check:: |
protected | property | The check's prefix in the State system. | |
Check:: |
protected | function | Returns the Security Review Checklist service. | |
Check:: |
protected | function | Returns the Config factory. | |
Check:: |
protected | function | Returns the service container. | |
Check:: |
public | function | Creates a new CheckResult for this Check. | |
Check:: |
protected | function | Returns the current Drupal user. | |
Check:: |
protected | function | Returns the database connection. | |
Check:: |
public | function | Enables the check. Has no effect if the check was not skipped. | |
Check:: |
protected | function | Returns the entity type manager. | |
Check:: |
public | function | Returns the namespace of the check. | |
Check:: |
public | function | Returns the machine name of the check. | 5 |
Check:: |
final public | function | Returns the identifier constructed using the namespace and title values. | |
Check:: |
public | function | Returns whether the check is skipped. Checks are not skipped by default. | |
Check:: |
protected | function | Returns the Drupal Kernel. | |
Check:: |
public | function | Returns the last stored result of the check. | |
Check:: |
public | function | Returns the timestamp the check was last run. | |
Check:: |
protected | function | Returns the module handler. | |
Check:: |
protected | function | Returns the Security Review Security service. | |
Check:: |
protected | function | Returns the Security Review service. | |
Check:: |
public | function | Returns the check-specific settings' handler. | |
Check:: |
public | function | Marks the check as skipped. | |
Check:: |
public | function | Returns the user the check was skipped by. | |
Check:: |
public | function | Returns the timestamp the check was last skipped on. | |
Check:: |
public | function | Stores a result in the state system. | |
Check:: |
public | function | Returns whether the findings should be stored or reproduced when needed. | 2 |
DependencySerializationTrait:: |
protected | property | An array of entity type IDs keyed by the property name of their storages. | |
DependencySerializationTrait:: |
protected | property | An array of service IDs keyed by property name used for serialization. | |
DependencySerializationTrait:: |
public | function | 1 | |
DependencySerializationTrait:: |
public | function | 2 | |
ExecutablePhp:: |
protected | property | Drupal's HTTP Client. | |
ExecutablePhp:: |
public | function |
Returns the evaluation page of a result. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Evaluates a CheckResult and returns a plaintext output. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Converts a result integer to a human-readable result message. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Returns the namespace of the check. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Returns the human-readable title of the check. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Returns the check-specific help page. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
The actual procedure of carrying out the check. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Same as run(), but used in CLI context such as Drush. Overrides Check:: |
|
ExecutablePhp:: |
public | function |
Initializes the configuration storage and the settings handler. Overrides Check:: |
|
StringTranslationTrait:: |
protected | property | The string translation service. | 1 |
StringTranslationTrait:: |
protected | function | Formats a string containing a count of items. | |
StringTranslationTrait:: |
protected | function | Returns the number of plurals supported by a given language. | |
StringTranslationTrait:: |
protected | function | Gets the string translation service. | |
StringTranslationTrait:: |
public | function | Sets the string translation service to use. | 2 |
StringTranslationTrait:: |
protected | function | Translates a string to the current language or to a given language. |