Перейти к содержимому
Назад к пути
Теория 3 мин чтения

4. Оптимизация: бинарный поиск

Оптимизация: Бинарный поиск SQLi

В мире алгоритмов бинарный поиск (Binary Search) — это способ быстро найти элемент в отсортированном массиве. Применительно к SQL-инъекциям этот метод позволяет радикально сократить количество запросов, необходимых для угадывания одного символа.

Для Go-разработчика парадокс в том, что вы сами знаете этот алгоритм наизусть — sort.Search, slices.BinarySearch. Но не задумываетесь, что атакующий применяет его поверх вашего же database/sql-кода: каждый его запрос — это один шаг mid := (lo + hi) / 2, а ответ сервера 200/404 — это сравнение if val < mid. Уязвимость становится оракулом, а оракул в связке с двоичным поиском превращается в data exfiltration на скорости log₂.

Разберем, как с помощью простых операторов «больше» и «меньше» хакер превращает долгий перебор в молниеносную атаку. Аналогия — игра «угадай число от 1 до 256»: ребёнок угадает за 8 вопросов «больше/меньше», и тот же принцип на уровне SQL вытаскивает один байт хэша пароля.


1. Механика: "Меньше или Больше"

Представьте запрос в Go-коде: SELECT id FROM users WHERE username = 'admin' AND password = '%s' (через fmt.Sprintf).

Это типовой паттерн «быстро прототипировать логин-эндпоинт» — fmt.Sprintf короче, чем db.QueryRow(q, args...), и в juniors-PR такая строка иногда даже проходит ревью. Атакующему этого достаточно: поле password уязвимо к blind SQLi, и хотя в ответе нет пароля, можно посимвольно вытащить любой секрет из любой таблицы.

Хакер не хочет проверять каждую букву от A до Z. Он спрашивает:

  1. ... ASCII(SUB(p,1,1)) > 64? → YES (Буква больше чем '@')
  2. ... ASCII(SUB(p,1,1)) > 100? → NO (Буква меньше чем 'd')
  3. ... ASCII(SUB(p,1,1)) > 80? → YES (Буква между 'd' и 'P')
  4. ... ASCII(SUB(p,1,1)) > 90? → NO (Буква между 'P' и 'Z')

Вместо последовательного перебора хакер каждый раз делит пополам диапазон возможных кодов ASCII. Под капотом: на каждом шаге функция SUBSTRING(password, position, 1) возвращает один символ, ASCII() превращает его в число 0-127, дальше сравнение. Go-сервер возвращает 200/404 (или разную длину body) — атакующий пишет 50 строк на requests и за 8 round-trip получает один символ. Все запросы выглядят как ?id=1 или ?username=admin — обычный пользовательский трафик, никаких подозрительных '/UNION в URL.


2. Математика: Почему 8 запросов?

Поскольку кодов ASCII 256, количество шагов для нахождения одного символа составляет всего log2(256) = 8. Для извлечения пароля из 10 символов понадобится всего 80 запросов, тогда как линейный перебор мог бы потребовать более полутора тысяч. На реальной задаче — bcrypt-хэш длиной 60 символов — это 480 запросов; при rate 50/sec — менее 10 секунд. Даже стандартный rate limiter Go-приложения (например, golang.org/x/time/rate с Limit(100) на IP) пропустит атаку, потому что 50 RPS — нормальная активность браузера при загрузке странички с десятком картинок.


3. Таблица работы: Линейный vs Бинарный поиск

Характеристика Линейный поиск (A, B, C...) Бинарный поиск (>100, <50...)
Запросов на символ До 256 Всегда 8
Скорость 🔴 Низкая 🟢 Очень высокая
Сложность 🟢 Легко 🟡 Нужен скрипт

4. Как это выглядит в Go (Индикаторы)

Сотни однотипных запросов со строго заданными интервалами, в которых мелькают ASCII, SUBSTRING и знаки сравнения — это классический признак работы автоматизированного инструмента взлома, использующего бинарный поиск. В логах PostgreSQL (log_statement = 'all') это будет лавина запросов вида WHERE id=1 AND ASCII(SUBSTRING(...,1,1))>X с шагом X каждые 100ms. Один такой шаблон в access-логе nginx + такой же шаблон в slow-query log Postgres = 100% сработавшая SQLi разведка.

Защита одна — параметризация всех плейсхолдеров. С db.QueryRow("SELECT id FROM users WHERE username=$1 AND password=$2", u, p) ввод хакера в p идёт как литеральная строка "' OR 1=1--" и сравнивается с хэшем bcrypt. Результат — false, аутентификация падает, оракул мёртв. Никакие ASCII, SUBSTRING, > не доходят до парсера SQL.


Бинарный поиск превращает слепую инъекцию в эффективный механизм выгрузки данных. Это OWASP A03:2021 (Injection) и CWE-89 — атака, известная с 1998 года, но всё ещё топ-3 по реальному ущербу. Heartland Payment Systems в 2008-м потеряла 134M кредитных карт через ровно такую blind-эксфильтрацию.

Однако в Go существуют ситуации, когда даже Prepared Statements не могут предотвратить инъекцию. Параметризация работает только для значений в WHERE/SET/VALUES, но не для идентификаторов (имена таблиц, колонок) и не для ключевых слов (ASC/DESC, LIMIT N). Изучим риски, связанные с динамическими API, сортировкой и фильтрами.

Продолжить чтение

Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу

Когда закончишь — отметь раздел, чтобы продолжить.

🚧 Сайт в разработке. Полный функционал пока недоступен. Все вопросы — support@hackandfix.ru