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

6. Защита: Whitelisting vs чёрные списки

Защита: Whitelisting vs Чёрные списки

Концепция белого списка (Whitelisting) проста: «Запрещено всё, что не разрешено явно». Для Go-разработчика это означает создание таблицы абсолютно безопасных значений, за пределы которой код не выйдет ни при каких обстоятельствах. Это фундаментально отличается от попыток «вычищать» опасные символы, которые почти всегда обречены на провал.

Эта дихотомия (allowlist vs denylist) — один из фундаментальных принципов компьютерной безопасности, описанный ещё в фундаментальных работах Зальцера и Шредера 1975 года под названием «fail-safe defaults». Принцип звучит так: по умолчанию доступ должен быть запрещён, и только явное разрешение даёт право на действие. Применительно к SQL: если динамическая часть запроса (имя колонки, направление сортировки, имя таблицы) принимается от пользователя, она должна сравниваться с заранее определённым списком допустимых значений и заменяться на безопасное значение по умолчанию при любом несоответствии.

Аналогия: представь систему контроля доступа в дата-центр. Один подход — «список запрещённых лиц»: охранник проверяет каждого входящего по базе данных и пускает всех, кого там нет. Если злоумышленник не зарегистрирован — он пройдёт. Второй подход — «список разрешённых лиц»: охранник пускает только тех, у кого есть в его базе именная пропуск-карта. Если человек не в списке — отказ. Второй подход непробиваем по дизайну: даже если злоумышленник украл униформу или подделал документы, его имя не появится в списке allowed. Whitelisting в SQL работает по той же логике.

Изучим, как реализовать надежную проверку параметров в Go и почему мы никогда не должны полагаться на чёрные списки.


1. Механика: "Белые списки — это бетон"

Если ваше приложение принимает имя колонки для сортировки, ввод должен проходить через мапу разрешенных значений. Это касается всех мест, где параметр запроса представляет собой identifier (имя колонки, таблицы, схемы, направление ORDER BY) — ни Prepared Statements, ни bind-параметры не работают для identifiers. Параметризация защищает только VALUE-контекст (WHERE col = ?), а identifiers требуют whitelisting на уровне приложения.

// ✅ ПРАВИЛЬНЫЙ Go-код (Whitelisting)
allowedColumns := map[string]bool{
    "name":  true,
    "price": true,
    "date":  true,
}

sortBy := c.Query("sort")
// Если колонки нет в списке — используем значение по умолчанию!
if !allowedColumns[sortBy] {
    sortBy = "id"
}

// Теперь sortBy ГАРАНТИРОВАННО содержит только одно из разрешенных слов
query := fmt.Sprintf("SELECT * FROM items ORDER BY %s", sortBy)

Хакерская попытка внедрить price; DROP TABLE просто не пройдет проверку в allowedColumns, и приложение безопасно откатится к сортировке по id.

Разбор паттерна: ключевая идея — превратить пользовательский ввод в перечислимый тип. Из бесконечного пространства возможных строк (a, aa, a' OR 1=1--, price; DROP, ...) мы оставляем только три валидных значения: name, price, date. Любая инъекция автоматически попадает в категорию «не в списке» и заменяется на безопасный дефолт. Никакая обфускация не поможет атакующему, потому что мы не пытаемся «отличить хорошее от плохого» — мы пропускаем только заранее известные хорошие значения.

В Go-экосистеме это часто оформляется как enum: type SortColumn int с константами SortByName, SortByPrice, SortByDate, и функция-mapper из строки в enum с явным fallback. Преимущество: type system Go ловит ошибки на этапе компиляции, а не runtime.


2. Чёрные списки (Blacklisting) — ловушка для новичков

Попытки удалять слова SELECT или UNION из ввода бесполезны:

  1. Хакер может изменить регистр: SeLeCt.
  2. Использовать кодировки: %27 вместо '.
  3. Применить обфускацию, которую вы не предусмотрели.

Безопасность — это не когда вы пытаетесь поймать всё плохое, а когда вы разрешаете только хорошее.

Конкретные примеры провалов blacklist-фильтров из практики:

  • Фильтр strings.ToLower(input) + проверка на select — обходится через символы Cyrillic «с» (U+0441) и «е» (U+0435), визуально неотличимые от ASCII, но проходящие проверку. Если СУБД настроена с UTF-8 collation и case-insensitive matching, парсер может их корректно понимать.
  • Регулярка regexp.MustCompile("OR\\s+1=1") — обходится через OR/**/1=1 (комментарий вместо пробела), OR%091=1 (URL-encoded таб), OR(1)=(1) (скобки вместо пробелов).
  • Удаление кавычек через strings.ReplaceAll(s, "'", "") — обходится через 0x61646d696e (hex-литерал, не требующий кавычек) или Unicode «smart quote» U+2018.

Каждый новый bypass требует обновления blacklist, и эта гонка проигрывается defender'ом — атакующий всегда на шаг впереди.


3. Таблица работы: Whitelist vs Blacklist

Характеристика Белый список (Whitelisting) Чёрный список (Blacklisting)
Безопасность 🟢 Непробиваемо 🔴 Легко обойти
Гибкость 🟡 Код требует обновления 🟢 Работает само
Вердикт Единственно верный путь Опасная иллюзия защиты

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

Используйте мапы (map[string]bool) для всех динамических элементов: имен таблиц, колонок или направлений сортировки. Это делает структуру ваших запросов жестко заданной и недоступной для манипуляций.

Дополнительные приёмы для production-кода: вынесите whitelist в отдельный пакет internal/sqlsafe, чтобы все валидации были в одном месте; покройте функцию-валидатор тестами с edge cases (пустая строка, длинная строка, Unicode); добавьте метрику sqlsafe_invalid_input_total{column="sort"} в Prometheus, чтобы видеть попытки атак в Grafana. Если значение приходит из enum в protobuf-схеме API, валидация делается на уровне gRPC-сервера автоматически — лучший вариант, потому что invalid значение даже не достигает бизнес-логики.


Безопасность начинается с жестких рамок. Не пытайтесь предугадать все хитрости хакера — просто не давайте ему возможности выйти за пределы разрешенного сценария.

Соответствие стандартам: OWASP ASVS v4 раздел 5.3.4 «Verify that the application protects against SQL Injection» рекомендует именно whitelisting для identifier-контекстов. CWE-1287 «Improper Validation of Specified Type of Input» — отдельная категория для случаев, когда тип значения проверяется недостаточно строго. NIST SP 800-53 SI-10 «Information Input Validation» как control objective. PCI-DSS 6.5.1 в комментариях явно упоминает «input validation» как защитную меру против инъекций.

Мы завершили изучение "слепых" Boolean инъекций. После квиза вас ждет практика, а затем мы перейдем к еще более скрытной технике — инъекциям на основе времени.

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

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

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

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