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. Он спрашивает:
... ASCII(SUB(p,1,1)) > 64? → YES (Буква больше чем '@')... ASCII(SUB(p,1,1)) > 100? → NO (Буква меньше чем 'd')... ASCII(SUB(p,1,1)) > 80? → YES (Буква между 'd' и 'P')... 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, сортировкой и фильтрами.
Продолжить чтение
Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу
Когда закончишь — отметь раздел, чтобы продолжить.