2. Опасности: перегрузка пула соединений
Опасности: Перегрузка пула соединений
Пул соединений (Connection Pool) в Go — это ограниченный ресурс открытых каналов к базе данных (sql.DB). Когда вы выполняете запрос, Go берет один канал из пула, а по завершении возвращает его обратно. Time-based атака опасна тем, что она легко превращается в атаку типа DoS (Denial of Service), парализуя работу всего приложения.
В Go connection pooling реализован прямо в стандартной библиотеке database/sql — это одна из её сильных сторон. Когда вы вызываете sql.Open("postgres", connString), Go не открывает физическое соединение немедленно, а создаёт объект-обёртку *sql.DB, который управляет пулом. Первый запрос триггерит открытие первого реального TCP-соединения к СУБД, последующие — переиспользуют существующие. Параметры пула задаются через SetMaxOpenConns (максимум активных соединений), SetMaxIdleConns (максимум idle соединений), SetConnMaxLifetime (как долго соединение живёт перед принудительным перезаключением). По умолчанию MaxOpenConns = 0 (unlimited), что в production почти всегда означает «жди OOM в peak hours».
Аналогия: представь оператора колл-центра с 20 операторами. Каждый оператор может обслуживать одного клиента одновременно. Если все 20 заняты на длинных разговорах, новые клиенты слышат «ваш звонок очень важен для нас, оставайтесь на линии». Атакующий, который намеренно занимает всех операторов длинными разговорами (например, спрашивая сложные вопросы, заставляющие искать в документации по 30 минут) — фактически парализует работу колл-центра без необходимости его взлома. Time-based SQLi работает так же: атакующий заставляет каждое соединение в db-pool ждать pg_sleep(30), и легитимные пользователи остаются за пределами очереди.
Разберем, как всего несколько «спящих» запросов могут полностью остановить ваш сайт.
1. Механика: "Захват всех ресурсов"
Представьте, что в настройках вашего Go-сервера указан лимит в 20 соединений к базе (db.SetMaxOpenConns(20)). Это разумное ограничение для экономии ресурсов.
Хакер присылает 21 запрос с нагрузкой ?id=1 AND pg_sleep(30) одновременно.
Что произойдет внутри сервера:
- Первые 20 запросов займут все доступные слоты в пуле.
- Эти соединения будут «спать» по 30 секунд, ничего не делая, но удерживая ресурсы.
- Любой следующий запрос — от хакера или от обычного пользователя — встанет в очередь и будет ждать освобождения места.
- Результат: Сайт перестает отвечать всем легитимным пользователям.
В production-окружении эффект усиливается каскадом: пока 20 соединений «спят», health-check от Kubernetes liveness probe тоже встаёт в очередь, не получает ответа, считает pod недоступным и инициирует рестарт. После рестарта pod регистрируется в Service Mesh, получает trafic, и сразу же все 20 connection slots снова забиваются злоумышленными запросами — pod снова не отвечает на health-check, новый рестарт. Crash loop, deployment в состоянии «Not Ready», alerting в PagerDuty, эскалация инцидента на дежурного SRE.
2. Сравнение: UNION SQLi vs Time-based (DoS)
| Параметр | UNION SQLi | Time-based SQLi |
|---|---|---|
| Ресурс | Нагрузка на трафик | Нагрузка на время и соединения |
| Цель хакера | Кража данных | Кража данных + Паралич системы |
| Последствия | Работает медленнее | Полный отказ (DoS) |
3. Мониторинг из Go-кода
Аномалии в работе пула можно отследить через стандартную статистику db.Stats():
WaitCount: Если это число растет — пользователи стоят в очереди за данными.OpenConnections: Больше не увеличивается, так как лимит исчерпан.
Если в этот момент мониторинг показывает резкий рост ошибок 504 Gateway Timeout, значит, пул соединений забит «зависшими» процессами.
Эти метрики легко экспортировать в Prometheus через prometheus/client_golang: создайте prometheus.GaugeFunc для каждого поля db.Stats() и регулярно (раз в 15 секунд) обновляйте значения. В Grafana дашборде получите наглядную картину состояния пула — рост WaitCount или MaxOpenConnections равных лимиту в реальном времени.
Дополнительные сигналы для алертов: db.Stats().WaitDuration (суммарное время ожидания соединения за период) растёт быстрее, чем обычно — это явный признак насыщения пула. Алерт WaitDuration > 10s/min укажет на проблему до того, как пользователи начнут видеть 504 Gateway Timeout.
4. Побочный эффект или злой умысел?
Иногда DoS-атака — это лишь случайное следствие попытки быстро выгрузить данные. Хакер запускает атаку в 100 потоков, чтобы сократить время ожидания, и сам того не желая, обрушивает целевой сервер. Однако в руках опытного злоумышленника это становится мощным инструментом давления.
Реальный кейс из 2018 года: исследователь безопасности случайно положил публичный API одного крупного финтех-стартапа на 6 часов, запустив sqlmap с --threads 50 против найденной им SQLi-уязвимости. Цель была эксфильтрация данных, но эффект — массовая недоступность сервиса. Стартап потерял миллионы в комиссиях за платежи, репутацию, и в итоге заплатил bug bounty с условием полного раскрытия инцидента. История попала в OWASP CRS (Core Rule Set) как пример того, почему даже «безобидное» исследование SQLi требует rate limiting со стороны атакующего.
Для защиты на стороне приложения: установите db.SetConnMaxLifetime(5 * time.Minute) и db.SetMaxOpenConns(N) где N меньше, чем max_connections СУБД минус соединения других сервисов; добавьте statement_timeout в connection string (statement_timeout=5000 миллисекунд) — любой долгий запрос будет принудительно прерван на стороне сервера, освобождая соединение даже при pg_sleep(60) атаке.
Time-based SQLi угрожает не только конфиденциальности данных, но и доступности всей системы. Одно неосторожное соединение может стать причиной падения всего сервера.
Соответствие стандартам: CWE-400 «Uncontrolled Resource Consumption» — этот CWE относится к классу уязвимостей resource exhaustion и часто упоминается в bug bounty отчётах вместе с CWE-89 SQL Injection. OWASP API Security Top 10 2023 — API4:2023 Unrestricted Resource Consumption. ISO 27001 Annex A.12.1.3 «Capacity management» требует мониторинга и управления ресурсами для предотвращения отказа в обслуживании.
Теперь изучим, как хакеры используют логические условия, чтобы заставлять базу «спать» только тогда, когда им это нужно для получения информации.
Продолжить чтение
Что бы прочитать модуль полностью, зарегистрируйтесь/войдите на платформу
Когда закончишь — отметь раздел, чтобы продолжить.