Подключение произвольного файла в работу уязвимого файла на локальном сервере с помощью временных загрузочных файлов при загрузке по стандарту rfc1867

Подключение произвольного файла в работу уязвимого файла на локальном сервере с помощью временных загрузочных файлов при загрузке по стандарту rfc1867

Статья описывает способ использования уязвимости LFI (Local File Inclusion): подключение произвольного PHP-скрипта на локальном сервере.

Автор: Gynvael Coldwind

Введение

Статья описывает способ использования уязвимости LFI (Local File Inclusion): подключение произвольного PHP-скрипта на локальном сервере. В статье не описываются уязвимости в самом PHP-движке или какой-то принципиально новый класс уязвимостей.

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

Одна из проблем безопасности PHP-приложений, заключается в необходимости проверки, действительно ли подключение противником файла на локальном сервере (LFI-атака) позволяет выполнить произвольный код?

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

  • подключение загруженных файлов. Достаточно прямолинейный метод, для которого требуется наличие на проверяемом сайте сервиса загрузки (например, загрузка фото или документов) и возможность доступа php-скриптов к загруженным файлам, а также к месту их хранения.
  • подключение псевдо-протоколов data:\\ или php:\\input. Необходимо, чтобы эти протоколы были доступны, и их можно было бы подключить с помощью директивы include (для этого параметр allow_url_include = On). Также в некоторых случаях можно использовать псевдо-протокол php:\\filter.
  • подключение файлов журнала. Для этого способа необходимо, чтобы PHP-скрипт имел доступ к различным файлам журнала, например файлам журнала ошибок httpd-сервера или файлам журнала доступа; размер файлов журнала может усложнить проведение атаки (например, если файл журнала ошибок превышает 2 ГБ).
  • подключение /proc/self/environ/. PHP должен использоваться в режиме CGI в среде с псевдо-файловой системой /proc, и PHP-скрипт должен иметь доступ к этой псевдо-файловой системе.
  • подключение файлов сессии. В этом случае требуется, чтобы противник мог изменять значение любой строки в сессии (например, для вставки кода <?phpphpinfo();?>); состояние сессии хранилось в сериализованном файле сессии (по умолчанию в PHP: x|s:19:”<?phpphpinfo();?>)”), и PHP-скрипт имел доступ к файлу сессии (обычно он называется /tmp/sess_SESSIONID).
  • подключение других файлов, созданных PHP-приложением. Использование этого способа очень сильно зависит от конкретного приложения, системы и функционала веб-сайта. Под другими файлами обычно понимают файлы базы данных, файлы кэша, файлы журнала приложения и.т.д.

Среди других способов проверки безопасности PHP-приложения можно выделить проверку на ядовитый NULL-байт [1] (уязвимость исправлена в PHP версии 5.3.4; дата релиза 09.12.2010) и добавление лишнего слеша (/) в адресную строку [2] (уязвимость исправлена в 2009 г.).

Подключение временного загрузочного файла

Еще один способ проведения описываемой атаки основывается на особенностях обработки файлов, загружаемых через HTTP. Об этом способе практически никто не знает, и реально он осуществим только на Windows-платформах (подробности можно найти в разделе “Проведение атаки под Linux ”). На самом деле, когда я начал писать эту статью, то думал, что об этом способе вообще никто не знает, но после нескольких дней поисков я нашел человека, который обнаружил этот способ атаки раньше меня. Тем не менее, других доказательств общеизвестности этого способа я не нашел, поэтому и решил опубликовать статью.

После получения POST-пакета с файлом(ами), закодированным(ыми) согласно стандарту RFC 1867, PHP-движок создает один или несколько временных файлов для хранения данных из загружаемых файлов. Скрипту-обработчику требуется переместить временной загрузочный файл в определенное место с помощью функции move_uploaded_file (если файл должен остаться на сервере после завершения скрипта). После завершения скрипта PHP-движок удаляет все временные файлы (если таковые имеются).

На следующем рисунке показана последовательность действий в описываемой ситуации:

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

Преимущества и недостатки метода

Преимущество метода заключается в том, что у PHP-скрипта есть полные права доступа на директорию, в которой создаются временные файлы (см. параметр upload_tmp_dir в php.ini); другими словами, скрипт может подключить файлы из этой директории. При стандартной установке параметр upload_tmp_dir не устанавливается, и в качестве директории для хранения временных файлов используется /tmp в Linux-системах, либо С:\Windows\Temp в Windows.

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

Проведение атаки под Windows

В Windows для генерации случайного имени временного файла PHP использует функцию GetTempFileName. В документации про эту функцию можно найти следующую информацию:

Функция GetTempFileName создает имя временного файла следующего вида: <path>\<pre><uuuu>.TMP

В случае с PHP <path>это upload_temp_dir (обычно С:\Windows\Temp), <pre> – это “php” (без кавычек), последняя часть <uuuu> – шестнадцатеричное значение параметра uUnique.

uUnique представляет собой один из аргументов функции GetTempFileName, и в случае с PHP данный аргумент устанавливается в 0, что говорит функции использовать текущее системное время. В документации также можно найти важное замечание:

Используются только младшие 16 бит параметра uUnique. Следовательно, если значение параметров lpPathName и lpPrefixString не меняются, то функция GetTempFileName сгенерирует всего 65535 уникальных имен файлов.

Такое малое количество различных имен можно просто перебрать, а использование текущего системного времени (в миллисекундах) еще больше упрощает атаку.

Однако, благодаря некоторым особенностям функции FindFirstFile [3], атака перебором может даже и не понадобиться. В Windows эта функция позволяет использовать маски (“<<” вместо ‘*’ и ‘>’ вместо ‘?’) при поиске локального файла. Поэтому можно сформировать путь включения файла следующего вида:

http://site/vuln.php?inc=c:\windows\temp\php<<

Как правило, в директории могут оказаться другие файлы с префиксом “php”, и из-за этого маску придется уточнять. В таком случае, лучше выбрать маску, под которую не подойдет ни один файл (например, php1<< или phpA<<, в худшем случае php11<< и.т.д.) и загружать файлы, пока PHP-движок не сгенерирует временный файл с подходящим под маску именем.

Проведение атаки под Linux

В GNU/Linux для генерации имен временных файлов PHP-движок пользуется функцией mkstemp из библиотеки GNU libc. Эта функция, в зависимости от сборки glibc, может генерировать случайное значение одним из трех способов (далее следует описание на псевдокоде, все переменные типа uint64_t):

  1. random_value = (seed += time()^PID)
  2. random_value = (seed += (gettimeofday().sec << 32 | gettimeofday().usec)^PID)
  3. random_value = (seed += rdtsc^PID)

Затем random_value представляется в виде 6-значного числа в системе исчисления с основанием 62 (набор символов из множества A-Za-z0-9) и добавляется к префиксу “/tmp/php” (если для хранения временных файлов не используется другая директория), например, /tmp/phpUs7MxA.

Из всех трех способов генерации случайного значения наиболее часто сейчас применяется последний.

Проведенные мной тесты показали, что случайные значения в достаточной степени непредсказуемы для удаленного противника.

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

  • У противника есть возможность получить список всех файлов в директории загрузки (/tmp) (например, с помощью другого PHP-скрипта). В этом случае от противника потребуется “быстрота реакции”: он сначала должен будет загрузить один файл с помощью медленного скрипта (т.е. скрипта, выполняющегося относительно долго), затем получить список всех файлов в директории загрузки, найти необходимый временный загрузочный файл, и успеть провести LFI-атаку.
  • У противника есть возможность получить массив $_FILES. Сейчас ловкости противнику потребуется еще больше, и не факт, что атака пройдет успешно, так как массив $_FILES необходимо получить во время загрузки, а затем еще успеть отправить другой пакет до того, как завершится скрипт, и временной файл будет удален. Если в PHP включена буферизация вывода (например, включен режим mod_gzip в Apache), то это вообще неосуществимо.

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

См. также последний абзац в разделе “Заключительные замечания”.

Заключительные замечания

Один из способов, которым я хотел увеличить период существования временного файла, заключался в том, чтобы создать HTTP-пакет с 20 файлами (по умолчанию в PHP это максимум для HTTP-пакета) и отослать первые 19 файлов с нормальной скоростью, а последний файл с медленной скоростью (байт, пауза, байт, пауза, повтор). Теоретически, предыдущие загруженные файлы из одного пакета должны по-прежнему существовать, пока не закончится загрузка. Тем не менее, оказывается, что PHP-движок не начнет обрабатывать загружаемые файлы до тех пор, пока не прибудет весь HTTP-пакет, и до этого момента временные файлы просто не создадутся.

Также в некоторых случаях, когда httpd имеет доступ к директории /proc/self/fd (как правило, права httpd понижены до прав www-data, а владельцем /proc/self/fd является root, и для доступа к этой директории нужны его права) можно попробовать загрузить файлы (в одном потоке) и подключить /proc/self/fd/XYZ к другому файлу (например, начать с XYZ=10). Однако дескриптор временного файла существует только в период с его открытия до закрытия, а это очень короткий промежуток времени. Загрузка больших файлов может слегка увеличить этот промежуток.

Направления для дальнейшего исследования

Существуют некоторые возможные улучшения и направления для дальнейшего исследования описанного способа атаки:
  • попробовать предугадать результат работы функций mktemp и mkstemp в других системах (*BSD, Solaris, и.т.д.), либо в других реализациях библиотеки libc;
  • провести глубокий анализ функции mkstemp в GNU/Linux и узнать, сможет ли локальный или удаленный противник предугадать результат этой функции, не обладая никакой информацией, либо зная только последнее сгенерированное имя;
  • уловка с FindFirstFile пока срабатывает, но в скором времени эту уязвимость могут устранить, поэтому стоит также исследовать возможность предугадывания имени временного файла в Windows.

Ссылки

  1. PHP 5.3 Changelog. http://php.net/ChangeLog-5.php
  2. Francesco "ascii" Ongaro, Giovanni "evilaliv3" Pellerano. PHP filesystem attack vectors. http://www.evilaliv3.org/articles/php-filesystem-attack-vectors/
  3. Vladimir Vorontsov, Arthur Gerkis. Oddities of PHP file access in Windows®. Cheat-sheet, 2011. http://onsec.ru/onsec.whitepaper-02.eng.pdf

Благодарности

Выражаю особую благодарность Феликсу Грёберту за интересную беседу, которая привела к написанию этой статьи.

Отказ от обязательств

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

Устали от того, что Интернет знает о вас все?

Присоединяйтесь к нам и станьте невидимыми!