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 рамките се използват неправилно (напр. сурови заявки с заместване на стойности чрез низове). Според данни на 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 използват параметризация по подразбиране, но суровите заявки изискват внимание от разработчика. SQLite не поддържа множество заявки чрез точка и запетая, което ограничава възможностите на нападателя, но не предпазва от четене на данни чрез WHERE условия. Винаги използвайте selectionArgs в Android и NSPredicate с параметри в iOS.
API-то, към което се свързва мобилното приложение, е уязвимо колкото всеки уеб сървър. Мобилните разработчици често смятат, че SQLi е само проблем на backend-а, но уязвимостта възниква на ниво API endpoint, който приема параметри от клиента. Разделянето на отговорностите не защитава: ако backend разработчикът е забравил да параметризира заявката, мобилното приложение на потребителя се превръща във вектор за атака. Изисквайте от backend-а използване на 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) автоматично използват параметризация при изпълнение на заявки, освен ако разработчикът не премине на сурови заявки. Въпреки това, ORM не защитава напълно: конструкции като @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true) в JPA изискват предаване на параметри чрез именувани параметри, а не чрез конкатенация. Query Builders (Knex, jOOQ) също параметризират заявките по подразбиране, ако не се използват сурови методи.
Екранирането на специални знаци (mysql_real_escape_string) — остарял метод, който не защитава от всички видове SQL Injection. Проблем: екранирането зависи от кодирането и може да бъде заобиколено чрез използване на многобайтови кодировки (напр. GBK в азиатски системи). Използвайте екраниране само в legacy код, където параметризацията не е възможна, и винаги в комбинация със строга валидация на типа входни данни.
| Метод | Ефективност | Препоръка |
|---|---|---|
| Prepared Statements | 100% | Задължително за всички заявки |
| ORM (правилна употреба) | 99% | Препоръчва се |
| Екраниране на низове | 70% (зависи от кодирането) | Само legacy |
| Валидация на вход (бял списък) | 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 endpoints чрез 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 и именувани параметри. Ако използвате сурови заявки (nativeQuery в JPA, rawQuery в Room), защитата не работи — трябва да предавате параметри чрез подготвени изрази, а не чрез конкатенация на низове.
Second-Order SQL Injection е атака, при която злонамерени данни се съхраняват в базата данни като безопасни, но след това се използват в друга заявка без екраниране. Например, нападателят се регистрира с потребителско име ' OR '1'='1. Данните се съхраняват като низ — при регистрация няма атака. Но ако друга заявка замести потребителското име в SQL без параметризация, инжекцията се активира.
NoSQL Injection е потенциално по-опасен поради по-ниската осведоменост на разработчиците. Разработчиците знаят за SQL Injection и повечето използват ORM, но малко знаят за NoSQL Injection. В MongoDB неправилно формирана заявка може да върне всички документи в колекцията. Защита — същите prepared statements (BSON параметризация) и строга валидация на входните данни.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също