You are here

UploadExtensions.php in Security Review 8

File

src/Checks/UploadExtensions.php
View source
<?php

namespace Drupal\security_review\Checks;

use Drupal\Core\Link;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\security_review\Check;
use Drupal\security_review\CheckResult;
use Symfony\Component\Routing\Exception\RouteNotFoundException;

/**
 * Checks for unsafe extensions in the allowed extensions settings of fields.
 */
class UploadExtensions extends Check {

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

  /**
   * {@inheritdoc}
   */
  public function getTitle() {
    return 'Allowed upload extensions';
  }

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

  /**
   * {@inheritdoc}
   */
  public function run() {

    // If field is not enabled return with INFO.
    if (!$this
      ->moduleHandler()
      ->moduleExists('field')) {
      return $this
        ->createResult(CheckResult::INFO);
    }
    $result = CheckResult::SUCCESS;
    $findings = [];

    // Check field configuration entities.
    foreach (FieldConfig::loadMultiple() as $entity) {

      /** @var FieldConfig $entity */
      $extensions = $entity
        ->getSetting('file_extensions');
      if ($extensions != NULL) {
        $extensions = explode(' ', $extensions);
        $intersect = array_intersect($extensions, $this
          ->security()
          ->unsafeExtensions());

        // $intersect holds the unsafe extensions this entity allows.
        foreach ($intersect as $unsafe_extension) {
          $findings[$entity
            ->id()][] = $unsafe_extension;
        }
      }
    }
    if (!empty($findings)) {
      $result = CheckResult::FAIL;
    }
    return $this
      ->createResult($result, $findings);
  }

  /**
   * {@inheritdoc}
   */
  public function help() {
    $paragraphs = [];
    $paragraphs[] = $this
      ->t('File and image fields allow for uploaded files. Some extensions are considered dangerous because the files can be evaluated and then executed in the browser. A malicious user could use this opening to gain control of your site. Review <a href=":url">all fields on your site</a>.', [
      ':url' => Url::fromRoute('entity.field_storage_config.collection')
        ->toString(),
    ]);
    return [
      '#theme' => 'check_help',
      '#title' => 'Allowed upload extensions',
      '#paragraphs' => $paragraphs,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function evaluate(CheckResult $result) {
    $findings = $result
      ->findings();
    if (empty($findings)) {
      return [];
    }
    $paragraphs = [];
    $paragraphs[] = $this
      ->t('The following extensions are considered unsafe and should be removed or limited from use. Or, be sure you are not granting untrusted users the ability to upload files.');
    $items = [];
    foreach ($findings as $entity_id => $unsafe_extensions) {
      $entity = FieldConfig::load($entity_id);

      /** @var FieldConfig $entity */
      foreach ($unsafe_extensions as $extension) {
        $item = $this
          ->t('Review @type in <em>@field</em> field on @bundle', [
          '@type' => $extension,
          '@field' => $entity
            ->label(),
          '@bundle' => $entity
            ->getTargetBundle(),
        ]);

        // Try to get an edit url.
        try {
          $url_params = [
            'field_config' => $entity
              ->id(),
          ];
          if ($entity
            ->getTargetEntityTypeId() == 'node') {
            $url_params['node_type'] = $entity
              ->getTargetBundle();
          }
          $items[] = Link::createFromRoute($item, sprintf('entity.field_config.%s_field_edit_form', $entity
            ->getTargetEntityTypeId()), $url_params);
        } catch (RouteNotFoundException $e) {
          $items[] = $item;
        }
      }
    }
    return [
      '#theme' => 'check_evaluation',
      '#paragraphs' => $paragraphs,
      '#items' => $items,
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function evaluatePlain(CheckResult $result) {
    $findings = $result
      ->findings();
    if (empty($findings)) {
      return '';
    }
    $output = '';
    foreach ($findings as $entity_id => $unsafe_extensions) {
      $entity = FieldConfig::load($entity_id);

      /** @var FieldConfig $entity */
      $output .= $this
        ->t('@bundle: field @field', [
        '@bundle' => $entity
          ->getTargetBundle(),
        '@field' => $entity
          ->label(),
      ]);
      $output .= "\n\t" . implode(', ', $unsafe_extensions) . "\n";
    }
    return $output;
  }

  /**
   * {@inheritdoc}
   */
  public function getMessage($result_const) {
    switch ($result_const) {
      case CheckResult::SUCCESS:
        return $this
          ->t('Only safe extensions are allowed for uploaded files and images.');
      case CheckResult::FAIL:
        return $this
          ->t('Unsafe file extensions are allowed in uploads.');
      case CheckResult::INFO:
        return $this
          ->t('Module field is not enabled.');
      default:
        return $this
          ->t('Unexpected result.');
    }
  }

}

Classes

Namesort descending Description
UploadExtensions Checks for unsafe extensions in the allowed extensions settings of fields.