Безопасная заливка файлов на сайт. Одно из тысячи решений

от автора

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

Как Вы все уже хорошо знаете, самый надежный способ защиты при заливке файлов, это:

1. Переименовывать файлы в имена и расширения файлов независимо от входящего имени файла.
2. Отключить в папке, куда заливаются файлы, выполнение скриптов.

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

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

Для лучшего восприятия сейчас реализуем примитивный файлообменник. Для этого нам понадобится: папка, назовем ее, например «files», и 2 скрипта «upload.php» и «get.php».

В скрипте «upload.php» реализуем заливку файла с преобразованием имени в hex-код. Т.е. например, если пользователь решил залить файл «index.php» то он нормально зальется в папку, но с именем «696e6465782e706870» что полностью безопасно для сервера и не выполнится PHP-обработчиком.

Вот скрипт «upload.php»:

<? $uploaddir = "files/";  function StringToHex($s) {     $hex = "";     for ($i=0;$i<strlen($s);$i++) $hex=$hex.dechex(ord($s[$i]));     return $hex; }  if (isset($_FILES['userfile'])) { 	// Преобразуем имя файла в hex 	$new_name = StringToHex(strtolower($_FILES['userfile']['name'])); 	if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploaddir . $new_name)) 	{ 		// Ok... 	} else { 		// Error... 	} } ?> <html> <body> <form enctype="multipart/form-data" method="post"> <input name="userfile" type="file" /><input type="submit" value="Сохранить" /> </form> </body> </html> 

Сейчас займемся папкой, куда будут попадать файлы. Первым делом поставим ей права 777 и создадим в ней файл «.htaccess»:

RewriteEngine on RewriteRule (.*) get.php?file=$1 [L,QSA] 

Ну и осталось добавить в папку «files» скрипт «get.php», который будет отдавать файлы пользователю, для наглядности я в него добавил возможность просмотра файлов в папке с их реальными именами (ответ для тех читателей, которые спросят типа «а зачем все это было, если можно было имена шифровать в md5» и.т.д.).

<? function StringToHex($s) {     $h = "";     for ($i=0;$i<strlen($s);$i++) $h=$h.dechex(ord($s[$i]));     return $h; }  function HexToString($h) {     $s = "";     for ($i=0;$i<strlen($h)-1;$i+=2) $s=$s.chr(hexdec($h[$i].$h[$i+1]));     return $s; }  if (empty($_GET["file"])) { 	// Для наглядности (вывод содержимого папки) 	if ($dh = opendir(".")) 	while (($file = readdir($dh)) !== false) 	{ 		if (preg_match("/^[a-z0-9]+$/", $file)) 		echo '<a href="',HexToString($file),'">',HexToString($file),'</a><br />'; 	} 	closedir($dh); 	exit; }  $file_to_user = $_GET["file"]; $file_name = StringToHex(strtolower($_GET["file"]));  if (file_exists($file_name)) { 	// Если имя файла поле преобразования совпало с существующим файлом - отдадим его пользователю на скачивание 	if (ob_get_level()) ob_end_clean();	 	header('Content-Description: File Transfer'); 	header('Content-Type: application/octet-stream'); 	header('Content-Disposition: attachment; filename='.basename($file_to_user)); 	header('Content-Transfer-Encoding: binary'); 	header('Expires: 0'); 	header('Cache-Control: must-revalidate'); 	header('Pragma: public'); 	header('Content-Length: '.filesize($file_name)); 	readfile($file_name); } ?> 

Таким образом, для пользователей сайта всегда будут доступны файлы (на скачивание), залитые в папку по нормальным ссылкам:

http://site.xx/files/img.gif http://site.xx/files/index.php http://site.xx/files/jquery.js http://site.xx/files/readme.txt 

В реальности же в папке «files» будут файлы с именами:

696e6465782e706870 696d672e676966 6a71756572792e6a73 726561646d652e747874 

Ну, вот собственно и все. Естественно есть куча других вариантов реализации защищенной заливки, особенно красиво все можно реализовать с использованием БД, я же решил предоставить свой метод приема/отдачи файлов в их естественном виде, без запретов на скрипты, и при этом с безопасностью для сайта. Для более высокой защиты (например: от повторных имен файлов, и.т.д.) скрипты нужно улучшать, тут только базовая идея реализована.

ссылка на оригинал статьи http://habrahabr.ru/post/183000/


Комментарии

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

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