Загрузка файла PHP. Что я могу добавить, чтобы сделать его более безопасным?

Я собрал пример сценария загрузки одного файла, который пытается охватить все, что PHP может проверить, прежде чем разрешить успешную загрузку файла. Есть ли что-нибудь еще, доступное в PHP 7.4+, которое я мог бы использовать, чтобы сделать это более безопасным? Например, я использую filter_input ниже, хотя я не нахожу его во многих скриптах.

Взглянем

<?php 

  # EVALUATE REQUEST METHOD
  $REQUEST_METHOD = filter_input(INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_ENCODED);

  switch ($REQUEST_METHOD) {

    # HTTP:POST   - PAYLOAD:BLOB
    case 'POST':
      # POST IMAGE
      if(in_array(@$_FILES["files"], $_FILES) && count($_FILES) === 1) {
        upload();
      }
      break;

    default:
      methodInvalid();
      break;
  }

    /**
     * Function upload() uploads a single file.
     * 
     * 
     */
  function upload() {

      // Establish the upload file directory
      $upload_dir =  $_SERVER['DOCUMENT_ROOT'] . '/gui/v1/uploads/submittals/';

      // Establish the upload file path
      $upload_file = $upload_dir . $_FILES['files']['name'][0];

      // Derive the upload file extension
      $upload_file_extension = strtolower(pathinfo($upload_file, PATHINFO_EXTENSION));

      // Allowed file types
      // $allowed_file_extensions = ['pdf', 'jpg', 'jpeg', 'png', 'gif'];
      $allowed_file_extensions = ['pdf'];
      
        /**
         * Does tmp file exist?
         * 
         * 
         */
      if (!file_exists($_FILES['files']['tmp_name'][0])) {

        # ERROR object
        $errorObject             = new stdClass();
        $errorObject->apiVersion = '1.0';
        $errorObject->context="upload.submittal";

        # ABOUT ERROR object
        $aboutError              = new stdClass();
        $aboutError->code="ERR-000";
        $aboutError->message="Select file to upload.";

        # APPEND ABOUT ERROR object TO ERROR object
        $errorObject->error      = $aboutError;

        # RETURN JSON RESPONSE
        header('Content-type:application/json;charset=utf-8');
        return print(json_encode($errorObject));

      }

        /**
         * Is file extension allowed?
         * 
         * 
         */
      if (!in_array($upload_file_extension, $allowed_file_extensions)) {

        # ERROR object
        $errorObject             = new stdClass();
        $errorObject->apiVersion = '1.0';
        $errorObject->context="upload.submittal";

        # ABOUT ERROR object
        $aboutError              = new stdClass();
        $aboutError->code="ERR-000";
        $aboutError->message="Allowed file formats .pdf";

        # APPEND ABOUT ERROR object TO ERROR object
        $errorObject->error      = $aboutError;

        # RETURN JSON RESPONSE
        header('Content-type:application/json;charset=utf-8');
        return print(json_encode($errorObject));

      }

        /**
         * Is file bigger than 20MB?
         * 
         * 
         */
      if ($_FILES['files']['size'][0] > 20000000) {

        # ERROR object
        $errorObject             = new stdClass();
        $errorObject->apiVersion = '1.0';
        $errorObject->context="upload.submittal";

        # ABOUT ERROR object
        $aboutError              = new stdClass();
        $aboutError->code="ERR-000";
        $aboutError->message="File is too large. File size should be less than 20 megabytes.";

        # APPEND ABOUT ERROR object TO ERROR object
        $errorObject->error      = $aboutError;

        # RETURN JSON RESPONSE
        header('Content-type:application/json;charset=utf-8');
        return print(json_encode($errorObject));

      }

        /**
         * Does file already exist?
         * 
         * 
         */
      if (file_exists($upload_file)) {

            /**
             * File overwritten successfuly!
             * 
             * 
             */
        move_uploaded_file($_FILES['files']['tmp_name'][0], $upload_file);

        # SUCCESS object
        $successObject               = new stdClass();
        $successObject->apiVersion   = '1.0';
        $successObject->context="upload.submittal";
        $successObject->status="OK";

        # UPLOAD SUBMITTAL object
        $data                        = new stdClass();
        $data->submittalUploaded     = true;
        # APPEND DATA object TO SUCCESS object
        $successObject->data         = $data;

        # APPEND empty arrays to DATA object
        $successObject->data->arr1   = [];
        $successObject->data->arr2   = [];
        $successObject->data->arr3   = [];

        # RETURN JSON RESPONSE
        header('Content-type:application/json;charset=utf-8');
        return print(json_encode($successObject));

      }

        /**
         * Can file actually be uploaded?
         * 
         * 
         */
      if (!move_uploaded_file($_FILES['files']['tmp_name'][0], $upload_file)) {

            /**
             * File upload error!
             * 
             * 
             */
        # ERROR object
        $errorObject             = new stdClass();
        $errorObject->apiVersion = '1.0';
        $errorObject->context="upload.submittal";

        # ABOUT ERROR object
        $aboutError              = new stdClass();
        $aboutError->code="ERR-000";
        $aboutError->message="File couldn"t be uploaded.';

        # APPEND ABOUT ERROR object TO ERROR object
        $errorObject->error      = $aboutError;

        # RETURN JSON RESPONSE
        header('Content-type:application/json;charset=utf-8');
        return print(json_encode($errorObject));

      } else {

            /**
             * File uploaded successfuly!
             * 
             * 
             */
        # SUCCESS object
        $successObject               = new stdClass();
        $successObject->apiVersion   = '1.0';
        $successObject->context="upload.submittal";
        $successObject->status="OK";

        # UPLOAD SUBMITTAL object
        $data                        = new stdClass();
        $data->submittalUploaded     = true;
        # APPEND DATA object TO SUCCESS object
        $successObject->data         = $data;

        # APPEND empty arrays to DATA object
        $successObject->data->arr1   = [];
        $successObject->data->arr2   = [];
        $successObject->data->arr3   = [];

        # RETURN JSON RESPONSE
        header('Content-type:application/json;charset=utf-8');
        return print(json_encode($successObject));

            // We could insert URL file path to a database from here...

      }

    }

    /**
     * Function methodInvalid() warns about invalid method.
     * 
     * 
     */
  function methodInvalid() {

    # ERROR object
    $errorObject             = new stdClass();
    $errorObject->apiVersion = '1.0';
    $errorObject->context="uploads";

    # ABOUT ERROR object
    $aboutError              = new stdClass();
    $aboutError->code="ERR-000";
    $aboutError->message="Invalid Request. Allowed Methods are POST.";

    # APPEND ABOUT ERROR object TO ERROR object
    $errorObject->error      = $aboutError;

    # RETURN JSON RESPONSE
    header('Content-type:application/json;charset=utf-8');
    return print(json_encode($errorObject));

  }

 ?>

1 ответ
1

Несколько моих мыслей после быстрого сканирования сверху вниз:

Фильтрация REQUEST_METHOD абсурдно ориентирована на будущее (вы должны предвидеть эволюцию кода, где буквальное содержимое хранится в базе данных и в конечном итоге выводится без проверки в какой-то браузер), но почему-то мне нравится ваше мышление.

Счетчик проверок ($ _ FILES) === 1 вроде как противоположный. Если интерфейс превращается в загрузку второго файла (в другой переменной POST), ваш код игнорирует обе загрузки. Он не вызывает methodInvalid и не регистрирует сообщение об ошибке.

Позже в коде вы дважды проверяете, действительно ли PHP поместил временный файл туда, где он говорит, что поместил его (это излишне, но лучше быть чрезмерно параноиком, чем чрезмерно расслабленным). Но вы не проверяете, что $ _FILES[‘files’][‘name’] содержит неприятные вещи, такие как «../», которые могут привести к загрузке файлов в места, куда вы не хотите, чтобы они уходили.

Я заметил множество магических значений, которые появляются внутри вашего кода (поддерживаемые вами расширения, максимальный размер файла, каталог загрузки, коды ошибок, версии API и т. Д.). Вытащите их.

Код не имеет структуры (если вы не учитываете «один оператор за другим» как структуру). Введение функций для изоляции / инкапсуляции функциональных аспектов значительно упрощает поддержку и развитие кода.

Вызов «new stdclass» и присвоение ему поля «apiVersion» подняли бровь. Если вы серьезно относитесь к разработке API, представьте несколько специальных классов для ответов.

Подводя итог: у кода есть много возможностей для улучшений, но я действительно ценю вашу цель сделать его максимально безопасным. В нынешнем виде он выглядит довольно надежно с точки зрения безопасности. Но не игнорируйте тот факт, что текущий низкий уровень удобочитаемости затрудняет / слишком утомляет подлинную проверку того, действительно ли он безопасен.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *