
SQL-ін'єкція без міфів: як знайти вразливість у проєкті та закрити її
Практичний гід з SQL-ін'єкцій: як працює атака, де шукати її в коді, які мови та фреймворки мають небезпечні точки, як тестувати проєкт і які інструменти та бібліотеки допомагають захиститися.
SQL-ін'єкція виникає, коли неперевірене введення користувача вбудовується в SQL-команду так, що база даних сприймає частину даних як інструкцію. Основний захист — параметризовані запити або безпечні prepared statements, доповнені allow-list валідацією, найменшими привілеями для DB-користувача та автоматичними тестами.
Головна ідея: розділіть дані та інструкції
Уявімо пошук товарів. Програма хоче виконати SELECT * FROM products WHERE name = ?. Якщо значення передається як параметр, база отримує окремо шаблон запиту й окремо значення. Якщо ж розробник склеює рядок вручну, введення може змінити структуру WHERE, додати іншу умову або завершити команду.
Як одна й та сама функція стає вразливою
Користувач контролює значення
Це може бути не лише form field: query string, JSON, cookie, заголовок, імпорт CSV, webhook, параметр сортування або значення з черги. Якщо джерело можна змінити ззовні, вважайте його untrusted.
Значення склеюють із SQL
Проблема з'являється в query = '... ' + input + ' ...', template string, raw query builder або ORM-методі, який приймає готовий SQL. Валідація формату сама по собі не замінює параметризацію.
DB engine бачить не дані, а синтаксис
Спеціальні символи та фрагменти SQL можуть змінити умову, об'єднати результати, вплинути на час відповіді або перетворити read-операцію на write-операцію — залежно від драйвера й прав.
Помилка виходить за межі одного endpoint
Якщо один connection user має надмірні права, компрометація endpoint може зачепити таблиці, звіти, персональні дані, резервні копії або інші tenant-и.
Ментальна модель
Питайте не «чи є тут ORM?», а «чи може зовнішнє значення вплинути на структуру SQL і з якими правами виконується цей запит?».
SQLi не зникла: що говорять дані
Цифри нижче потрібно читати чесно. OWASP рахує широку категорію Injection, куди входять SQLi, XSS та інші CWE, а MITRE оцінює слабкість CWE-89 за даними CVE. Це не відсоток усіх атак у світі й не порівняння мов між собою.
274 228
загальних occurrences для категорії Injection у наборі даних OWASP; середня incidence rate — 3,37%. [2]
№2
SQL Injection посіла друге місце у MITRE CWE Top 25 за 2025 рік, score — 28,72. [3]
94%
застосунків у дослідженні OWASP тестувалися на певну форму injection. [2]
Що це означає для команди
SQLi — зріла, добре описана проблема, але її зрілість не робить legacy-код безпечним. Висока відтворюваність — перевага для захисту: патерни легко перевіряти статичним аналізом і тестами.
Де шукати SQL-ін'єкцію у вже існуючому проєкті
Почніть із data-flow, а не зі списку фреймворків: знайдіть джерело зовнішнього значення, шлях до data-access layer і фактичний виклик драйвера.
Пошук небезпечних sink-ів
Знайдіть query, execute, raw, text, literal, sequelize.query, knex.raw, jdbc.Statement, SqlCommand та еквіваленти. Перевірте, чи отримують вони склеєний рядок.
Перевірка dynamic SQL
Особливо уважно перегляньте ORDER BY, назви колонок/таблиць, report builder і фільтри. Для них часто не можна використати bind variable — застосуйте allow-list mapping із фіксованих значень. [1]
ORM escape hatches
Перегляньте raw SQL, unsafe operators, custom scopes, string-based filters і міграції. ORM знижує ризик лише там, де команда не обходить його параметризований API.
Не забудьте неочевидні entry points
Адмінка, імпорт, cron, інтеграції, GraphQL resolvers, search endpoints і внутрішні API теж приймають дані, які можуть бути скомпрометовані раніше в ланцюжку.
Перевірте error handling
SQL stack trace, назва таблиці, тип драйвера або різні помилки для різних умов полегшують діагностику атаки. Зовні повертайте безпечну помилку, деталі залишайте в correlation-id логах.
Які мови й фреймворки частіше потрапляють у зону ризику
Немає чесного рейтингу «найнебезпечнішої мови»: SQLi можлива майже всюди, де є SQL і небезпечний API. Різниця — у тому, наскільки легко випадково перейти з параметризованого шляху на raw SQL.
| Екосистема | Типова точка ризику | Безпечний default | Що перевірити |
|---|---|---|---|
| PHP: Laravel / Symfony | DB::raw, query string, старий mysqli/PDO-код | Query Builder/Eloquent bindings, PDO prepared statements | raw(), bind parameters, legacy controllers |
| JavaScript / TypeScript: Node.js | pg/mysql2 query з template string, knex.raw, Sequelize raw | driver placeholders, Prisma parameters, query builder | $queryRawUnsafe, raw(), dynamic filters |
| Python: Django / SQLAlchemy | cursor.execute з конкатенацією, text() із рядком | Django QuerySet, SQLAlchemy bound parameters | extra(), raw(), literal SQL, f-strings |
| Java: Spring / Hibernate | Statement, String concatenation, HQL з input | PreparedStatement, JPA parameters, named parameters | createNativeQuery, HQL interpolation |
| .NET: ASP.NET / EF Core | FromSqlRaw або SqlCommand із рядком | LINQ, FromSqlInterpolated, параметри | Raw methods, interpolated SQL, DB role |
| Go / Rust | database/sql або client API з побудовою рядка | QueryContext/Execute з placeholders, sqlx bind | fmt.Sprintf, format!, dynamic identifiers |
Висновок по стеку
Найбільший ризик мають не мови, а команди, які змішують user input із query text, дозволяють raw SQL без review або дають застосунку роль DBA.
Антипатерн і безпечний варіант
Небезпечно: рядок знає забагато
const sql = `SELECT * FROM users WHERE email = '${email}'`;
const result = await db.query(sql);Тут email стає частиною SQL-тексту. Навіть якщо зараз це «лише пошук», той самий патерн легко копіюють у login, export або admin-функцію.
Безпечніше: драйвер відділяє значення
const result = await db.query(
"SELECT * FROM users WHERE email = $1",
[email],
);Текст запиту фіксований, а email переданий як параметр. Формат placeholder відрізняється між драйверами, тому використовуйте документацію саме свого client library.
Для sort field — не bind, а mapping
const columns = { newest: "created_at", price: "price" } as const;
const column = columns[input] ?? columns.newest;
const sql = `SELECT * FROM products ORDER BY ${column}`;Назва колонки не є звичайним значенням для bind parameter. Дозволяйте тільки ключі з allow-list і ніколи не підставляйте raw input напряму.
Чим перевіряти: від IDE до production signals
Жоден інструмент не бачить усю картину. Найкращий результат дає комбінація аналізу коду, тесту endpoint-ів і спостереження за runtime.
CodeQL
SAST для пошуку data-flow від user input до SQL sink у JavaScript/TypeScript, Python, Java, C# та інших підтриманих мовах. Додайте в CI й переглядайте findings разом із кодовласником.
Semgrep
Швидкі pattern/data-flow правила для локальної перевірки й pre-commit. Добре підходить, щоб заборонити конкретні небезпечні виклики на кшталт raw query без параметрів.
Burp Suite / OWASP ZAP
DAST для staging: проксі, повторення запитів, активне й пасивне сканування. Запускайте лише в межах дозволеного scope і не вмикайте руйнівні checks на production.
PortSwigger Web Security Academy
Безпечне навчальне середовище з SQLi labs: error-based, blind, UNION та інші сценарії. Корисно для команди, щоб побачити атаку руками без ризику для клієнтських даних.
Sentry / DB audit logs / WAF
Сигнали експлуатації: сплески 4xx/5xx, незвичні latency, помилки парсингу SQL, масові SELECT, дивні user-agent і доступ до нетипових таблиць. WAF — шар зниження ризику, не patch коду.
Базовий стандарт, який радять експерти
OWASP радить не покладатися на один фільтр. Зберіть defense-in-depth baseline і зафіксуйте його в Definition of Done.
Параметризовані запити або безпечні stored procedures; dynamic SQL — лише з allow-list і окремим review. [1]
Не використовуйте escaping як основний захист: OWASP прямо вважає його крихким і радить лише як крайній варіант для legacy. [1]
Окремий DB-користувач для застосунку, мінімальні SELECT/INSERT/UPDATE права, без DBA/owner ролі; для read-only flow — read-only account або view. [1]
Секрети й production data не мають потрапляти в логи. Логуйте подію, endpoint, actor, request-id і policy result, але не повний SQL із персональними параметрами.
Додайте negative tests: quote у звичайному імені, довгий рядок, unicode, неправильний тип, невідомий sort key, cross-tenant ID і boundary values.
Тримайте driver, ORM і database engine оновленими, але не вважайте patch dependency заміною secure query construction.
Що робити після виявлення підозрілої SQLi
0–1 година
Зупинити розширення шкоди
Зафіксуйте endpoint, час, request-id і scope. За потреби тимчасово вимкніть функцію, обмежте route або поверніть DB role до read-only. Не видаляйте логи й не запускайте експерименти на production.
Того ж дня
Закрити root cause
Замініть string-built SQL на parameterized API, додайте allow-list для identifiers, перевірте права DB user і створіть regression test, який падає на старій реалізації.
Після patch
Перевірити blast radius
Проаналізуйте DB audit logs, незвичні exports, зміни даних, service accounts і доступ до інших tenant-ів. Зафіксуйте, які дані могли бути прочитані або змінені.
До закриття інциденту
Покращити процес
Додайте SAST/DAST у CI, правило review для raw queries, least-privilege migration і короткий playbook для команди. Інакше наступна SQLi з'явиться в іншому endpoint.
Часті запитання
ORM зазвичай параметризує стандартні запити, але не захищає автоматично raw SQL, небезпечні оператори, dynamic identifiers або кастомний query builder. Перевіряйте escape hatches і правила команди.
Це корисна валідація формату, але не універсальний захист. Основним захистом для значень мають бути параметризовані запити; для імен таблиць, колонок і сортування — фіксований allow-list mapping.
Так. GraphQL змінює форму API, але resolver усе одно може передавати аргумент у небезпечний SQL, filter або raw query. Тестуйте data-flow від resolver arguments до DB client.
WAF може блокувати відомі патерни й знизити ймовірність експлуатації, але не виправляє query construction і не гарантує покриття обфускацій та business-specific шляхів. Patch, least privilege і тести все одно потрібні.
Використовуйте локальний sandbox, OWASP Juice Shop, WebGoat або PortSwigger Academy. Не тестуйте сторонній сайт, API клієнта чи production без чітко погодженого scope і правил тесту.
Джерела та лабораторії
Хочете знати, де саме ризик у вашому проєкті?
PAS7 Studio може провести code review і security audit для API, адмін-панелі та data-access layer: знайти raw SQL, перевірити tenant boundaries, налаштувати SAST/DAST у CI, зменшити DB-права й підготувати план виправлень із пріоритетами.
Пов'язані статті

Скільки коштує розробка AI асистента у 2026: RAG чатбот, база знань, CRM, Telegram та підтримка
Практичний гід для бізнесу: від чого залежить ціна розробки AI асистента у 2026 році, що входить у RAG чатбот, інтеграції з CRM, Telegram, guardrails, оцінювання, моніторинг і супровід.

AI для розробки лендінгів: де він реально прискорює запуск, а де псує конверсію
Дослідження про використання AI у розробці лендінгів: v0, Webflow AI, Builder.io, Framer-подібні AI builders, генерація UX, copy, SEO, персоналізація, A/B тести, ризики шаблонності, безпеки, доступності та технічного боргу.

AI SEO / GEO у 2026: ваші наступні клієнти — не люди, а агенти
Пошук зміщується від кліків до відповідей. Боти та AI-агенти сканують, цитують, рекомендують і дедалі частіше купують. Дізнайтесь, що таке AI SEO / GEO, чому класичного SEO вже недостатньо, і як PAS7 Studio допомагає брендам перемагати у «агентному» вебі.

Найпотужніший чіп від Apple? M5 Pro і M5 Max б'ють рекорди
Аналітичний розбір Apple M5 Pro і M5 Max станом на березень 2026 року. Пояснюємо, чому ці чіпи можна вважати найпотужнішими професійними ноутбучними SoC від Apple, як вони виглядають на тлі M4 Pro, M4 Max, M1 Pro, M1 Max і що показують у порівнянні з актуальними Intel та AMD.
Професійна розробка для вашого бізнесу
Створюємо сучасні веб-рішення та боти для бізнесу. Дізнайтеся, як ми можемо допомогти вам досягти цілей.