БАГодельня
2.23K subscribers
190 photos
2 videos
128 links
Канал про разработку и безопасность: код, костыли, разбор задач, ревью и размышления.

Вещает руководитель отдела разработки: @ipatove
Download Telegram
Костылим со всей силы в sql-запросах

📖 Пару лет назад был пост с кодревью, где числовой постфикс к имени файла находился брутфорсом, перебирая все доступные имена. Освежить в памяти можно тут: Новая папка(123). Сегодня получилось реализовать тоже самое только с базой данных. Сперва не планировал писать, но задачка получилась интересная и не стандартная – костыль, который вряд ли кому-то пригодится в том виде, как он есть, однако показывает не самые стандартные возможности SQL.

📌 Что имеем? Есть таблица в БД mysql, куда пишутся записи с некоторыми данными, а одно из полей хранит уникальный ключ такого вида: keyname_123, где 123 – это инкремент для уникальности. Нужно узнать самый большой постфикс для ключа и добавить запись с ключом keyname_(N+1).
Важное примечание. В силу некоторых обстоятельств, сделать по-человечески, и вынести индекс ключа в отдельный столбец, и по нему уже делать выборку, в данном случае нельзя. Можем работать только с такой строкой.

📌 Как реализовано? С помощью while цикла делались запросы к таблице для проверки существования записи с ключом keyname_i, где i – инкремент от 0 и до конца, с увеличением на единицу. Цикл крутится, пока запрос к БД не вернет пустой ответ, это сигнализирует о найденном индексе ключа, который можно использовать для новой записи. В данном случае индексов может быть не много, не более десятка, но что может стать с проектом в будущем никто не знает, и такой поиск индекса будет серьезной проблемой при увеличении объемов. Да и в целом, перебор значений — это уж совсем крайний случай.

📌 Как сделать получше? А теперь несколько вариантов, как еще можно решить эту задачу, не мучая БД лишними запросами.

1. Так же перебором, но гонять в цикле не запросы к БД, а данные. Сделать запрос вида:
SELECT key_column 
FROM tbl_test
WHERE key_column LIKE 'kek_%';

Достаем все ключи и находим самый большой индекс. Такой вариант будет работать быстрее и меньше грузить БД на небольших объемах данных.

2. Для mysql 8.0+ версий можно написать запрос и регуляркой отрезать от ключа текстовую часть, оставив только числовой постфикс. И сразу же в запросе получить максимальное значение. То есть один запросом получаем сразу максимальный индекс. Запрос:
SELECT MAX(CONVERT(REGEXP_SUBSTR (key_column, '[0-9]+$'), INT)) AS max_prefix
FROM tbl_test
WHERE key_column like 'kek_%';


3. Для mysql ниже 8 версии, где нет поддержки REGEXP_SUBSTR, тоже можно реализовать запрос, как во втором примере, но через предварительный подсчет символов до числа. Запрос:
SELECT MAX(CAST(SUBSTRING(key_column, 5) AS UNSIGNED)) AS max_prefix
FROM tbl_test
WHERE key_column LIKE 'kek_%';


Если у кого есть еще идеи sql-запросов, кидайте в комментарии, может быть есть более простые или интересные варианты.

———
#️⃣ #sql #костыли

🐞БАГодельня: Канал // Чат
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥5👍21
Выборка WHERE txt_field = 0 для текстовых полей

📖 Сразу оговорюсь – я понимаю, что никому в здравом уме не придет идея делать подобные запросы умышлено. Но понимание таких нюансов может пригодиться для упрощения дебага, а также при каких-то нестандартных ситуациях, не связанных с разработкой для продакшена.

Как раз во время поиска проблемы с выборками из БД и был обнаружен подобный запрос. В коде был баг, который при определенных обстоятельствах превращал параметр строкового ключа в число ноль и в типе int использовался для запроса к базе данных MySql. Не сразу обнаружился ноль в запросе, и пришлось повозиться, прежде удалось понять, почему вместо выборки по строковому ключу, возвращаются вообще все данные из таблицы.

📌 Странные запросы и результаты
На скрине упрощенная таблица БД, которая состоит из двух полей – id и txt_field. Поле txt_field имеет тип varchar и хранит строки. Если сделать такой sql запрос:
SELECT * FROM `tbl_test` WHERE `txt_field` = 0;

В ответ получим три строки:
id  txt_field
1 kek
2 kek_1
3 kek_2

А такой запрос:
SELECT * FROM `tbl_test` WHERE `txt_field` = 1;

Вернет одну строку:
id  txt_field
4 1_kek


📌 Почему так происходит?
Странные выборки получаются и не очень очевидные. Но это нормальное поведение MySql. Когда в запросе нужно сравнить число и строку, MySql пытается сделать приведение к одному типу данных. Но приведение типов с MySql работает по своим правилам, вот таким:
– Читает строку слева направо до первого нечислового символа
– Если в начале строки нет цифр, результат преобразования будет равен нулю
– Если есть цифры, то берет только числовую часть и отбрасывает остатки
Поэтому все строки, которые начинаются с нечисловых символов, становятся равны нулю и удовлетворяют условиям выборки WHERE txt_field = 0. Однако, если в начале строки есть число, то такая строка преобразуется в числовое значение, поэтому запрос с WHERE txt_field = 1, выдаст результат с «1_kek».

📌 Примеры преобразований строк в число:
'123kek' → 123
'kek_1' → 0 (первый символ не цифра)
'1_kek' → 1
'1.23kek' → 1.23
'kek' → 0
'' (пустая строка) → 0


———
#⃣ #Костыли #sql #Типы

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥84👍2
Слепая SQL-инъекция. Часть 1

📖 Про sql-инъекции уже был пост, в том примере был понятен результат запросов – при добавлении инъекций, сайт отдавал результат выполнения запросов к базе данных. Это был описан пример UNION-инъекции.

Но такого «подарка» с удобным дебагом не всегда можно получить, чаще уязвимые эндпоинты ничего не отвечают или результат запроса можно определить только по косвенным данным. Когда есть хоть какой-то признак успешного выполнения запроса, то логика сводится к бинарной – успешно прошел запрос или нет. В таких случаях инъекции нужно строить с условиями. В случае вставки или удаления данных все понятно, а для получения значений, нужно немного помудрить. Целиком получить имя БД, таблиц и значения из них уже не получится, однако можно вытянуть нужную информацию по символу – разделяя результат посимвольно с помощью substr и перебором сравнивая с алфавитом.

📌 Пример инъекции:
1 AND (SELECT SUBSTR(flag_column,{position},1) FROM flag LIMIT 1) = '{char}'

Где {position} – это позиция символа, который пытаемся определить для записи из колонки flag_column в таблице flag. А {char} – это символ, с которым сверяемся. Если мы угадали первый символ, то запрос будет содержать какой-то ответ, поскольку условие вернет true. Так перебирая по одному символу и опираясь на статус ответа запроса, можно получить не только значения из таблицы, но и саму структуру таблиц. Например, в mysql можно сделать запрос к INFORMATION_SCHEMA, которая возвращает список и описание таблиц.

📌 Чтобы не делать полный брутфорс 95 печатных символов ASCII(от 32 до 126), из которых может состоять текст(имена таблиц, содержимое и тд), можно ускорить поиск в 13-14 раз с помощью бинарного поиска: log₂(95) ≈ 7 запросов на один символ. В самой инъекции меняется только условие >= '{char}', вместо равенства стало неравенство. Так мы существенно оптимизируем поиск.

📌 Однако, если сервер совсем никак не реагирует на инъекции и всегда отвечает одинаково, такой подход не сработает, поскольку нет никаких признаков, которые бы сигнализировали об успешном нахождении правильного символа. Но и такую проблему можно решить – с помощью техники Time-based. При успешном выполнении условия, мы умышленно загружаем и/или делаем паузу на стороне сервера, чтобы по времени ответа можно было определить успешность запроса. В mysql такое легко сделать с помощью: SLEEP(3), где 3 – это количество секунд, на которые «задумается» mysql. Дальше, остается только изменить логику обработки – вместо самих ответом запросов, опираться на их время выполнения. В некоторых СУБД нет sleep или он может быть отключен в настройках. В подобных случаях можно самим сделать задержку – использовать в запросах сложные или рекурсивные расчеты, которые так же вызовут задержку ответа. К примеру, в sqlite нет sleep, но есть функция randomblob, которая генерирует рандомные байты, и если запросить большое количество, то запрос может подвисать на несколько секунд.

📌 Как защититься
Точно так же, как и с другими типами инъекций – экранировать все данные, которые используются в запросах. Вредоносные данные могут приходить на публичные эндпоинты и на приватные, через другие методы или прослойки. Любой запрос к базе данных должен быть экранирован, даже если он на прямую не использует публичные данные.

❗️Для тестирования подобных инъекций опубликовано две задачи на нашей площадке, на них можно потренироваться:
Слепая инъекция
Совсем слепая инъекция

👨‍💻 Не влезает все в один пост. Тут только теоретическая часть, следом выйдет второй пост со скриптами и описанием инструментов.

———
#⃣ #SQL #БазаЗнаний

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
7🔥5👍4
Слепая SQL-инъекция. Часть 2

Вторая часть, продолжение предыдущего поста «Слепая SQL-инъекция».

📖 Такие атаки, где нет явного ответа сервера о результате запроса, на БД сложно выполнять в «ручном режиме», нужно писать скрипты или пользоваться готовыми инструментами. Самый мощный инструмент для таких целей – sqlmap. Он умеет работать полностью автоматически, если передать нужный адрес уязвимости и правильный набор параметров. Для тестирования этого инструмента и подобных типов уязвимостей, опубликовано две задачки на нашей площадке. Первая уязвимость отвечает true/false, а вторая совсем ничего не подсказывает, и в добавок ограничена по ресурсам, чтобы потренироваться с настройками таймингов.

📌 Запуска sqlmap на примере второй задачи опишу, тут больше всего флагов:
sqlmap -u "http://31.207.77.216:4005/check?number=1" --technique=T --time-sec=2 --delay=1 --timeout=3 --dbms=SQLite --hex --level=5 --risk=3 -T flag -C flag --dump -v 6


-u "http://31.207.77.216:4005/check?number=1" – адрес цели
--technique=T – указываем тип техники для атаки Time-based
--time-sec=2 – время задержки для проверки условий
--delay=1 – интервалы задержек между запросами
--timeout=3 – ожидание ответа сервера
--dbms=SQLite – тип субд
--hex – кодирование данных
--level=5 – уровень тестирования(от 1 до 5, 5 максимальный набор)
--risk=3 – уровень риска(1-3, на сколько заметный пейлоад в запросе)
-T flag -C flag – имена целевой таблицы и колонки
--dump – получение данных
-v 6 – уровень дебага при работе программы

И это только часть флагов, которые доступны в утилите sqlmap. Инструмент умеет автоматически определять СУБД, извлекать имена БД, имена и структуры таблиц, а так же собирать и выкачивать полный дамп базы данных.

📌 Инструменты – это хорошо, но в самых не стандартных ситуациях спасают только самодельные скрипты. К тому же написание скриптов это еще и гимнастика для мозгов, а самое главное – способ детальнее погрузиться в тему и разобраться в вопросе более детально. Хорошо пользоваться готовым инструментов, когда понимаешь как он устроен, действовать в нестандартных ситуациях будет проще и понятнее. Поэтому для примеров решения задачек прикладываю еще два скрипта, которые помогут более детально разобраться в способах применения слепой инъекции.

📌 Скрипты нахождения флагов из тестовых задачек на тему поста:
Первый скрипт – простой брутфорс флага из таблицы по одному символу: скрипт.
Второй скрипт, который использует бинарный поиск для оптимизации и ускорения: скрипт.

❗️ Потренировать можно тут:
Слепая инъекция
Совсем слепая инъекция

———
#⃣ #SQL #БазаЗнаний

🐞 Канал // Чат // Задачи
Please open Telegram to view this post
VIEW IN TELEGRAM
1🔥7👍442