Jak na to: SQL injection, magic_quotes_gpc, addslashes() a stripslashes()

V názvu článku jsem vyjmenoval slova, která jsou strašákem nejednoho PHP programátora. Strašák to je ale pouze uměle vytvořený, vycházející z neznalosti problematiky.

Dost často se ve spojení s SQL injection (typ útoku hackera) zmiňuje PHP konstanta magic_quotes_gpc. Prý, že pokud je zapnutá, tak se dá SQL injection předejít. A víte, že od PHP 5.3 bude standardně magic_quotes_gpc vypnutá?

Znamená to, že poté budou SQL dotazy napadnutelné pomocí SQL injection? Ale vůbec ne.

Konstanta magic_quotes_gpc totiž s ochranou SQL injection nemá v podstatě nic společného. Pouze zde existuje průsečík v možnosti jejího využití. Proto ji neznalí programátoři začali přisuzovat větší váhu, než má. Váha magic_quotes_gpc je samozřejmě nulová, zvláštně, když od PHP 5.3 bude vypnutá a v PHP 6 zanikne úplně.

Vysvětleme si tedy, oč jde. SQL injection je vážný problém.

SQL injection: Co to je a jak se bránit?

SQL injection je označení pro typ útoku hackera. Jedná se o změnu, podvržení původního SQL dotazu. Dotaz tedy udělá ne to, co chcete vy, ale co chce hacker.

Pokud chceme z databáze získat nějaký záznam (v tomto případě údaje o uživateli zdenekvecera) použijeme obdobný dotaz:

$sql = "SELECT *
FROM uzivatel
WHERE nick='zdenekvecera' LIMIT 1";

Všimněte si, že vkládané hodnoty uzavíráme mezi apostrofy. Je to zcela korektní a běžná praxe.

Pokud nick uživatele předáváme například v parametru adresy GET, může dotaz vypadat takto:

$sql = "SELECT *
FROM uzivatel
WHERE nick='".$_GET['uzivatel_nick']."' LIMIT 1";

A zde nastává kritický moment, protože uvedený příklad je potenciálně nebezpečný. Snadno lze napadnout pomocí SQL injection.

Představte si, že $_GET[‘uzivatel_nick”] nebude obsahovat nick uživatele (zdenekvecera), ale řetězec se znaky, které dokáží podobu SQL dotazu změnit. Mezi takové znaky patří apostrof:

'

Pokud bude proměnná $_GET[‘uzivatel_nick’] obsahovat:

' OR id=2 ORDER BY id DESC --

Tak dotaz bude vypadat takto:

$sql = "SELECT *
FROM uzivatel
WHERE nick='' OR id=2 ORDER BY id DESC --' LIMIT 1";

Dvě pomlčky MySQL interpretuje jako komentář, takže výsledná podoba provedeného dotazu je následující:

$sql = "SELECT *
FROM uzivatel
WHERE nick='' OR id=2 ORDER BY id DESC";

Útočník tedy dokázal změnit podmínku WHERE. Místo nicku uživatele se vyhledává ID.

Relativně snadno tak může útočník získat citlivé údaje (skryté e-maily, čísla platebních karet), přístup k administrátorským účtům nebo třeba smazat data z databáze.

Co je tedy hlavní problém? Absence escapování

Problém je v tomto případě apostrof. Aby se apostrof do databáze uložil jako apostrof, a nebyl interpretován jako znak uvozující konec řetězce, muselo by se před apostrof umístit zpětné lomítko (\), z angličtiny známé jako backslash. Tato akce se nazývá escapování.

Tento kód je tedy bezpečný:

$sql = "SELECT *
FROM uzivatel
WHERE nick='\' OR id = 2 ORDER BY id DESC --' LIMIT 1";

Jediná změna je v tom, že před apostrof se umístilo zpětné lomítko a v databázi se vyhledá následující řetězec:

' OR id = 2 ORDER BY id DESC --

Zpětné lomítko je součástí jen po dobu zpracování, do databáze se nezapisuje (při INSERT nebo UPDATE) a ani není součástí při výběru (SELECT). Do databáze se tedy zpětné lomítko neuloží.

Řešení je tedy jednoduché, stačí přidat před apostrof zpětné lomítko (escapovat).

Jak přidat zpětné lomítko (backshlash)?

Snadno. Každá databáze nabízí funkci, která ošetří nebezpečné znaky u vstupního řetězce. Apostrof totiž není jediný. Kromě něj způsobuje problém třeba i uvozovka či zpětné lomítko.

MySQL (resp. PHP) má funkci mysql_real_escape_string().

Takto vykonaný dotaz by byl zcela korektní a bezpečný:

$sql = "SELECT *
FROM uzivatel
WHERE nick='".mysql_real_escape_string($_GET['uzivatel_nick'])."' LIMIT 1";

Funkce mysql_real_escape_string() totiž ošetří problémové znaky a vrátí jejich bezpečnou podobu (se zpětnými lomítky), jak už bylo uvedeno výše.

' => \'
" => \"
\ => \\

Tímto končí problematika SQL injection.

PHP také ošetřuje apostrofy a uvozovky pomocí addslashes()

Podobně, jako mysql_real_escape_string(), se i chová addslashes(). Je tedy vhodné addslashes() pro ochranu před SQL injection použít? Jednoznačně ne.

Funkce mysql_real_escape_string() ošetřuje všechny problémové znaky, které jsou pro MySQL typické. Navíc, na rozdíl od addslashes(), zohledňuje i znakovou sadu (kódování).

SQL dotaz ošetřený pomocí addslashes() není 100% ochráněn před SQL injection (viz zde a zde).

addslashes() tedy pro escapování znaků kvůli SQL injection nepoužívejte.

Co s tím má společného magic_quotes_gpc a stripslashes()?

Vůbec nic. Pokud máte zapnutou PHP konstantu magic_quotes_gpc, tak u $_POST, $_GET a $_COOKIE automaticky dochází k přidávání zpětných lomítek například před uvozovku, apostrof a zpětné lomítko. Ještě jednou zdůrazňuji, že automaticky.

Tyto proměnné ale nemají s SQL databází, potažmo s SQL injection, vůbec nic společného.

Když do formuláře s názvem komentar napíšete:

it's ok

a odešlete, v proměnné $_POST[‘komentar’] budete mít:

it\'s ok

Opakuji, že se tak děje pouze tehdy, když je magic_quotes_gpc zapnutá.

Pokud chcete takovou proměnnou vypsat na stránce, vložit do formuláře nebo uložit do databáze, musíte nejprve zavolat funkci stripslashes() s parametrem $_POST[‘komentar’]. Ta totiž odstraní z řetězce nadbytečná zpětná lomítka.

Kdyby byla konstanta magic_quotes_gpc vypnutá, proměnná $_POST[‘komentar’] bude klasicky obsahovat:

it's ok.

Jestli je magic_quotes_gpc zapnutá zjistíte pomocí funkce get_magic_quotes_gpc().

Zapnutá magic_quotes_gpc a ochrana před SQL injection

Malý příklad. Představte si, že konstanta magic_quotes_gpc je zapnutá a vy proměnnou $_POST[‘komentar’] ihned vkládáte do SQL dotazu.

V tom případě byste nejprve měli z proměnné $_POST[‘komentar’] odstranit nadbytečná zpětná lomítka pomocí stripslashes() a následně ošetřit řetězec pomocí mysql_real_escape_string().

Pokud byste stripslashes() nepoužili, k dotazu by se přidaly další lomítka:

it\\\'s ok

To by znamenalo, že se lomítko uloží i do databáze. V databázi byste tak měli:

it\'s ok

Když je ale konstanta magic_quotes_gpc vypnutá, krok s stripslashes() musíte naopak vynechat.

A to je celá věda

Až bude magic_quotes_gpc standardně deaktivovaná (PHP 5.3) nebo zrušena (PHP 6), tyto problémy odpadnou. Nic se ale nezmění na tom, že bude potřeba ošetřovat SQL dotazy tak, jak je uvedeno výše. SQL injection je vážný problém.

David Grudl napsal o escapování pěkný souhrnný článek, doporučuji k přečtění.




7 Responses to “Jak na to: SQL injection, magic_quotes_gpc, addslashes() a stripslashes()”

  • Kalimero Says:

    Proč píšeš, že magic_quotes_gpc neochrání před sql injection, když následně píšeš, že vkládá před uvozovky a apostrofy lomítko, čili tím “zničí” potencionální SQL Injection útok?
    Pokud do SQL dotazu vkládám proměnou z POST nebo GET, která je ošetřena magic_quotes_gpc, tak jak ji chceš obejít, když se ti zalomítkují apostrofy?

  • Zdeněk Veřeřa Says:

    Ochrana dotazu před SQL injection je možná pomocí zapnuté konstanty magic_quotes_gpc dostatečná, není ale 100%. Viz citace z článku:

    SQL dotaz ošetřený pomocí addslashes() není 100% ochráněn před SQL injection (viz zde a zde).

  • Kalimero Says:

    Bohužel k mé škodě nevládnu anglickým jazykem, takže moc nevím o co se v těch článcích jedná, ale nenašel jsem tam ani jednu zmínku o magic _quotes_gpc
    Můžeš mi prosím v kostce tedy říct, v jakém připadě mě magic_quotes_gpc neochrání před řetězci v sql? Čísla, která do sql dotazu vkládám bez uvozovek, klasicky intvaluju, popřípadě jinak ověřuju, zda jsou opravdu čísla.

  • Jakub Vrána Says:

    Doplním, že mysql_real_escape_string() zohledňuje pouze kódování nastavené funkcí mysql_set_charset(), která je k dispozici až od PHP 5.2.3 a MySQL 5.0.7. Ve starších verzích je potřeba používat jednobajtová kódování nebo UTF-8 a vyhnout se složitějším kódováním jako sjis.

  • Poky Says:

    Chtěl bych se zeptat, pokud používám v get jako parametr číslo a tím vybírám z db číslo například článku a dělám to následovně:

    $id=$_GET[‘id’];
    if(is_numeric($id)){
    mysql_query(‘select * from clanky where id=”‘.$id.'”‘);
    }else echo “vyfič!”;

    Je to bezpečné?

  • Zdeněk Veřeřa Says:

    Ano. Použít is_numeric() pro ověření, zda-li proměnná obsahuje číselnou hodnotu, je dostačující.

    Když vidím uvedený zdroják, upozornil bych na dvě věci:
    – Pokud nebude $_GET[‘id’] existovat, PHP vyhodí NOTICE. Řešením je kontrola if(isset($_GET[‘id’])){ … }
    – Konstrukce “select *” není moc šťastná, pokud nepotřebuješ opravdu všechny sloupce (=> zbytečně náročné na výkon a traffic). Efektivnější je vyjmenovat požadované sloupce SELECT `nazev`, `autor` …

  • Janíček Jaroslav Says:

    Mám problém z UTF-8 pomocí objektu mysqli vytvořím připojení a jedu. Vše mám ve svým FrameWorku ale najednou přestalo fungovat na stránkách se zobrazují nesmyslné znaky. Připojoju se ke dvou databázím z čehož jedna znich je lokální a od té doby to blbne.. včechny testy zda je utf8 format aktivni jsem provedl každý výstup hlásí že jede pod utf 8 ale nesmysl to stejně zobrazí

Leave a Reply