Скрипт полной безопасной загрузки изображений

Я не знаю, случится ли это, но я попробую.

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

В моем проекте мне нужно быть в безопасности с загруженными изображениями. Там также может быть действительно большой объем, и для этого может потребоваться большая пропускная способность, поэтому покупка API не является вариантом.

Поэтому я решил получить полный скрипт PHP для безопасной загрузки изображений. Я также думаю, что это поможет многим людям, потому что невозможно найти действительно безопасный. Но я не эксперт по php, поэтому для меня действительно есть головная боль, чтобы добавить некоторые функции, поэтому я попрошу эту помощь сообщества создать один полный скрипт БЕСПЛАТНОЙ безопасной загрузки изображений.

На самом деле замечательные темы об этом здесь (однако они просто говорят, что нужно делать, чтобы сделать трюк, но не как это сделать, и, как я уже сказал, я не мастер по PHP, поэтому я не могу это сделать я сам): список проверки безопасности загрузки изображений PHP https://security.stackexchange.com/questions/32852/risks-of-a-php-image-upload-form

В общем, они говорят, что это то, что необходимо для загрузки образа безопасности (я приведу цитату из приведенных выше страниц):

  • Отключите PHP от работы в папке загрузки с помощью .httaccess.
  • Не разрешать загрузку, если имя файла содержит строку «php».
  • Разрешить только расширения: jpg, jpeg, gif и png.
  • Разрешить только тип файла изображения.
  • Запретить изображение с двумя типами файлов.
  • Измените имя изображения. Загрузите в подкаталог не корневой каталог.

Также:

  • Повторно обработайте изображение с помощью GD (или Imagick) и сохраните обработанное изображение. Все остальные просто забавны для хакеров »
  • Как указано в rr, используйте move_uploaded_file () для любой загрузки "
  • Кстати, вы хотите быть очень строгим в своей папке загрузки. Эти места являются одним из темных углов, где многие подвиги
    бывает. Это действительно для любого типа загрузки и любого программирования
    язык / сервер. Проверьте
    https://www.owasp.org/index.php/Unrestricted_File_Upload
  • Уровень 1: проверьте расширение (файл расширения заканчивается на)
  • Уровень 2: проверьте тип MIME ($ file_info = getimagesize ($ _ FILES ['image_file']; $ file_mime = $ file_info ['mime'];)
  • Уровень 3: прочитайте первые 100 байт и проверьте, есть ли у них какие-либо байты в следующем диапазоне: ASCII 0-8, 12-31 (десятичный).
  • Уровень 4: проверьте магические числа в заголовке (первые 10-20 байт файла). Здесь вы можете найти несколько байтов заголовков файлов:
    http://en.wikipedia.org/wiki/Magic_number_%28programming%29#Examples
  • Возможно, вы захотите запустить «is_uploaded_file» в $ _FILES ['my_files'] ['tmp_name']. Видеть
    http://php.net/manual/en/function.is-uploaded-file.php

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

ЭТО ЧТО МЫ СЕЙЧАС

  • Основной PHP:

    function uploadFile ($file_field = null, $check_image = false, $random_name = false) { //Config Section //Set file upload path $path = 'uploads/'; //with trailing slash //Set max file size in bytes $max_size = 1000000; //Set default file extension whitelist $whitelist_ext = array('jpeg','jpg','png','gif'); //Set default file type whitelist $whitelist_type = array('image/jpeg', 'image/jpg', 'image/png','image/gif'); //The Validation // Create an array to hold any output $out = array('error'=>null); if (!$file_field) { $out['error'][] = "Please specify a valid form field name"; } if (!$path) { $out['error'][] = "Please specify a valid upload path"; } if (count($out['error'])>0) { return $out; } //Make sure that there is a file if((!empty($_FILES[$file_field])) && ($_FILES[$file_field]['error'] == 0)) { // Get filename $file_info = pathinfo($_FILES[$file_field]['name']); $name = $file_info['filename']; $ext = $file_info['extension']; //Check file has the right extension if (!in_array($ext, $whitelist_ext)) { $out['error'][] = "Invalid file Extension"; } //Check that the file is of the right type if (!in_array($_FILES[$file_field]["type"], $whitelist_type)) { $out['error'][] = "Invalid file Type"; } //Check that the file is not too big if ($_FILES[$file_field]["size"] > $max_size) { $out['error'][] = "File is too big"; } //If $check image is set as true if ($check_image) { if (!getimagesize($_FILES[$file_field]['tmp_name'])) { $out['error'][] = "Uploaded file is not a valid image"; } } //Create full filename including path if ($random_name) { // Generate random filename $tmp = str_replace(array('.',' '), array('',''), microtime()); if (!$tmp || $tmp == '') { $out['error'][] = "File must have a name"; } $newname = $tmp.'.'.$ext; } else { $newname = $name.'.'.$ext; } //Check if file already exists on server if (file_exists($path.$newname)) { $out['error'][] = "A file with this name already exists"; } if (count($out['error'])>0) { //The file has not correctly validated return $out; } if (move_uploaded_file($_FILES[$file_field]['tmp_name'], $path.$newname)) { //Success $out['filepath'] = $path; $out['filename'] = $newname; return $out; } else { $out['error'][] = "Server Error!"; } } else { $out['error'][] = "No file uploaded"; return $out; } } if (isset($_POST['submit'])) { $file = uploadFile('file', true, true); if (is_array($file['error'])) { $message = ''; foreach ($file['error'] as $msg) { $message .= '<p>'.$msg.'</p>'; } } else { $message = "File uploaded successfully".$newname; } echo $message; } 
  • И форма:

     <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" enctype="multipart/form-data" name="form1" id="form1"> <input name="file" type="file" id="imagee" /> <input name="submit" type="submit" value="Upload" /> </form> 

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

Solutions Collecting From Web of "Скрипт полной безопасной загрузки изображений"

Когда вы начинаете работать над безопасным сценарием загрузки изображений, есть много вещей, которые следует учитывать. Теперь меня нет рядом с экспертом по этому вопросу, но меня попросили развить это однажды в прошлом. Я пройду через весь процесс, через который я прошел, чтобы вы могли следовать. Для этого я собираюсь начать с очень простой html-формы и php-скрипта, который обрабатывает файлы.

Форма HTML:

 <form name="upload" action="upload.php" method="POST" enctype="multipart/form-data"> Select image to upload: <input type="file" name="image"> <input type="submit" name="upload" value="upload"> </form> 

PHP-файл:

 <?php $uploaddir = 'uploads/'; $uploadfile = $uploaddir . basename($_FILES['image']['name']); if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) { echo "Image succesfully uploaded."; } else { echo "Image uploading failed."; } ?> 

Первая проблема: типы файлов
Атакующим не нужно использовать форму на своем веб-сайте для загрузки файлов на ваш сервер. Запросы POST могут быть перехвачены несколькими способами. Подумайте о аддонах браузера, прокси-серверах, сценариях Perl. Как бы мы ни старались, мы не можем помешать злоумышленнику пытаться загрузить что-то, чего он не должен. Таким образом, вся наша безопасность должна выполняться на серверах.

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

 <?php if($_FILES['image']['type'] != "image/png") { echo "Only PNG images are allowed!"; exit; } $uploaddir = 'uploads/'; $uploadfile = $uploaddir . basename($_FILES['image']['name']); if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) { echo "Image succesfully uploaded."; } else { echo "Image uploading failed."; } ?> 

К сожалению, этого недостаточно. Как я упоминал ранее, злоумышленник имеет полный контроль над запросом. Ничто не мешает ему / ей изменять заголовки запросов и просто менять тип содержимого на «image / png». Поэтому вместо того, чтобы просто полагаться на заголовок Content-type, было бы лучше также проверить содержание загруженного файла. Вот где пригодится библиотека php GD. Используя getimagesize() , мы будем обрабатывать изображение с помощью библиотеки GD. Если это не изображение, это не сработает, и при этом вся загрузка не будет выполнена:

 <?php $verifyimg = getimagesize($_FILES['image']['tmp_name']); if($verifyimg['mime'] != 'image/png') { echo "Only PNG images are allowed!"; exit; } $uploaddir = 'uploads/'; $uploadfile = $uploaddir . basename($_FILES['image']['name']); if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) { echo "Image succesfully uploaded."; } else { echo "Image uploading failed."; } ?> 

Тем не менее, мы все еще не находимся. Большинство типов файлов изображений позволяют добавлять текстовые комментарии к ним. Опять же, ничто не мешает злоумышленнику добавить некоторый PHP-код в качестве комментария. Библиотека GD оценит это как абсолютно достоверное изображение. PHP-интерпретатор полностью игнорирует изображение и запускает PHP-код в комментарии. Это правда, что это зависит от конфигурации php, какие расширения файлов обрабатываются интерпретатором php, а какие нет, но поскольку есть много разработчиков, которые не имеют контроля над этой конфигурацией из-за использования VPS, мы не можем предположить интерпретатор php не будет обрабатывать изображение. Вот почему добавление белого списка расширения файлов также недостаточно безопасно.

Решением этого будет хранение изображений в месте, где злоумышленник не может получить доступ к файлу напрямую. Это может быть вне корневого каталога документа или в каталоге, защищенном файлом .htaccess:

 order deny,allow deny from all allow from 127.0.0.1 

Редактирование: после разговора с некоторыми другими программистами PHP я настоятельно рекомендую использовать папку за пределами корня документа, потому что htaccess не всегда надежный.

Нам все равно нужен пользователь или любой другой посетитель, чтобы иметь возможность просматривать изображение. Поэтому мы будем использовать php для получения изображения для них:

 <?php $uploaddir = 'uploads/'; $name = $_GET['name']; // Assuming the file name is in the URL for this example readfile($uploaddir.$name); ?> 

Вторая проблема: атаки локального файла
Хотя наш скрипт уже достаточно безопасен, мы не можем предположить, что сервер не страдает от других уязвимостей. Общей уязвимостью безопасности называется локальное включение файлов . Чтобы объяснить это, мне нужно добавить пример кода:

 <?php if(isset($_COOKIE['lang'])) { $lang = $_COOKIE['lang']; } elseif (isset($_GET['lang'])) { $lang = $_GET['lang']; } else { $lang = 'english'; } include("language/$lang.php"); ?> в <?php if(isset($_COOKIE['lang'])) { $lang = $_COOKIE['lang']; } elseif (isset($_GET['lang'])) { $lang = $_GET['lang']; } else { $lang = 'english'; } include("language/$lang.php"); ?> 

В этом примере мы говорим о многоязычном веб-сайте. Язык сайтов не является чем-то, что считается «высокорисковым». Мы пытаемся получить предпочтительный язык посетителей через файл cookie или GET и включить на нем требуемый файл. Теперь подумайте, что произойдет, когда злоумышленник войдет в следующий URL:

http://www.example.com/index.php?lang=../uploads/my_evil_image.jpg

PHP будет включать файл, загруженный злоумышленником, в обход факта, что он не может получить доступ к файлу напрямую, и мы вернемся к квадрату.

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

 CREATE TABLE `uploads` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL, `original_name` VARCHAR(64) NOT NULL, `mime_type` VARCHAR(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8; 

 <?php if(!empty($_POST['upload']) && !empty($_FILES['image']) && $_FILES['image']['error'] == 0)) { $uploaddir = 'uploads/'; /* Generates random filename and extension */ function tempnam_sfx($path, $suffix){ do { $file = $path."/".mt_rand().$suffix; $fp = @fopen($file, 'x'); } while(!$fp); fclose($fp); return $file; } /* Process image with GD library */ $verifyimg = getimagesize($_FILES['image']['tmp_name']); /* Make sure the MIME type is an image */ $pattern = "#^(image/)[^\s\n<]+$#i"; if(!preg_match($pattern, $verifyimg['mime']){ die("Only image files are allowed!"); } /* Rename both the image and the extension */ $uploadfile = tempnam_sfx($uploaddir, ".tmp"); /* Upload the file to a secure directory with the new name and extension */ if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) { /* Setup a database connection with PDO */ $dbhost = "localhost"; $dbuser = ""; $dbpass = ""; $dbname = ""; // Set DSN $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname; // Set options $options = array( PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ); try { $db = new PDO($dsn, $dbuser, $dbpass, $options); } catch(PDOException $e){ die("Error!: " . $e->getMessage()); } /* Setup query */ $query = 'INSERT INTO uploads (name, original_name, mime_type) VALUES (:name, :oriname, :mime)'; /* Prepare query */ $db->prepare($query); /* Bind parameters */ $db->bindParam(':name', basename($uploadfile)); $db->bindParam(':oriname', basename($_FILES['image']['name'])); $db->bindParam(':mime', $_FILES['image']['type']); /* Execute query */ try { $db->execute(); } catch(PDOException $e){ // Remove the uploaded file unlink($uploadfile); die("Error!: " . $e->getMessage()); } } else { die("Image upload failed!"); } } ?> в <?php if(!empty($_POST['upload']) && !empty($_FILES['image']) && $_FILES['image']['error'] == 0)) { $uploaddir = 'uploads/'; /* Generates random filename and extension */ function tempnam_sfx($path, $suffix){ do { $file = $path."/".mt_rand().$suffix; $fp = @fopen($file, 'x'); } while(!$fp); fclose($fp); return $file; } /* Process image with GD library */ $verifyimg = getimagesize($_FILES['image']['tmp_name']); /* Make sure the MIME type is an image */ $pattern = "#^(image/)[^\s\n<]+$#i"; if(!preg_match($pattern, $verifyimg['mime']){ die("Only image files are allowed!"); } /* Rename both the image and the extension */ $uploadfile = tempnam_sfx($uploaddir, ".tmp"); /* Upload the file to a secure directory with the new name and extension */ if (move_uploaded_file($_FILES['image']['tmp_name'], $uploadfile)) { /* Setup a database connection with PDO */ $dbhost = "localhost"; $dbuser = ""; $dbpass = ""; $dbname = ""; // Set DSN $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname; // Set options $options = array( PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ); try { $db = new PDO($dsn, $dbuser, $dbpass, $options); } catch(PDOException $e){ die("Error!: " . $e->getMessage()); } /* Setup query */ $query = 'INSERT INTO uploads (name, original_name, mime_type) VALUES (:name, :oriname, :mime)'; /* Prepare query */ $db->prepare($query); /* Bind parameters */ $db->bindParam(':name', basename($uploadfile)); $db->bindParam(':oriname', basename($_FILES['image']['name'])); $db->bindParam(':mime', $_FILES['image']['type']); /* Execute query */ try { $db->execute(); } catch(PDOException $e){ // Remove the uploaded file unlink($uploadfile); die("Error!: " . $e->getMessage()); } } else { die("Image upload failed!"); } } ?> 

Итак, теперь мы сделали следующее:

  • Мы создали безопасное место для сохранения изображений
  • Мы обработали изображение с помощью библиотеки GD
  • Мы проверили образ MIME-типа
  • Мы переименовали имя файла и изменили расширение
  • Мы сохранили как новое, так и оригинальное имя файла в нашей базе данных
  • Мы также сохранили тип MIME в нашей базе данных

Нам все равно нужно показывать изображение посетителям. Мы просто используем столбец id нашей базы данных для этого:

 <?php $uploaddir = 'uploads/'; $id = 1; /* Setup a database connection with PDO */ $dbhost = "localhost"; $dbuser = ""; $dbpass = ""; $dbname = ""; // Set DSN $dsn = 'mysql:host='.$dbhost.';dbname='.$dbname; // Set options $options = array( PDO::ATTR_PERSISTENT => true, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION ); try { $db = new PDO($dsn, $dbuser, $dbpass, $options); } catch(PDOException $e){ die("Error!: " . $e->getMessage()); } /* Setup query */ $query = 'SELECT name, original_name, mime_type FROM uploads WHERE id=:id'; /* Prepare query */ $db->prepare($query); /* Bind parameters */ $db->bindParam(':id', $id); /* Execute query */ try { $db->execute(); $result = $db->fetch(PDO::FETCH_ASSOC); } catch(PDOException $e){ die("Error!: " . $e->getMessage()); } /* Get the original filename */ $newfile = $result['original_name']; /* Send headers and file to visitor */ header('Content-Description: File Transfer'); header('Content-Disposition: attachment; filename='.basename($newfile)); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); header('Content-Length: ' . filesize($uploaddir.$result['name'])); header("Content-Type: " . $result['mime_type']); readfile($uploaddir.$result['name']); ?> 

Благодаря этому скрипту посетитель сможет просмотреть изображение или загрузить его с его оригинальным именем. Тем не менее, он не может получить доступ к файлу на вашем сервере и не сможет ли он обмануть ваш сервер, чтобы получить доступ к файлу для него / нее, поскольку у него нет способа узнать, какой файл он , (S) он не может перенаправить ваш каталог загрузки, поскольку он просто не позволяет никому обращаться к каталогу, кроме самого сервера.

И это завершает мой безопасный сценарий загрузки изображений.

Я хотел бы добавить, что я не включал максимальный размер файла в этот скрипт, но вы должны легко сделать это самостоятельно.

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

Поскольку код просто для больших сообщений здесь, вы можете скачать класс из MEGA здесь:

Загрузить ImageUpload Class

Просто прочитайте README.txt и следуйте инструкциям.

Ну, загрузить файлы на PHP слишком просто и безопасно. Я рекомендую рассказать о:

  • pathinfo – возвращает информацию о пути к файлу
  • move_uploaded_file – перемещает загруженный файл в новое место
  • copy – копирует файл
  • finfo_open – Создайте новый файл fileinfo

Чтобы загрузить файл на PHP, у вас есть два метода: PUT и POST (возможно, больше ..). Чтобы использовать метод POST с HTML, необходимо включить enctype в FORM следующим образом:

 <form action="" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="Upload"> </form> 

Затем, в вашем PHP вам нужно поймать, что вы загружаете файл с $ _FILES следующим образом:

 $_FILES['file'] 

Затем вам нужно перейти от temp («upload») с помощью move_uploaded_file:

 if (move_uploaded_file($_FILES['file']['tmp_name'], YOU_PATH)) { // ... } 

И после загрузки файла вам нужно проверить расширение, и лучший и лучший способ использует pathinfo следующим образом:

 $extension = pathinfo($_FILES['file']['tmp_name'],PATHINFO_EXTENSION); 

Но расширение не является безопасным, потому что вы можете загружать файл с расширением .jpg, но с text/php mimetype text/php и это бэкдор. Итак, я рекомендую проверить реальный finfo_open с помощью finfo_open следующим образом:

 $mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $_FILES['file']['tmp_name']); 

И не используйте $_FILES['file']['type'] потому что иногда и в зависимости от браузера и клиентской ОС вы можете получать application/octet-stream , а этот тип mimetype – это реальный тип загружаемого вами файла.

Я думаю, с этим вы можете загрузить файл с безопасностью.

Извините мой английский, пока!