SQL Injection — тип атаки на базу даних додатку, при якій зловмисник впроваджує шкідливий SQL-код у параметри запиту, отримуючи несанкціонований доступ до даних або можливість їх модифікації. За даними OWASP (2025), SQL Injection залишається однією з критичних вразливостей, здатної призвести до повної компрометації бази даних. Ін'єкція SQL-коду дозволяє зловмиснику читати, змінювати та видаляти записи, а в деяких випадках — отримати доступ до операційної системи сервера.
Головне
SQL Injection — це вразливість, що виникає, коли додаток формує SQL-запит шляхом конкатенації рядків з користувацькими даними. Зловмисник передає в параметр запиту спеціально сконструйований рядок, який змінює структуру SQL-команди. Замість того щоб бути даними, введений рядок стає частиною SQL-коду, що дозволяє атакуючому виконувати довільні запити до бази даних. За даними Verizon Data Breach Report (2025), SQL Injection присутній у 8% усіх досліджених витоків даних.
Незважаючи на відомість вразливості (перші згадки — кінець 1990-х), SQL Injection зустрічається в сучасних додатках. Причина — людський фактор: розробники допускають конкатенацію рядків у коді, legacy-код не рефакториться, а ORM-фреймворки використовуються неправильно (наприклад, raw-запити з підстановкою значень через рядки). За даними Veracode (2026), близько 14% усіх просканованих додатків містять хоча б одну SQLi-вразливість.
Успішна SQL Injection дає атакуючому широкий спектр можливостей: читання будь-яких таблиць бази даних, включаючи хеші паролів та персональні дані користувачів; модифікація та видалення записів; виконання адміністративних операцій (DROP TABLE, TRUNCATE); у деяких конфігураціях — віддалене виконання команд через xp_cmdshell (MSSQL) або INTO OUTFILE (MySQL). Наслідки — від витоку користувацьких даних до повної втрати контролю над системою.
SQL-ін'єкції класифікуються за способом вилучення даних з бази даних. Вибір методу залежить від того, як додаток обробляє результати запиту та повідомлення про помилки. Класифікація OWASP виділяє три основні типи: In-band (дані вилучаються через той самий канал), Inferential/Blind (логічні висновки) та Out-of-band (дані передаються через інший канал).
| Тип | Спосіб вилучення даних | Складність | Частота |
|---|---|---|---|
| In-band (класичний) | Безпосередньо через результат запиту | Низька | Висока |
| Blind SQLi | Логічні висновки за відповідями сервера | Висока | Середня |
| Out-of-band | Через зовнішній канал (DNS, HTTP) | Середня | Низька |
Найпоширеніший тип. Зловмисник впроваджує SQL-код у параметр запиту, і результат впровадження видно безпосередньо у відповіді сервера. Два підвиди: Error-based (через повідомлення про помилки БД) та UNION-based (через оператор UNION SELECT). Error-based використовує інформацію з повідомлень про помилки, наприклад, синтаксичну помилку MySQL, яка може розкрити ім'я таблиці або структуру запиту. UNION-based дозволяє об'єднати результати легітимного запиту з даними з інших таблиць бази даних.
Використовується, коли додаток не відображає результати запиту або повідомлення про помилки. Зловмисник задає питання типу «так/ні», відправляючи запити з логічними умовами та аналізуючи різницю у відповіді сервера (наприклад, за часом відгуку або вмістом сторінки). Time-based Blind SQLi використовує функцію затримки (SLEEP, WAITFOR DELAY) для підтвердження умов — якщо сторінка завантажується довше, умова істинна. Цей метод дуже повільний — вилучення одного запису може зайняти години.
# Приклад Blind SQL Injection (time-based)
# Якщо SQLi вразливий, SLEEP(2) виконується за умови
import requests
import time
payload = "' OR IF(SUBSTRING((SELECT password FROM users LIMIT 1),1,1)='a',SLEEP(2),0) -- "
url = "http://example.com/user?id=1" + payload
start = time.time()
response = requests.get(url)
elapsed = time.time() - start
# Якщо відповідь прийшла через >2 секунди — перша літера пароля 'a'
print(f"Time: {elapsed:.2f}s - First letter is 'a'" if elapsed > 2 else "First letter is not 'a'")
Дані передаються не через HTTP-відповідь, а через альтернативні канали: DNS-запити, HTTP-запити на зовнішній сервер, SMTP. Використовується, коли додаток не повертає результати запиту та не показує помилок. MySQL підтримує функцію LOAD_FILE(), яка може ініціювати DNS-запит, а MSSQL — xp_dirtree для відправки даних на віддалений SMB-сервер. Out-of-band SQL Injection ефективний, але вимагає додаткових налаштувань на сервері зловмисника та наявності певних функцій БД.
Механізм SQL Injection заснований на тому, що SQL використовує лапки для рядкових літералів. Якщо додаток підставляє користувацьке введення безпосередньо в SQL-запит без екранування, зловмисник може «закрити» рядок і додати довільний SQL-код. Наприклад, у запиті SELECT * FROM users WHERE name = '$input' підстановка ' OR '1'='1 перетворює запит на SELECT * FROM users WHERE name = '' OR '1'='1', що повертає всіх користувачів.
Розглянемо форму логіну із запитом SELECT * FROM users WHERE username = '$user' AND password = '$pass'. Якщо зловмисник вводить у поле username admin' -- , а пароль залишає порожнім, результуючий запит стає SELECT * FROM users WHERE username = 'admin' -- ' AND password = ''. Символи -- коментують решту запиту, і умова перевірки пароля відключається. Сервер повертає запис користувача admin, і зловмисник входить у систему без знання пароля.
# Приклад SQL Injection — обхід автентифікації
# ВРАЗЛИВИЙ КОД: пряма конкатенація рядків
def login_vulnerable(username, password):
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
return cursor.fetchone() is not None
# username = "admin' --" обнуляє перевірку пароля
# БЕЗПЕЧНА ВЕРСІЯ: параметризований запит
def login_secure(username, password):
query = "SELECT * FROM users WHERE username = %s AND password = %s"
cursor.execute(query, (username, password))
return cursor.fetchone() is not None
Оператор UNION SELECT дозволяє об'єднати результати двох SELECT-запитів. Якщо зловмисник знаходить вразливий параметр, він додає UNION SELECT із запитом з іншої таблиці. Наприклад: ' UNION SELECT username, password FROM admins --. Умова успіху — кількість колонок в обох запитах має збігатися. Кількість колонок визначається через ORDER BY (підстановка ' ORDER BY 1--, потім 2, 3... до помилки). Знаючи кількість колонок, зловмисник підставляє UNION SELECT з такою ж кількістю полів.
Мобільні додатки стикаються з SQL Injection як на серверній стороні (API), так і на клієнті — у локальних базах даних (SQLite, Realm). Хоча ризик серверної SQLi в мобільному API аналогічний веб-додаткам, локальна БД створює додатковий вектор. Якщо додаток зберігає дані в SQLite та виконує запити з конкатенацією рядків, шкідливі дані, що потрапили в локальну БД через API, можуть викликати SQLi при подальшій обробці.
Локальна SQLite-база на пристрої також вразлива до SQL Injection, якщо додаток формує запити конкатенацією рядків. Content Providers на Android та Core Data на iOS використовують параметризацію за замовчуванням, але raw-запити вимагають уваги розробника. SQLite не підтримує множинні запити через крапку з комою, що обмежує можливості зловмисника, але не захищає від читання даних через WHERE-умови. Завжди використовуйте selectionArgs в Android та NSPredicate з параметрами в iOS.
API, до якого звертається мобільний додаток, вразливий так само, як будь-який веб-сервер. Мобільні розробники часто вважають, що SQLi — проблема тільки бекенду, але вразливість виникає на рівні API-ендпоінта, який приймає параметри від клієнта. Розділення відповідальності не захищає: якщо бекенд-розробник забув параметризувати запит, мобільний додаток користувача стає вектором атаки. Вимагайте від бекенду використання ORM або prepared statements.
// Приклад SQL Injection в локальній SQLite на Android
// ВРАЗЛИВИЙ КОД: пряма конкатенація
fun getUserVulnerable(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = $userId", null
)
}
// БЕЗПЕЧНА ВЕРСІЯ: параметризація через selectionArgs
fun getUserSecure(db: SQLiteDatabase, userId: String): Cursor {
return db.rawQuery(
"SELECT * FROM users WHERE id = ?",
arrayOf(userId)
)
}
Захист від SQL Injection будується на простому принципі: ніколи не довіряйте користувацькому введенню в SQL-запитах. Єдиний надійний метод — параметризація запитів (prepared statements), при якій SQL-код та дані передаються окремо. Всі інші методи — екранування, валідація, WAF — є додатковими шарами захисту, але не замінюють параметризацію. За даними OWASP (2025), параметризація запобігає 100% SQL Injection-атак.
При використанні prepared statements SQL-запит спочатку компілюється сервером БД без даних, а потім значення параметрів передаються окремо. База даних розглядає параметри як дані, а не як виконуваний код. Навіть якщо зловмисник передасть ' OR '1'='1, БД інтерпретує це як рядкове значення, а не як SQL-код. Prepared statements підтримуються всіма сучасними мовами та фреймворками: PDO в PHP, PreparedStatement в Java, cursor.execute в Python.
Сучасні ORM (Hibernate, Entity Framework, SQLAlchemy, Room) автоматично використовують параметризацію при виконанні запитів, якщо розробник не переходить на raw-запити. Однак ORM не захищають повністю: конструкції на кшталт @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) в JPA вимагають передачі параметрів через named parameters, а не конкатенації. Query Builders (Knex, jOOQ) також параметризують запити за замовчуванням, якщо не використовувати raw-методи.
Екранування спецсимволів (mysql_real_escape_string) — застарілий метод, який не захищає від усіх типів SQL Injection. Проблема: екранування залежить від кодування та може бути обійдене при використанні багатобайтових кодувань (наприклад, GBK в азійських системах). Використовуйте екранування тільки в legacy-коді, де параметризація неможлива, і завжди в комбінації з суворою валідацією типу вхідних даних.
| Метод | Ефективність | Рекомендація |
|---|---|---|
| Prepared Statements | 100% | Обов'язково для всіх запитів |
| ORM (правильне використання) | 99% | Рекомендується |
| Екранування рядків | 70% (залежить від кодування) | Тільки legacy |
| Валідація введення (white-list) | 50% (тільки для чисел) | Додатково |
| WAF (Web Application Firewall) | 60% | Додатково |
Обліковий запис додатку повинен мати мінімально необхідні права: SELECT, INSERT, UPDATE, DELETE — тільки на ті таблиці, які реально потрібні додатку. Забороніть використання DROP, TRUNCATE, CREATE для прикладного облікового запису. Це обмежить збитки навіть у разі успішної SQL Injection: зловмисник не зможе видалити таблиці або виконати адміністративні операції.
Регулярне тестування на SQL Injection має бути частиною пайплайну безпечної розробки. Комбінація статичного аналізу, динамічного сканування та ручного пентесту дає найкращі результати. За даними Synopsys Cybersecurity Report (2025), автоматичні сканери знаходять до 70% SQLi-вразливостей, однак складні Blind-атаки вимагають ручного тестування.
Для мобільних додатків важливий і аналіз локального SQLite: перевірте всі rawQuery-виклики, ContentProvider-запити та Room-запити з rawQuery. Інструменти: Android Studio Lint (детектує SQLi в SQLite), MobSF (Mobile Security Framework) для автоматичного статичного та динамічного аналізу APK/IPA. Рекомендується також тестувати API-ендпоінти через sqlmap з проксі-перехопленням трафіку мобільного додатку.
Часті запитання
SQL Injection атакує реляційні бази даних через SQL-запити. NoSQL Injection впливає на нереляційні БД (MongoDB, Couchbase) через їх оператори запитів ($gte, $ne, $where). У MongoDB ін'єкція можлива, якщо додаток формує BSON-документ з рядка JSON. Механізми захисту аналогічні: параметризація та валідація типів.
Знайдіть всі місця, де SQL-запити формуються через конкатенацію рядків з користувацькими даними. Шукайте патерни виду "SELECT ... WHERE id = " + userId або f"UPDATE ... SET name = '{name}'". Кожен такий рядок — потенційна SQL-ін'єкція. Замініть всі на параметризовані запити або prepared statements.
ORM-фреймворки захищають автоматично, тільки якщо ви використовуєте їх методи Query Builder та named parameters. Якщо ви використовуєте raw-запити (nativeQuery в JPA, rawQuery в Room), захист не працює — необхідно передавати параметри через підготовлені вирази, а не через конкатенацію рядків.
Second-Order SQL Injection — атака, при якій шкідливі дані зберігаються в БД як безпечні, але потім використовуються в іншому запиті без екранування. Наприклад, зловмисник реєструється з username ' OR '1'='1. Дані зберігаються як рядок — при реєстрації атаки немає. Але якщо інший запит підставляє username в SQL без параметризації, ін'єкція спрацьовує.
NoSQL Injection потенційно небезпечніший через меншу обізнаність розробників. Розробники знають про SQL Injection і більшість використовують ORM, але про NoSQL Injection знають небагато. У MongoDB неправильно сформований запит може повернути всі документи колекції. Захист — ті ж prepared statements (BSON-параметризація) та сувора валідація вхідних даних.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також