Делаем безопасную загрузку изображений

в 6:45, , рубрики: php, security, web-разработка, защита, изображения, метки: , , , ,

Как показывает практика — загрузка файлов (и в частности изображений) без надлежащего контроля приводит к образованию уязвимостей. В этой публикации рассмотрим практическую реализацию одного из вариантов безопасной загрузки изображений на сервер. Исходная постановка задачи предполагает возможность загрузки пользователем изображения на сервер и возможность дальнейшего его просмотра.

Для начала кратко основные шаги:
— производим отправку изображения с использованием XMLHttpRequest;
— проводим проверку загруженных данных на сервере на предмет «действительно ли это изображение»;
— проводим принудительное преобразование изображения в jpeg

В качестве места хранения изображений был выбран субдомен сайта (images.ваш_сайт.com), который физически отделен от сервера самого сайта. Данный субдомен используется только для загрузки, хранения (ну и показа соответственно) изображений. Таким образом предполагается что даже в случае если невзирая на все предыдущие пункты таки будет загружен какой то «нехороший» файл, то ничего критичного с функционалом самого ваш_сайт.com он сделать не сможет.

Итак перейдем к практической части.

Шаг первый делаем форму отправки

<script type="text/javascript">
      function xhr_send( e , d) {
        if (d) {
          var xhr = new XMLHttpRequest();
          xhr.onreadystatechange = function(){
            if(xhr.readyState == 4){
var backjson=JSON.parse(xhr.responseText);
if (backjson.success == 'ok') {document.getElementById(e).innerHTML = 'Файл загружен успешно';location.reload();}
if (backjson.success == 'false') {document.getElementById(e).innerHTML = backjson.reason;}
            }
          }
          xhr.open("POST", "images.ваш_сайт.com/upload.php");
          xhr.setRequestHeader("Cache-Control", "no-cache");
          xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
          xhr.setRequestHeader("X-File-Name", d.name);
          xhr.send(d);
        }
      }
 
      function xhr_parse(f, e) {
        if (f) {
          document.getElementById(e).innerHTML = "Выбран файл : " + f.name + "(" + f.type + ", размер:" + f.size + ")";
        } else {
          document.getElementById(e).innerHTML = "No file selected!";
        }
      }
 
      function xhr_get(c) {
        var xhr_file = null;
if (window.File && window.FileList) {xhr_file = document.getElementById(c).files[0]; xhr_parse(xhr_file, "xhr_status"); } 
      }
 
      function xhr_submit(e) {
        var form = document.getElementById(e);
form.onsubmit = function(){  return false;}
xhr_form = new FormData(form);
        xhr_send("xhr_status", xhr_form);
 
      }
</script>
<form id="fload" enctype="multipart/form-data" method="post"/>
<input type="file" size="24" name="xhr_img" onchange="xhr_get(this.id);" id="xhr_input" value="Выбрать"/>
<input type="submit" name="Submit" value="Загрузить" id="xhr_upload" onclick="xhr_submit('fload');"/>
</form>
<div id="xhr_status"></div>

Шаг второй — проводим проверку загруженного файла на сервере. Так как запрос у нас кроссдоменный (даже не взирая на то что мы используем свой собственный субдомен), то для того чтобы Ваш браузер мог получить и обработать ответ от скрипта upload.php — в самом начале добавляем следующие строки:

header('Access-Control-Allow-Origin: http://ваш_сайт.com'); 
header('Access-Control-Allow-Headers: Content-Type,Cache-Control,X-Requested-With,X-File-Name');

Для проведения проверок и необходимых операций с самим изображением используем class.upload.php

В результате получим нечто похожее на это

include('class.upload.php');
 
header('Access-Control-Allow-Origin: http://ваш_сайт.com'); 
header('Access-Control-Allow-Headers: Content-Type,Cache-Control,X-Requested-With,X-File-Name');
 
$dir_dest = 'swap';
$dir_pics =  $dir_dest;
 
if (isset($_FILES['xhr_img'])) {
    $handle = new Upload($_FILES['xhr_img'], 'ru_RU'); //
    if ($handle->uploaded) {
 
$handle->allowed = array('image/*');
$handle->mime_check = true;
$handle->file_new_name_ext = 'jpg';
 
 
$handle->Process($dir_dest);
 
        if ($handle->processed) {
$message = array('success'=> 'ok');
     echo json_encode($message);
        } else {
$message = array('success'=> 'false', 'reason'=>$handle->error);
echo json_encode($message);
        }
 
        $handle-> Clean();
 
    } else {
$message = array('success'=> 'false', 'reason'=>$handle->error);
echo json_encode($message);
    }
 
}

И напоследок я добавил еще конвертацию загруженного изображения в формат jpeg, применение интерлейсинга и установка качества сжатия в 90:

$handle->image_convert = 'jpg';
$handle->jpeg_quality = 90;
$handle->image_interlace = true;

На этом спасибо за внимание и надеюсь кому то пригодится.

Автор: semaster

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js