You are here

class FilePermissions in Security Review 8

Check that files aren't writeable by the server.

Hierarchy

Expanded class hierarchy of FilePermissions

1 file declares its use of FilePermissions
security_review.module in ./security_review.module
Site security review and reporting Drupal module.

File

src/Checks/FilePermissions.php, line 15

Namespace

Drupal\security_review\Checks
View source
class FilePermissions extends Check {

  /**
   * {@inheritdoc}
   */
  public function getNamespace() {
    return 'Security Review';
  }

  /**
   * {@inheritdoc}
   */
  public function getTitle() {
    return 'File permissions';
  }

  /**
   * {@inheritdoc}
   */
  public function getMachineTitle() {
    return 'file_perms';
  }

  /**
   * {@inheritdoc}
   */
  public function storesFindings() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function run($cli = FALSE) {
    $result = CheckResult::SUCCESS;
    $file_list = $this
      ->getFileList('.');
    $writable = $this
      ->security()
      ->findWritableFiles($file_list, $cli);

    // Try creating or appending files.
    // Assume it doesn't work.
    $create_status = FALSE;
    $append_status = FALSE;
    if (!$cli) {
      $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.");
      $directory = $this
        ->moduleHandler()
        ->getModule('security_review')
        ->getPath();

      // Write a file with the timestamp.
      $file = './' . $directory . '/file_write_test.' . date('Ymdhis');
      if ($file_create = @fopen($file, 'w')) {
        $create_status = fwrite($file_create, date('Ymdhis') . ' - ' . $append_message . "\n");
        fclose($file_create);
      }

      // Try to append to our IGNOREME file.
      $file = './' . $directory . '/IGNOREME.txt';
      if ($file_append = @fopen($file, 'a')) {
        $append_status = fwrite($file_append, date('Ymdhis') . ' - ' . $append_message . "\n");
        fclose($file_append);
      }
    }
    if (!empty($writable) || $create_status || $append_status) {
      $result = CheckResult::FAIL;
    }
    return $this
      ->createResult($result, $writable);
  }

  /**
   * {@inheritdoc}
   */
  public function runCli() {
    if (!$this
      ->securityReview()
      ->isServerPosix()) {
      return $this
        ->createResult(CheckResult::INFO);
    }
    return $this
      ->run(TRUE);
  }

  /**
   * {@inheritdoc}
   */
  public function help() {
    $paragraphs = [];
    $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.');
    $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.');
    $paragraphs[] = new Link($this
      ->t('Read more about file system permissions in the handbooks.'), Url::fromUri('http://drupal.org/node/244924'));
    return [
      '#theme' => 'check_help',
      '#title' => $this
        ->t('Web server file system permissions'),
      '#paragraphs' => $paragraphs,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function evaluate(CheckResult $result) {
    if ($result
      ->result() == CheckResult::SUCCESS) {
      return [];
    }
    $paragraphs = [];
    $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>.');
    return [
      '#theme' => 'check_evaluation',
      '#paragraphs' => $paragraphs,
      '#items' => $result
        ->findings(),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function evaluatePlain(CheckResult $result) {
    if ($result
      ->result() == CheckResult::SUCCESS) {
      return '';
    }
    $output = $this
      ->t('Writable files:') . "\n";
    foreach ($result
      ->findings() as $file) {
      $output .= "\t" . $file . "\n";
    }
    return $output;
  }

  /**
   * {@inheritdoc}
   */
  public function getMessage($result_const) {
    switch ($result_const) {
      case CheckResult::SUCCESS:
        return $this
          ->t('Drupal installation files and directories (except required) are not writable by the server.');
      case CheckResult::FAIL:
        return $this
          ->t('Some files and directories in your install are writable by the server.');
      case CheckResult::INFO:
        return $this
          ->t('The test cannot be run on this system.');
      default:
        return $this
          ->t('Unexpected result.');
    }
  }

  /**
   * Scans a directory recursively and returns the files and directories inside.
   *
   * @param string $directory
   *   The directory to scan.
   * @param string[] $parsed
   *   Array of already parsed real paths.
   * @param string[] $ignore
   *   Array of file names to ignore.
   *
   * @return string[]
   *   The items found.
   */
  protected function getFileList($directory, array &$parsed = NULL, array &$ignore = NULL) {

    // Initialize $parsed and $ignore arrays.
    if ($parsed === NULL) {
      $parsed = [
        realpath($directory),
      ];
    }
    if ($ignore === NULL) {
      $ignore = $this
        ->getIgnoreList();
    }

    // Start scanning.
    $items = [];
    if ($handle = opendir($directory)) {
      while (($file = readdir($handle)) !== FALSE) {

        // Don't check hidden files or ones we said to ignore.
        $path = $directory . "/" . $file;
        if ($file[0] != "." && !in_array($file, $ignore) && !in_array(realpath($path), $ignore)) {
          if (is_dir($path) && !in_array(realpath($path), $parsed)) {
            $parsed[] = realpath($path);
            $items = array_merge($items, $this
              ->getFileList($path, $parsed, $ignore));
          }
          $items[] = preg_replace("/\\/\\//si", "/", $path);
        }
      }
      closedir($handle);
    }
    return $items;
  }

  /**
   * Returns an array of relative and canonical paths to ignore.
   *
   * @return string[]
   *   List of relative and canonical file paths to ignore.
   */
  protected function getIgnoreList() {
    $file_path = PublicStream::basePath();
    $ignore = [
      '..',
      'CVS',
      '.git',
      '.svn',
      '.bzr',
      realpath($file_path),
    ];

    // Add temporary files directory if it's set.
    $temp_path = \Drupal::service('file_system')
      ->getTempDirectory();
    if (!empty($temp_path)) {
      $ignore[] = realpath('./' . rtrim($temp_path, '/'));
    }

    // Add private files directory if it's set.
    $private_files = PrivateStream::basePath();
    if (!empty($private_files)) {

      // Remove leading slash if set.
      if (strrpos($private_files, '/') !== FALSE) {
        $private_files = substr($private_files, strrpos($private_files, '/') + 1);
      }
      $ignore[] = $private_files;
    }
    $this
      ->moduleHandler()
      ->alter('security_review_file_ignore', $ignore);
    return $ignore;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Check::$config protected property The configuration storage for this check.
Check::$container protected property The service container.
Check::$settings protected property Settings handler for this check.
Check::$state protected property The State system.
Check::$statePrefix protected property The check's prefix in the State system.
Check::checklist protected function Returns the Security Review Checklist service.
Check::configFactory protected function Returns the Config factory.
Check::container protected function Returns the service container.
Check::createResult public function Creates a new CheckResult for this Check.
Check::currentUser protected function Returns the current Drupal user.
Check::database protected function Returns the database connection.
Check::enable public function Enables the check. Has no effect if the check was not skipped.
Check::entityTypeManager protected function Returns the entity type manager.
Check::getMachineNamespace public function Returns the namespace of the check.
Check::id final public function Returns the identifier constructed using the namespace and title values.
Check::isSkipped public function Returns whether the check is skipped. Checks are not skipped by default.
Check::kernel protected function Returns the Drupal Kernel.
Check::lastResult public function Returns the last stored result of the check.
Check::lastRun public function Returns the timestamp the check was last run.
Check::moduleHandler protected function Returns the module handler.
Check::security protected function Returns the Security Review Security service.
Check::securityReview protected function Returns the Security Review service.
Check::settings public function Returns the check-specific settings' handler.
Check::skip public function Marks the check as skipped.
Check::skippedBy public function Returns the user the check was skipped by.
Check::skippedOn public function Returns the timestamp the check was last skipped on.
Check::storeResult public function Stores a result in the state system.
Check::__construct public function Initializes the configuration storage and the settings handler. 2
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
FilePermissions::evaluate public function Returns the evaluation page of a result. Overrides Check::evaluate
FilePermissions::evaluatePlain public function Evaluates a CheckResult and returns a plaintext output. Overrides Check::evaluatePlain
FilePermissions::getFileList protected function Scans a directory recursively and returns the files and directories inside.
FilePermissions::getIgnoreList protected function Returns an array of relative and canonical paths to ignore.
FilePermissions::getMachineTitle public function Returns the machine name of the check. Overrides Check::getMachineTitle
FilePermissions::getMessage public function Converts a result integer to a human-readable result message. Overrides Check::getMessage
FilePermissions::getNamespace public function Returns the namespace of the check. Overrides Check::getNamespace
FilePermissions::getTitle public function Returns the human-readable title of the check. Overrides Check::getTitle
FilePermissions::help public function Returns the check-specific help page. Overrides Check::help
FilePermissions::run public function The actual procedure of carrying out the check. Overrides Check::run
FilePermissions::runCli public function Same as run(), but used in CLI context such as Drush. Overrides Check::runCli
FilePermissions::storesFindings public function Returns whether the findings should be stored or reproduced when needed. Overrides Check::storesFindings
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.