20 февраля 2012

Пример удаленного выполнения PHP кода после загрузки изображения на сайт

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

Началось все с того, что клиент обратился с жалобой, что в браузере не отображался загруженный ими логотип. Логотип оказался "гифкой" с расширением .tif, который, естественно браузер и не отображал.

Просмотр кода показал, что существует проверка на то, что загружаемый файл - это изображение в формате GIF, JPEG или PNG и размеры данного изображения не должны превышать 300 пикселей по вертикали и горизонтали. Но вся беда в том, что после данной проверки скрипт перемещяет данный файл в надлежащую директорию, изменяя имя файла, но сохраняя его расширение.

Таким образом на сервер можно загрузить картинку с расширением .php. Что не мудрено, при запросе данной картинки браузером, интерпретатор PHP обработает этот файл. Уязвимость на лицо.

Теперь стоит задача: как сформировать файл так, чтобы он и валидацию при загрузке прошел и интерпретатор его выполнил.

Если просто добавить в конец графического файла PHP-код, то интерпретатор отказывается его выполнять (пробовал на PHP 5.3.10). Экспериментальным путем было обнаружено, что интерпретатору мешает наличие в файле символа с кодом 0h - индикатор конца строки в Си. Меняем в файле все вхождения данного символа на что-нибудь другое, и вуаля - интерпретатор успешно выполняет инструкции в конце файла.

Теперь осталось загрузить такой файл на сервер, чтобы его пропустил код валидации.

Сам код валидации основан на валидаторе из состава используемого нами фреймворка, принимающий список допустимых mime-type'ов файла а сам, для определения типа бинарного файла, использует внешнюю программу file (на *nix платформе).

Возьмите любую "гифку", поместите в конец файла PHP код и file покажет, что это image/gif, поскольку читает только заголовок файла.

# file -bi untitled-1.gif.php
image/gif

Фокус в том, что после замены нами символа с кодом 0h на любой другой, файл по прежнему детектируется как image/gif.

Финальный шаг: нужно "подогнать" изображение, чтобы его размеры не превышали 300 пикселей. Из описания GIF формата находим, какие байты информируют о размерах изображения - седьмой и восьмой байты описывают ширину, девятый и десятый - высоту. Если изображение будет меньше, чем 256 на 256 точек, то "старшие" - седьмой и девятый - байты будут нулевыми, что нам никак на подходит. Поэтому заменяем с седьмого по десятый байты на символы c кодом 01h и получаем картинку с размером 257 на 257 пикселей.

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

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

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