Категория > Новости > Роковые ошибки. Как искать логические уязвимости в веб-приложениях - «Новости»
Роковые ошибки. Как искать логические уязвимости в веб-приложениях - «Новости»13-01-2021, 00:00. Автор: Эдуард |
записывайся на мой курс по безопасности веб-приложений! Сразу предупрежу, что большинство задач, которые мы сегодня разберем, будут на языке PHP. Оно неспроста — подавляющее большинство сайтов и сервисов в интернете написаны именно на нем. Так что, несмотря на подпорченную репутацию этого языка, тебе придется разбираться в нем, чтобы хакерствовать серьезно. Сейчас же, в рамках этого занятия, знание PHP не является необходимостью, но, конечно, будет очень серьезным подспорьем. Впрочем, большинство уязвимостей не привязаны к конкретному языку или стеку технологий, так что, узнав их на примере PHP, ты легко сможешь эксплуатировать подобные баги и в ASP.NET, и в каком-нибудь Node.JS. А еще предупрежу, что задачки, которые мы сегодня разберем, не совсем начального уровня и совсем уж «валенкам» тут делать нечего — сначала стоит почитать матчасть и хоть немного представлять, с чем хочешь иметь дело. Если же ты можешь отличить HTTP от XML и у тебя не возникает вопросов вида «а что за доллары в коде?», то добро пожаловать! warningНи автор курса, ни редакция «Хакера» не несут ответственности за твои действия. Применение материалов этой статьи против любой системы без разрешения ее владельца преследуется по закону. Сегодня мы разберем несколько задач, которые я решал сам в рамках тренировки. Возможно, они покажутся тебе сложными, но не пугайся — всегда есть возможность отточить свои навыки на сайтах правительств специализированных сайтах для хакеров. Я сейчас говорю о HackTheBox и Root-me, которыми пользуюсь сам и всячески советую другим. Две из сегодняшних задач взяты именно оттуда. Задача 1Сначала я приведу код, с которым мы сейчас будем работать. $file = rawurldecode($_REQUEST['file']);$file = preg_replace('/^.+[/]/', $file);include("/inc/{$file}");?> По сути, тут всего три строки кода. Казалось бы, где тут может закрасться уязвимость? Чтобы это понять, давай разберем алгоритм, который здесь реализован. Вообще, при аудите кода стоит уметь читать его построчно. Тогда проще понять, что именно может пойти не так.
Итак, что может пойти не по плану? Как ты уже, конечно, догадался — проблема в функции очистки ввода (которая Шпаргалка Тут прямо написан ответ, как обойти защиту (подсказка: ищи справа). Видишь точку? А шапочку ( Ключевое тут «кроме переноса строки». Если в начале строки будет перенос строки — регулярка не отработает и введенная строка попадет в infoНа самом деле нормальные PHP-шники так файлы не подгружают. Рассмотренная задача — просто пример, хотя, по личному опыту, даже такие безнадежно небезопасные программы до сих пор нередко встречаются. В крайнем случае, можно попробовать найти поддомены вида Собственно пример чтения файла: Результат Задача 2Это задачка с root-me, где ты, возможно, уже видел ее. Но мы все равно рассмотрим ее подробнее — она относится к реалистичным, и шансы встретить что-то подобное в жизни немаленькие. В задании нам дается простой файлообменник и просят получить доступ к панели админа. Интерфейс файлообменника Интерфейс крайне прост: есть кнопка загрузки файла на сервер и просмотр загруженных файлов по прямым ссылкам. Забегая вперед, скажу, что грузить скрипты на PHP, bash и прочие — бесполезно, проверки реализованы верно и ошибка в другом месте. Обрати внимание на нижнюю часть страницы, а точнее — на фразу «frequent backups: this opensource script is launched every 5 minutes for saving your files». И приведена ссылка на скрипт, вызываемый каждые пять минут в системе. Давай глянем на него пристальнее: BASEPATH=$(dirname `readlink -f "$0"`)BASEPATH=$(dirname "$BASEPATH")cd "$BASEPATH/tmp/upload/$1"tar cvf "$BASEPATH/tmp/save/$1.tar" * Казалось бы — что тут такого? На параметры ты влиять не можешь, а мантру призыва tar вообще знаешь как свои пять пальцев. А проблема в самой мантре: тут она написана не полностью. Точнее, не в том виде, как ее увидит сам tar. Что делает звездочка? Вместо нее bash подставит имена всех файлов в текущей папке. Вроде ничего криминального. А давай обратимся к мануалу на Tar, который нам любезно предоставлен вместе с условием задачи. Интересности в Tar Вот это место представляет для нас самый большой интерес. Дело в том, что tar имеет несколько особых возможностей для гибкого мониторинга процесса архивации со стороны. Это достигается с помощью так называемых чек-пойнтов, у которых могут быть свои определенные действия. Одно из действий — Теперь вспомним про звездочку: вместо нее шелл (bash) подставит список всех файлов в текущей папке, при этом они могут иметь любые имена. В том числе такие, которые будут восприняты архиватором как специальные параметры. Таким образом, нам надо подсунуть файлы с именами в виде аргументов tar. Я использовал такие:
#!/bin/sh
cp ../../../admin/index.php ./
Просто заголовок и команда копирования админской панели в текущую папку. Естественно, тут мог быть реверс-шелл или еще что-то, но для решения конкретно этой задачи такая «тяжелая артиллерия» не нужна. Теперь дожидаемся выполнения нашего шелла — и увидим в окне файлообменника файл админ-панели в виде простого текста. Осталось только открыть его и найти там пароль! Пароль в чистом виде Задача 3Тут у нас плагин для WordPress, который позволяет запись аудио и видео. Я не буду просить тебя найти уязвимость, а сразу покажу ее. Уязвимое место Как видно из строк 247–251 на скриншоте, не предусмотрено никаких проверок на тип или содержимое файла — это просто классическая загрузка! Есть, правда, ограничение: файл грузится в стандартную директорию WordPress ( Но непорядок не в том, что имя файла меняется, а в том, что делается это с помощью функции
Смекаешь? Уникальный идентификатор, полученный с помощью Так как PHP — проект открытый, мы можем подсмотреть исходники функций стандартной библиотеки. Открываем исходник
uniqid = strpprintf(0, "%s%08x%05x", prefix, sec, usec);
Что тут происходит? А то, что возвращаемое значение зависит исключительно от текущего времени, которое в рамках одной планеты вполне предсказуемо. Хоть выходная последовательность и выглядит случайной, она таковой не является. Чтобы не быть голословным, вот пример имени файла, сгенерированного таким алгоритмом:
5ff21d43dbbab_shell.php
Полученное значение легко можно конвертировать обратно в дату и время его генерации:
echo date("r", hexdec(substr("5ff21d43dbbab", 0, 8)));
// Sun, 03 Jan 2021 11:38:43 -0800
Конечно, брутить все 13 символов — вши заедят, но у нас есть способ получше: мы можем пробрутить варианты на основе времени загрузки плюс-минус полсекунды, чтобы нивелировать разбежки часов на клиенте и сервере. А можно просто поверить, что часы у обоих хостов точные, а значит, можно проверить не миллион вариантов (1 секунду), а только варианты, возможные между временем отправки запроса и временем получения ответа. На шустром канале это будет порядка 300–700 мс, что не так и много. infoКонечно, не все реальные кейсы требуют глубоких познаний в PHP или другом серверном языке. Многие ошибки можно найти, даже не открывая код — с помощью автоматических сканеров. Подробнее о них — в нашей статье об автоматическом взломе. Они здорово помогают, так что не грех иметь парочку под рукой для экспресс-анализа! Я набросал простой скрипт на Python для демонстрации такой возможности. Его код представлен ниже:
#!/usr/bin/env python3
import requests, time
url = 'http://example.host/wordpress/wp-admin/admin-ajax.php'
data = {
'audio-filename': 'file.php',
'action': 'save_record',
'course_id': 'undefined',
'unit_id': 'undefined',
}
files = {
'audio-blob': open('pi.php.txt', 'rb')
}
print(time.time()) # Время отправки запроса
r = requests.post(url, data=data, files=files)
print(time.time()) # Время ответа
print(r.headers)
Нам нужно запустить его несколько раз, чтобы подобрать минимальное время между отправкой запроса и получением ответа — это позволит уменьшить время перебора. Также нужно помнить, что разбежки все же могут быть, и чисто на всякий случай стоит проверить, насколько локальное время соответствует времени на сервере. Частенько оно возвращается сервером в заголовке Теперь брутим: import sys, timetry: Как бы еще оптимизировать перебор? Ну, во-первых, питон сам по себе очень медленный и, конечно, не смог бы выполнить соединение, передачу заголовков, отправку файла и прочие мелкие накладные расходы в тот же момент. А интерпретатор PHP на стороне сервера едва ли моментально проверит права, запустит скрипт, отработает служебные функции и дойдет до собственно уязвимого места. Тут можно накинуть эдак тысяч сто микросекунд без малейших потерь. Во-вторых, выполнение Вот так на ровном месте мы сократили перебор на 200 000 запросов. Много это или мало? В моем случае это сократило количество запросов еще примерно на треть. Осталось порядка 500 000 вариантов, которые можно перебрать в пределах часа или даже меньше — у меня это заняло минут 15. Теперь давай напишем еще один скрипт, который и будет искать наш шелл с использованием этого алгоритма: import timeimport threadingimport requestsfrom threading import Locktry: Вот и всё: запускаешь, через некоторое время получаешь путь, и хост захвачен! Наверняка у тебя возник вопрос, нельзя ли как-то еще усовершенствовать этот перебор, потому что 500 тысяч вариантов — это все равно как-то многовато? Можно, но такого значимого ускорения, как раньше, уже не будет. Суть в том, что можно идти не от начала промежутка времени к концу, а от середины к краям. По опыту, это работает несколько быстрее. Другой способЕсть и способ попроще. Заключается он в следующем: новый путь к файлу формируется как Задача 4Последняя на сегодня задачка — тоже с root-me и тоже из категории реалистичных, но заметно посложнее. Сервис Web TV — новейшая французская разработка в сфере интернет-телевидения. Но нас интересует не новая дешевая трагедия, а админка. Главная страница Web TV. Простите за мой французский Только — вот незадача — Gobuster никаких признаков админки не обнаружил. Придется изучать, что нам доступно. А доступен логин (там форма авторизации) и ссылка на неработающий эфир. Попробуем залогиниться и перехватить запрос на авторизацию с помощью Burp. Буква З в слове «реальность» означает «защищенность» Запрос отправляем в Repeater (повторитель). Пусть пока там полежит. Взглянем еще разок на форму логина. Какие мысли тебя посещают, когда ты видишь форму для авторизации? Конечно, SQL-инъекция! А давай ткнем туда кавычку. Написали. Отправляем. Хм, ничего не поменялось. А как вообще узнать, что что-то поменялось? Смотри на заголовок Content-Length в ответе: в нашем случае там приходит ровно 2079 байт, если инъекции не было, и, очевидно, придет сильно другой результат в противном случае. Я попробовал еще немного, и инъекция так просто не выявилась, так что давай поищем в другом месте, а потом вернемся к этому запросу. Теперь посмотрим в адресную строку. Похоже, на сервере включен Я попробовал перейти на страницу Ошибка интерпретатора В первом сообщении об ошибке видна часть пути ( И действительно — сайт просто подставляет параметр в путь и пытается прочитать несуществующий файл Методом научного тыка был обнаружен параметр /?action=../../index.php Видно не все, но если открыть ответ в Burp или даже просто просмотреть код страницы браузером — открывается полный исходник. Вот тебе и directory traversal налицо. Результат обхода каталога Помнишь, мы не могли найти путь к админке? А на скриншоте он есть: именно на него будет редирект, когда скрипт проверит логин и пароль. infoВзглянем поподробнее на функцию Давай, не отходя от кассы, сразу и его прочитаем — вдруг там что-нибудь интересное есть. require_once '../inc/config.php';function decrypt($str, $key) { Код успешно прочитан, и видна интересная функция Если дальше прочитать код, то видна защита от спецсимволов в имени пользователя, потом из базы извлекается пароль и расшифровывается функцией выше. Казалось бы — вот оно, но сначала надо выяснить имя пользователя, которого у нас пока нет. Забегая вперед: все баги, которые есть в этом приложении, находятся в двух рассмотренных файлах, и других тут нет. Теперь, чтобы эксплуатировать дальше, давай вернемся к прошлому файлу и рассмотрим его код еще раз.
Присмотрись к вызову функции хеширования: помнишь ли ты, что означает второй параметр (
То есть вернется некоторая бинарная последовательность, которая будет распознана как строка. Нам нужно, чтобы последний байт был равен
И для этого нужно только подобрать такой символ из мультибайтовой кодировки, чтобы его последний байт был равен for ($i = 1; $i <= 10000; $i++) {$hash = sha1($i);if (substr($hash, 38, 2) == "5c") {echo $i." - ";die(sha1($i, true));} }?> На самом деле даже 10 000 вариантов — оверкилл, потому что Выполнилось все очень быстро — подошло уже число 17. Теперь у нас есть «правильный» пароль. Нужно посмотреть, какая будет реакция сервиса. Помнишь наш запрос на логин в Burp? Подставляй в качестве пароля число 17, а в логин — классический Печально, правда, что никакого результата из запроса не выводится. Как это побороть? Использовать любые шаблоны time-based, boolean-based или error-based. Мой любимый payload в таких случаях —
SQL error :XPATH syntax error: ':5.7.32-0ubuntu0.16.04.1'
Инъекция работает, пусть и выводит не больше 31 символа за раз. А нам большего и не надо. Видоизменим инъекцию немного, чтобы получить логин:
AND extractvalue(1,concat(0x3a,(select login from users limit 0,1)))
Ответ:
SQL error :XPATH syntax error: ':administrateur'
И теперь пароль:
AND extractvalue(1,concat(0x3a,(select passwd from users limit 0,1)))
И вот он:
SQL error :XPATH syntax error: ':e79c4da4f94b86cba5a81ba39fed083'
Но не все так просто. Как ты помнишь, длина хеша SHA-1 в шестнадцатеричной кодировке — 40 символов, а нам вернулись 31. Непорядок! Чтобы это исправить, просто возьмем функцию
AND extractvalue(1,concat(0x3a,(select right(passwd,20) from users limit 0,1)))
И вот наши последние 20 символов:
SQL error :XPATH syntax error: ':1ba39fed083dbaf8bce5'
Полный хеш — Дальше нужно обойти проверки в
Это все осталось лишь обернуть в заголовки PHP и запустить — и пароль у нас в руках! Разбор этих задач на вебинаре (видео)Напоследок напомню еще раз, что 18 января 2021 года я начну занятия по безопасности веб-приложений. Спеши присоединиться! Перейти обратно к новости |