SQL injection

SQL injection je technika napadení databázové vrstvy programu vsunutím (odtud „injection“) kódu přes neošetřený vstup a vykonání vlastního pozměňujícího poškozujícího SQL příkazu (dotazu DELETE, UPDATE, ALTER atp.). Toto nezamýšlené neošetřené chování vzniká při propojení aplikační vrstvy s databázovou vrstvou (téměř vždy se totiž jedná o dva různé programy).

SQL injection a web

[editovat | editovat zdroj]

V klasickém případě se jedná o útok na internetové stránky prováděný přes neošetřený formulář, manipulací s URL nebo třeba i podstrčením zákeřně upravené cookie. Na internetu je však stále velké množství webů, spravovaných převážně nezkušenými programátory, kteří o této technice útoku vůbec nevědí a tuto kritickou chybu opomíjejí.

Blind SQL injection

[editovat | editovat zdroj]

Blind SQL injection se používá, pokud je webová aplikace náchylná k SQL injection útoku, ale výsledek útoku se útočníkovi nezobrazí. Takto zranitelná stránka nemusí být jediná, která zobrazuje data, ale zobrazí se rozdílně v závislosti na výsledku logického výrazu vloženého do SQL dotazu na databázi. Tento typ útoku může být časově náročný, protože každý nový výraz musí být vytvořen pro každý odhalený bit. Existuje několik nástrojů, které pomohou automatizovat tyto útoky, jakmile byla zjištěna lokace zranitelnosti a cílová informace byla zjištěna.

Ukázka útoku

[editovat | editovat zdroj]

Mějme program odesílající dotaz do databáze:

 statement := "SELECT * FROM uzivatele WHERE jmeno = '" + zadaneJmeno + "';"

Pokud však uživatel zadá jako jméno například:

a' or 'b'='b

aplikační program dotaz doplní a odešle databázi ve formě:

 statement := "SELECT * FROM uzivatele WHERE jmeno = 'a' or 'b'='b';"

což může zapříčinit přemostění autentizační procedury, protože 'b' = 'b' je vždy pravda, tudíž klientská (zobrazovací) vrstva aplikace vypíše všechny uživatele (nejen s jménem 'a'), pokud se jedná o stránku záznamů, tj. ne stránku vlastností jednoho záznamu se jménem 'a'. Pro SQL injection se samozřejmě dají použít všechny dostupné příkazy. Pokud by tedy útočník v předešlém příkladě jako jméno zadal:

a';DROP TABLE uzivatele; --

vypadal by dotaz při odeslání serveru jako:

 statement := "SELECT * FROM uzivatele WHERE jmeno = 'a';DROP TABLE uzivatele; --';"

čímž by smazal celou tabulku uživatelů, pokud dotaz proběhně pod oprávněním uživatele s právem mazat databázové objekty (viz dále Obrana na straně databáze). Poslední apostrof se pomocí sekvence dvou pomlček stane poznámkou a nemá žádný vliv. Podobných průniků je samozřejmě celá řada. Dokonce díky klauzulím UNION a JOIN nejsme ani vázáni na tabulku předepsanou v části FROM a můžeme vypisovat data odkudkoliv z databáze.

Obrana na straně aplikace

[editovat | editovat zdroj]

Přímočarý, i když k chybám náchylný způsob, jak zabránit SQL útokům, je takzvané „Escapování“ znaků, které mají speciální význam v SQL. Manuál pro SQL DBMS vysvětluje, které znaky mají speciální význam, což povoluje vytvoření blacklistu znaků, které potřebují přeložit. Například každý výskyt apostrofu (') v parametru musí být nahrazen dvěma apostrofy pro vytvoření úplného validního řetězce. Například v PHP je při escapování zvykem používat funkcí mysql_real_escape_string() ještě před odesláním dotazu k provedení v databázi.

Příklad v jazyce Perl

[editovat | editovat zdroj]
 $query = $sql->prepare(
        "SELECT * FROM uzivatele WHERE jmeno = "
        . $sql->quote($zadaneJmeno)
   );

Příklad v jazyce PHP (MySQLi)

[editovat | editovat zdroj]

Příklad využívá moderní knihovnu MySQLi:

$link = @mysqli_connect(db_hostname, db_username, db_password, db_name); //otevření nového připojení do MySQL

$val1 = mysqli_real_escape_string($link, $_POST["va1"]); //escapuje nedovolené znaky ze superglob. proměnné
$val2 = mysqli_real_escape_string($link, $_GET["va1"]); //escapuje nedovolené znaky ze superglob. proměnné

$query = sprintf("SELECT * FROM `test` WHERE value1='%s' AND value2='%s'",
                  $val1,
                  $val2); //vytvoří dotaz do databáze s již ošetřenými hodnotami

$result = mysqli_query($link, $query); //vrací výsledek dotazu
/*
  Zde můžeme využívat výsledky z databáze uložené v $result např.:
  while($row = mysqli_fetch_object($result)) {
    echo $row->value3;
    ...
  }
*/
mysqli_close($link); //uzavřeme spojení s databází

Takto může vypadat ošetřený kód proti sql injection. Vstupní proměnné jsou zde ošetřeny pomocí funkce mysqli_real_escape_string(mysqli $link, string $escapestr), která přidá zpětné lomítko následujícím znakům: \x00, \r, \n, \, ', , a \x1a. Tato funkce by měla být použita pro každou proměnnou předávanou dotazu. Ovšem existují i výjimky u kterých je zbytečné tuto funkci použit, např. pokud se jedná o proměnnou obsahující pouze celé číslo (int), zde se doporučuje použít v PHP intval(). Níže je ukázka staršího způsobu ošetření kódu proti SQL injection, tzv. escapování proměnných:

<?php
$mysqli = new mysqli("localhost", "redakcni_system", "tajne-heslo", "redakcni_system");
if ($mysqli->character_set_name()!="utf8mb4") { $mysqli->set_charset("utf8mb4"); }
$ucet = $_POST['ucet'];
$ucet = $mysqli->real_escape_string($ucet); // starší obrana proti SQL injection
$dotaz = "select * from uzivatele where ucet=\"".$ucet."\"";
$vysledek = $mysqli->query($dotaz);
$mysqli->close();
?>
vizualizace SQL injection
vizualizace SQL injection

Nyní si ukážeme názornou ukázku (vizualizace) SQL injection, zadání přihlašovacího jména a hesla. Při chybějícím řádku okomentovaném jako "starší obrana proti SQL injection" se provede SQL injektáž a útočník i bez zadání hesla získá všechny údaje z tabulky uzivatelé. Místo očekávaného dotazu

select * from uzivatele where ucet="admin";

se provede již daty upravený SQL dotaz útočníka:

select * from uzivatele where ucet="" OR "1"="1";

Získané údaje útočníka získané úspěšným útokem, SQL injection, z přihlašovacího formuláře:

select * from uzivatele where ucet="" OR "1"="1";
+-------------+--------------+----------+-------+--------------------------------------------------------------+---------------+
| uzivatel_id | ucet         | Prijmeni | jmeno | heslo                                                        | funkce        |
+-------------+--------------+----------+-------+--------------------------------------------------------------+---------------+
|           1 | admin        | Novák    | Josef | $2y$10$yC9GNxDIZkod9FYvmZWx4ucBWmcISVo5HPBkBVs30aoLtLyltK9pW | správce       |
|           2 | pjotr        | Hladiš   | Petr  | $2y$10$2Lkshc/udDrQzk1eP8jcIuadhIFVsLffQDS6hh93.ttiOvukc92Qy | šéfredaktor   |
|           3 | Jana         | Malá     | Jana  | $2y$10$mWBfBo.o.aWI16GdAgMgb.0C3y/U3JpBMAZ.SsxESVxI9DLZtpLsG | redaktor      |
|           4 | prispevatel1 | Novotný  | Jan   | $2y$10$Ww25MJ8b5KfS888gyRf0PukEEQTvD0kYtr.ozE52PiaWif8RSCpFa | přispěvatel   |
+-------------+--------------+----------+-------+--------------------------------------------------------------+---------------+
4 rows in set (0,001 sec)

Za nejmodernější obranu proti SQL injection odpovídající přelomu roku 2022/2023 se považuje oddělené předávání dotazu a jeho parametrů databázovému serveru, tedy parametrizované SQL dotazy. Pro příklad mějme databázi "redakcni_system" s tabulkou "uzivatele":

MariaDB [redakcni_system]> show tables;
+---------------------------+
| Tables_in_redakcni_system |
+---------------------------+
| uzivatele                 |
+---------------------------+
1 row in set (0,001 sec)

MariaDB [redakcni_system]> describe uzivatele;
+-------------+------------------+------+-----+---------+----------------+
| Field       | Type             | Null | Key | Default | Extra          |
+-------------+------------------+------+-----+---------+----------------+
| uzivatel_id | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| ucet        | varchar(25)      | YES  | UNI | NULL    |                |
| Prijmeni    | varchar(25)      | YES  |     | NULL    |                |
| jmeno       | varchar(25)      | YES  |     | NULL    |                |
| heslo       | varchar(128)     | YES  |     | NULL    |                |
| funkce      | varchar(20)      | YES  |     | NULL    |                |
+-------------+------------------+------+-----+---------+----------------+
6 rows in set (0,002 sec)

MariaDB [redakcni_system]> select * from uzivatele;
+-------------+--------------+----------+--------+--------------------------------------------------------------+---------------+
| uzivatel_id | ucet         | Prijmeni | jmeno  | heslo                                                        | funkce        |
+-------------+--------------+----------+--------+--------------------------------------------------------------+---------------+
|           1 | admin        | Novák    | Josef  | $2y$10$yC9GNxDIZkod9FYvmZWx4ucBWmcISVo5HPBkBVs30aoLtLyltK9pW | správce       |
|           2 | pjotr        | Hladiš   | Petr   | $2y$10$2Lkshc/udDrQzk1eP8jcIuadhIFVsLffQDS6hh93.ttiOvukc92Qy | šéfredaktor   |
|           3 | Jana         | Malá     | Jana   | $2y$10$mWBfBo.o.aWI16GdAgMgb.0C3y/U3JpBMAZ.SsxESVxI9DLZtpLsG | redaktor      |
|           4 | prispevatel1 | Novotný  | Jan    | $2y$10$Ww25MJ8b5KfS888gyRf0PukEEQTvD0kYtr.ozE52PiaWif8RSCpFa | přispěvatel   |
+-------------+--------------+----------+--------+--------------------------------------------------------------+---------------+

Nyní pro autorizaci (přihlášení) je nejlepší provést parametrizovaný dotaz, kdy si nejprve ověříme, zda počet vrácených záznamů je pouze jeden (nebo žádný). Nikdy není správné, pokud by byl počet > 1. Níže je uveden příklad parametrizovaného dotazu v PHP s rozhraním MySQLi a nad databází MariaDB (dříve MySQL):

$mysqli = new mysqli("localhost", "redakcni_system", "tajne-heslo", "redakcni_system");
if ($mysqli->character_set_name()!="utf8mb4") { $mysqli->set_charset("utf8mb4"); }
$delka=15; $ucet = mb_substr(trim($_POST['ucet']),0,$delka,"utf-8"); $ucet=strip_tags($ucet);
$ucet = str_replace(array("'",">","<",'"'), array("","","",""), $ucet);
// =================== parametrizovaný dotaz ===============  
$dotaz = "select * from uzivatele where ucet=?";
$typy = "s";
$parametry = array($ucet);
if ($stmt = $mysqli->prepare($dotaz)) { 
 $stmt->bind_param($typy, ...$parametry);
 $stmt->execute();
 $vysledek = $stmt->get_result();
 $stmt->close();
 $poczaznamu = $vysledek->num_rows;
 while($zaznam = $vysledek->fetch_assoc()) {
  echo $zaznam['ucet']." - ".$zaznam['jmeno']." ".$zaznam['Prijmeni']." - ".$zaznam['funkce']."<br />";
 }
}  
$mysqli->close();

Jako typy parametrů (zde proměnná $typy) se používá:

  • i (integer)
  • d (double, real, float)
  • s (string)
  • b (blob)

Proč raději vždy používat pouze pole v

$stmt->bind_param($typy, ...$parametry)

je patrné z ukázky pro úpravu záznamu(ů), která je (ta ukázka) s parametrizovaným dotazem odolná proti SQL injection:

$mysqli = new mysqli("localhost", "redakcni_system", "tajne-heslo", "redakcni_system");
if ($mysqli->character_set_name()!="utf8mb4") { $mysqli->set_charset("utf8mb4"); }
$uid = intval($_POST['uid']);
$delka=15; $ucet = mb_substr(trim($_POST['ucet']),0,$delka,"utf-8"); $ucet=strip_tags($ucet);
$delka=50; $heslo = mb_substr(trim($_POST['heslo']),0,$delka,"utf-8"); $heslo = password_hash($heslo, PASSWORD_DEFAULT);
$delka=25; $prijmeni = mb_substr(trim($_POST['prijmeni']),0,$delka,"utf-8"); $prijmeni=strip_tags($prijmeni);
$delka=25; $jmeno = mb_substr(trim($_POST['jmeno']),0,$delka,"utf-8");
$jmeno=strip_tags($jmeno); $jmeno = str_replace(array("'",">","<",'"'), array("","","",""), $jmeno); 
$dotaz = "UPDATE uzivatele SET ucet = ?, heslo = ?, Prijmeni = ?, jmeno = ? WHERE uzivatel_id = ?";
$typy = "ssssi";
$parametry = array($ucet,$heslo,$prijmeni,$jmeno,$uid);
if ($stmt = $mysqli->prepare($dotaz)) {
  $stmt->bind_param($typy, ...$parametry);
  $stmt->execute();
  echo $stmt->affected_rows;
  $stmt->close();
}
$mysqli->close();

Příklad v jazyce PHP (Oracle)

[editovat | editovat zdroj]
$stmt = oci_parse($connOci, 'SELECT * FROM LOGIN WHERE jmeno = :login');
oci_bind_by_name($stmt, ':login', $_POST['login']);
oci_execute($stmt);

Existuje však mnoho funkcí pro různé typy databází v PHP, jako například pg_escape_string() for PostgreSQL. Další funkce, které slouží k ochraně databází, které neobsahují funkce pro escapování v SQL. Funkce se nazývá addslashes(string $str). Navrací string se zpětnými lomítky před znaky, které musí být v dotazech na databázi v uvozovkách. Mezi tyto znaky patří jednoduchá uvozovka ('), dvojitá uvozovka (“), zpětné lomítko (\) a NULL. Běžné předávání escapovaných řetězců SQL databázi je náchylné k chybám, protože je snadné zapomenout daný řetězec escapovat. Vytvoření transparentní vrstvy k ošetření dat ze vstupu může snížit náchylnost k chybám, dokonce ji eliminovat úplně.

Obecně řečeno, spolu s tímto je vhodné aplikaci otestovat pro všechny možnosti uživatelského vstupu. Ve vývojové verzi se doporučuje mít nastavenu nízkou úroveň vypisování chybových hlášek a varování (abychom si jich všimli a opravili je). V ostré verzi je naopak zvykem vypisování chybových hlášek co nejvíc potlačit (mohly by poskytnout útočníkovi dodatečné informace).

Obrana na straně databáze

[editovat | editovat zdroj]

V databázi můžeme útoku zabránit (nebo ho přinejmenším extrémně ztížit) vhodným nastavením oprávnění uživatele, se kterými bude program přistupovat k úložišti, k čemuž je možné využít i tzv. VIEW (zúžený, nebo naopak syntetizovaný POHLED na záznamy tabulek v databázi, který je navíc výkonnější). Málokdy je třeba přímo z aplikační vrstvy mazat (ev. upravovat atp.) tabulky anebo dokonce celé databáze, takže je vhodné pro aplikací využívaného databázového uživatele (pokud možno ne správce databáze) zakázat jak relevantní SQL příkazy (DROP, ALTER atp.), tak přístup k systémovým tabulkám daného databázového serveru.

Případy SQL injection

[editovat | editovat zdroj]
  • 1. listopadu 2006 použil jistý student SQL injection, aby pronikl na stránky tchajwanského časopisu o počítačové bezpečnosti.[1]
  • 29. června 2007 hacker „znetvořil“ stránku britské pobočky firmy Microsoft.[2] Její databázový systém se stal terčem útoku v lednu následujícího roku, kdy byl spuštěn hromadný útok zneužívající chyby v Microsoft SQL Server na SQL injection a byly nakaženy desítky tisíc počítačů, zejména v Číně.[3] Další, ještě větší vlna útoků, zaměřující se na bezpečnostní díry Microsoft IIS a Microsoft SQL server, proběhla od dubna do srpna 2008 a odhadem zasáhla kolem půl milionu počítačů.[4]
  • 13. dubna 2008 vypnul registr sexuálních a násilných útočníků státu Oklahoma svoji webovou stránku z důvodu „údržby“ poté, co byl informován, že 10 579 čísel sociálního zabezpečení patřících útočníkům bylo staženo skrze SQL injection útok.
  • 17. srpna 2009 obvinilo soudní oddělení Spojených států amerického občana Alberta Gonzalese a dva nejmenované Rusy za krádež 130 milionů čísel kreditních karet za pomoci SQL injection útoku. Údajně „největší krádež identity v dějinách Ameriky.“ Muž ukradl karty mnoha obětem útoku, poté, co sledoval jejich platby. Mezi zasaženými společnostmi byl výrobce kreditních karet Heartland Payment Systems, obchodní řetězec s příslušenstvím 7-Eleven a řetězec supermarketů Hannaford Brothers.
  • V červenci 2010 jihoamerický výzkumník bezpečnosti spadající pod Ch Russo získal citlivé uživatelské informace z populární BitTorrent stránky The Pirate Bay. Získal přístup k hlavnímu panelu administrace a zneužil SQL injection zranitelnosti. Tím získal informace o uživatelských účtech, včetně IP adres, MD5 hesel a záznamy o uploadovaných torrentech.
  • 24.–26. července 2010 hackeři z Japonska a Číny použili SQL injection, aby získali přístup k datům z kreditních karet zákazníků Neo Beat – společnosti se sídlem v Osace, která provozuje obrovskou síť internetových obchodů. Útok se netýkal pouze Neo Beat, ale i jejích sedmi obchodních partnerů včetně řetězců supermarketů Izumiya Co, Maruetsu Inc and Ryukyu Jusco Co. Krádež dat zaznamenalo 12 191 zákazníků. 14. srpna 2010 bylo nahlášeno přes 300 zneužití kreditních karet třetí stranou k objednání zboží a služeb v Číně.
  • 8. listopadu 2010 byly napadeny stránky britského královského námořnictva za pomoci SQL injection.
  • 27. března 2011 byla za pomoci SQL blind injection napadena domácí stránka databáze MySQL. Za útokem stojí TinKode.
  • V srpnu 2011 ukradl hacker uživatelské záznamy z vývojářské stránky Nokia za pomoci SQL injection.
  • V září 2011 získali turečtí hackeři přístup k DNS záznamům NetNames a přesměrovali uživatele na vlastní stránku. Tento útok se týkal uživatelů Betfair (internetové sázení), The Telegraph, The Register, The National Geographic, UPS, Acer a Vodafone.com. K útoku se následně přiznali. Zveřejnili to na Zone-H.
  • 17. prosince 2016 byl ve 3 hodiny ráno napaden nejlepší český server diablovky Mu Online, ZEN MU. Byla vymazána veškerá databáze postav. Díky včasnému zásahu Onderka a Pixieho se ovšem podařilo server uvést do provozu ještě tentýž den kolem poledne.

Mezi (poměrně úzkou) komunitou programátorů, bezpečnostních analytiků a databázových návrhářů se v posledních několika měsících dokonce rozšířil humor, narážející na některé tyto útoky, mezi jinými i SQL injection.[5][6][7]

V tomto článku byl použit překlad textu z článku SQL injection na anglické Wikipedii.

  1. www.xiom.com [online]. [cit. 2010-05-29]. Dostupné v archivu pořízeném dne 2010-01-17. 
  2. http://www.cgisecurity.net/2007/06/hacker-defaces.html
  3. Archivovaná kopie. www.pcworld.com [online]. [cit. 2010-05-29]. Dostupné v archivu pořízeném z originálu dne 2008-05-26. 
  4. Archivovaná kopie. www.computerworld.com [online]. [cit. 2010-05-29]. Dostupné v archivu pořízeném dne 2009-02-23. 
  5. https://xkcd.com/327/
  6. http://gizmodo.com/5498412/sql-injection-license-plate-hopes-to-foil-euro-traffic-cameras
  7. Archivovaná kopie. images.cryhavok.org [online]. [cit. 2013-05-10]. Dostupné v archivu pořízeném dne 2012-06-16. 

Související články

[editovat | editovat zdroj]

Externí odkazy

[editovat | editovat zdroj]