es kú el indžekšnTakéSQLi, SQL injekce, injektáž SQLPokročilý
Definice
SQL injection je zranitelnost, při které útočník vloží do vstupu aplikace text, jenž se stane součástí SQL dotazu a změní jeho význam. Útočník tak čte cizí data, obchází přihlášení nebo maže tabulky. Příčinou je skládání dotazu spojováním řetězců místo použití parametrizovaných dotazů s odděleným kódem a daty.
Než se na to spolehnete: Wikidata Q identifikátor pro SQL injection uvádím z paměti a nemusí být přesný; doporučuji ověřit. Existenci české verze článku na Wikipedii rovněž stojí za to zkontrolovat.
Kde vzniká záměna kódu a dat
SQL injection stojí na jediné chybě: aplikace poskládá text dotazu spojením pevné části a hodnoty od uživatele, a databáze pak celý výsledek přečte jako kód. Databáze nemá jak poznat, že část řetězce původně pocházela z formuláře. Když uživatel do pole napíše apostrof, ukončí jím řetězcový literál a všechno za ním se stává novou logikou dotazu. Podrobněji o samotném jazyce viz SQL.
-- zranitelné složení dotazu
"SELECT * FROM users WHERE email = '" + vstup + "'"
-- vstup: ' OR '1'='1
SELECT * FROM users WHERE email = '' OR '1'='1'
Jaké varianty útočníci používají
- Klasická (in-band): výsledek útoku se vrátí přímo v odpovědi, typicky přes
UNION SELECTnebo chybovou hlášku databáze. - Slepá (blind): aplikace nic nevypíše, ale liší se chování. Útočník se ptá po jednom bitu: podmínkou zjišťuje, zda stránka vrátí výsledek, nebo se odpověď zdrží kvůli funkci typu
pg_sleep. - Out-of-band: databáze je donucena navázat vlastní spojení ven, například DNS dotazem, a data odtečou tímto kanálem.
- Second-order: nebezpečná hodnota se nejdřív bezpečně uloží a odpálí se až později, když ji jiná část aplikace vloží do dotazu.
Co obrana skutečně řeší
Jediná spolehlivá obrana odděluje kód od dat na úrovni protokolu. Parametrizovaný dotaz (prepared statement) pošle databázi šablonu s placeholdery a hodnoty zvlášť; hodnota se nikdy neparsuje jako SQL, ať obsahuje cokoli. Escapování apostrofů ručně je náhradní řešení, které selhává na kódování znaků, číselných kontextech i na nestandardních režimech serveru.
Parametry ale nelze použít pro názvy tabulek, sloupců ani pro směr řazení. Tam patří whitelist povolených hodnot, ne řetězcová manipulace. ORM a query buildery chrání jen do chvíle, kdy vývojář sáhne po metodě typu raw().
# Python, DB-API: hodnota jde mimo text dotazu
cur.execute("SELECT id FROM users WHERE email = %s", (email,))
Vrstvy, které škodu zmenšují
Ani dokonalý kód nestačí sám o sobě. Aplikační účet databáze má mít jen práva, která opravdu potřebuje: účet e-shopu nepotřebuje DROP TABLE ani čtení tabulky s auditními logy. Hesla patří do databáze jen jako moderní hash, aby únik nevedl rovnou k převzetí účtů. WAF filtruje známé vzory, ale obchází se kódováním a komentáři, takže je to detekční vrstva, ne oprava. Validace vstupu (délka, formát, výčet hodnot) zmenšuje prostor pro útok, sama ale zranitelnost neodstraní.
Testovat se dá staticky (analýza kódu hledající konkatenaci do dotazu) i dynamicky, včetně automatizovaných skenerů. U starších systémů bývá největší práce najít všechna místa, kde se dotaz skládá v uložených procedurách nebo v reportovacích nástrojích mimo hlavní aplikaci.
Příklady z praxe
Obejití přihlašovacího formuláře
Přihlašovací stránka skládá dotaz z e-mailu a hesla přímo do textu. Útočník zadá do pole e-mail hodnotu s apostrofem a podmínkou, která je vždy pravdivá, a doplní komentář, jímž odřízne kontrolu hesla. Databáze vrátí první řádek tabulky uživatelů, což bývá administrátor, a aplikace ho přihlásí.
-- vstup: admin@firma.cz' -- SELECT id FROM users WHERE email = 'admin@firma.cz' --' AND password_hash = '...'Řazení výpisu podle parametru z URL
Výpis objednávek předává název sloupce z query stringu přímo do klauzule ORDER BY, protože parametr sem doplnit nelze. Útočník tam vloží poddotaz a čte data z jiné tabulky. Oprava je mapa povolených hodnot: co není v seznamu, spadne na výchozí sloupec.
const sortable = { date: 'created_at', price: 'total' }; const col = sortable[req.query.sort] ?? 'created_at'; db.query(`SELECT * FROM orders ORDER BY ${col} DESC`);
Časté omyly
- MýtusPoužíváme ORM, takže SQL injection nás netrápí.
- Ve skutečnostiORM chrání jen dotazy, které samo skládá. Jakmile vývojář použije raw SQL, vlastní fragment ve WHERE nebo dynamický název sloupce, je zranitelnost zpátky. Riziko také zůstává v uložených procedurách, které si dotaz sestavují uvnitř databáze.
- MýtusStačí odstranit apostrofy a slova jako DROP nebo UNION.
- Ve skutečnostiBlacklisty se obcházejí kódováním, komentáři uprostřed klíčového slova nebo změnou velikosti písmen. V číselném kontextu navíc útočník apostrof vůbec nepotřebuje. Spolehlivé je jen oddělení dat od kódu parametrizovaným dotazem.
- MýtusAplikace je za HTTPS a přihlášením, tak se k dotazům nikdo nedostane.
- Ve skutečnostiHTTPS chrání přenos, ne zpracování vstupu na serveru. Útočník bývá běžný registrovaný uživatel a injection provede přes normální formulář. Velká část případů se navíc týká funkcí dostupných až po přihlášení.
Časté dotazy
- Jak poznám v kódu místo náchylné na SQL injection?
- Náchylné místo pozná vývojář podle toho, že se text dotazu vzniká spojováním řetězců, formátováním nebo šablonovým literálem, do kterého vstupuje proměnná. Podezřelé jsou také volání typu raw, execute s jediným argumentem nebo dynamicky sestavené klauzule WHERE a ORDER BY. Pomůže grep po znacích plus a interpolaci v okolí slov SELECT, INSERT a UPDATE, statický analyzátor s pravidly pro injection a kontrola uložených procedur, které si dotaz skládají uvnitř databáze.
- Chrání parametrizované dotazy proti všem typům SQL injection?
- Parametrizované dotazy odstraní injection všude, kde jde o hodnotu: řetězec, číslo, datum. Nepokryjí ale místa, kam parametr syntakticky nepatří, tedy názvy tabulek a sloupců, směr řazení, LIMIT v některých ovladačích a celé fragmenty SQL. Pro ta místa je potřeba whitelist povolených hodnot nebo mapování z klíče na pevný název. Riziko zůstává i tehdy, když se parametr sice předá, ale uvnitř uložené procedury se z něj poskládá další dynamický dotaz.
- Co dělat, když už k útoku přes SQL injection došlo?
- Po zjištěném útoku je prvním krokem zajistit logy databáze i webového serveru a neupravovat je, protože jsou hlavním důkazem rozsahu. Následuje uzavření zranitelného místa, rotace přihlašovacích údajů k databázi a všech tajemství, která mohla uniknout, a vynucení změny hesel uživatelů. Rozsah úniku se určí z logů dotazů. Pokud unikly osobní údaje, vzniká podle GDPR povinnost posoudit ohlášení dozorovému úřadu zpravidla do 72 hodin a případně informovat dotčené osoby.
- Je NoSQL databáze proti injection imunní?
- NoSQL databáze imunní není, jen má jinou podobu útoku. Místo SQL syntaxe útočník podstrčí objekt nebo operátor, který změní význam dotazu, typicky operátory porovnání v dokumentových databázích nebo vložený kód do serverem vyhodnocovaného výrazu. Příčina je stejná jako u SQL: nedůvěryhodný vstup se stane součástí struktury dotazu. Obrana spočívá ve validaci typů, odmítnutí vstupů začínajících znakem dolaru a v používání ovladačů, které hodnoty předávají odděleně.
Zdroje
- SQL Injection Prevention Cheat Sheet(otevře se v novém okně)
- OWASP Top Ten(otevře se v novém okně)
- PREPARE(otevře se v novém okně)
- sqlite3: DB-API 2.0 interface for SQLite databases(otevře se v novém okně)