Bezpečnost a bezpečné programování 9. Databáze a bezpečnost Ing. Tomáš Zahradnický, EUR ING, Ph.D. České vysoké učení technické v Praze Fakulta informačních technologií Katedra počítačových systémů Příprava studijních programů Informatika pro novou fakultu ČVUT je spolufinancována Evropským sociálním fondem a rozpočtem Hlavního města Prahy v rámci Operačního programu Praha adaptabilita (OPPA) projektem CZ.2.17/3.1.00/31952 Příprava a zavedení nových studijních programů Informatika na ČVUT v Praze. Praha & EU: Investujeme do vaší budoucnosti T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 1 / 16
Obrázek: http://xkcd.com/327/. T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 2 / 16
Úvod Veškerý vstup do důvěryhodné zóny musí být prověřen. Pokud je vstup nekánonický, měl by být odmítnut. Podívejme na tento příklad: Příklad v PHP soubor.php <?php $db_connection = mysql_connect( localhost, www ) or die( Nemohu se připojit k databázovému serveru. ); mysql_select_db( jmenodb ) or die( Databáze neexistuje. ); $query = sprintf( "SELECT * FROM tabulka WHERE jmeno= %s ;", $_GET[ name ] ); $result = mysql_query( $query, $db_connection ); $data = mysql_fetch_assoc($result); print_r($data);...?> T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 3 / 16
Útok typu SQL Injection I Zaměřme se na řádek: $query = sprintf( "SELECT * FROM tabulka WHERE jmeno= %s ;", $_GET[ name ] ); 1 Uživatel 1 spustí náš skript přes: http://server/soubor.php?name=pepa Řádek stavící dotaz zkonstuuje SQL dotaz: SELECT * FROM tabulka WHERE jmeno= pepa ; 2 Uživatel 2 spustí náš skript přes: http://server/soubor.php?name=pepa%27%38+%20delete+from+ tabulka+where+%271%27=%271 Řádek stavící dotaz zkonstuuje SQL dotaz: SELECT * FROM tabulka WHERE jmeno= pepa ; DELETE FROM tabulka WHERE 1 = 1 ; T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 4 / 16
Útok typu SQL Injection II Zatímco první uživate jako jméno uživatele vyplnil pepa, druhý uživatel vyplnil jako jméno uživatele pepa ; DELETE FROM tabulka WHERE 1 = 1. Dotaz se provedl korektně a má-li uživatel www dostatek práv, bude veškerý obsah tabulky tabulka smazán. Chyba v příkazu je zřejmá a objevila se díky tomu, že jsme do chráněné zóny databázového konektoru vpustili externí data, aniž bychom je jakkoliv prověřili. Pokud bychom dodržovali princip nejmenších oprávnění, příkaz DELETE by selhal s chybou ACCESS_DENIED, nicméně útočník by si mohl, díky oprávnění používat nad tabulkou příkaz SELECT, vypsat obsah celé tabulky, pokud by jako jméno vložil pepa or 1 = 1. Je nutné data prověřit a teprve pak je použít s databázovým enginem. T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 5 / 16
Zabraňování útokům typu SQL Injection I SQL Injection K SQL injekci může dojít v případě, že náš program nedostatečně kontroluje vstup z externího, nedůvěryhodného zdroje. Útočník donutí náš program provést nad databází příkazy, které on sám zadá. Je dnes velmi rozšířeným typem útoku, počítejte s ní! Proti útokům typu SQL Injection se lze bránit následujícími metodami: ctít princip nejmenších oprávnění, důsledně kontrolovat vstupy, escapováním znaků, které by narušily formu SQL příkazu, použitím placeholderů. T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 6 / 16
Zabraňování útokům typu SQL Injection II Ctít princip nejmenších oprávnění V žádném případě nedávejte uživateli více oprávnění než musí mít! webový uživatel v našem případě potřebuje pouze právo SELECT nad tabulkou tabulka. v žádném případě by neměl mít další práva! mějte na paměti, že právo můžete udělit i jen pro některé sloupce tabulky! Běží DB server jen lokálně tzn. na 127.0.0.1, anebo je dostupný i přes další rozhraní? Je dostupný po LAN a/nebo i WAN? Pamatujte, že můžete omezit síťová rozhraní, na kterých server běží. MySQL může běžet na lokálním socketu, který je nepřístupný po síti (mysql_connect( :/tmp/mysql.sock, www )). Vymazali jste výchozí uživatele db? (scott/tiger, sa/,...?) T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 7 / 16
Zabraňování útokům typu SQL Injection III Důsledně kontrolovat vstupy a escapovat problematické znaky O kontrole vstupů jsme si již říkali, nicméně i přes kontrolu mohou projít data, která by mohla být škodlivá. Proto je potřeba ještě provést escapování všech nepovolených ve vstupu znaků, abychom měli jistotu, že nám vložení escapovaného vstupu SQL příkazy nikterak nenaruší: Escapovat všechny nevhodné znaky: 0x00, 0x0A, 0x0D, 0x1A, 0x22, 0x27, 0x5C. Escapovat jen s apostrofy tak, jako se to často dělá, nemusí stačit! string vstup =...; name = name.replace( ", "); PHP obsahuje funkci mysql_real_escape_string mysql_real_escape_string( $_GET[ name ], $db_connection) T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 8 / 16
Zabraňování útokům typu SQL Injection IV Používání placeholderů I Parametrizovaný příkaz (Parametrized command) Parametrizovaný příkaz nazveme příkaz, který má na místě jednoho nebo více parametrů nevyplněné hodnoty, jejichž vyplnění necháme na službách databázového systému. Vyplňování probíhá typicky tak, že DB předáme šablonu příkazu parametrizovaný příkaz a parametry k dosazení za placeholdery často? (OLE DB, ODBC) nebo @jmeno (.Net). Při vyplňování můžeme často určit i typ parametru. Řetězec s dotazem do finální podoby konstruuje databázový systém. Příklad. SELECT * FROM tabulka WHERE name=? ; T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 9 / 16
Zabraňování útokům typu SQL Injection V Používání placeholderů II Použití placeholderů v C# /* Vytvoření objektu připojení k DB */ string connectionstring =...; SqlConnection conn = new SqlConnection( connectionstring ); /* Vytvoření placeholderu, do kterého dosadíme vstupní parametr */ SqlParameter par = new SqlParameter(); par.parametername = "@name"; par.sqldbtype = System.Data.SqlDbType.VarChar; par.direction = System.Data.ParameterDirection.Input; par.value = "pepa"; /* Vytvoření parametrizovaného příkazu pro databázi obsahující placeholder */ SqlCommand cmd = new SqlCommand( "SELECT * FROM tabulka WHERE name=@name;", conn ); /* Nahrazení placeholderu obsahem parameter */ cmd.parameters.add(par); try { /* Otevření spojení s DB */ conn.open(); /* Vykonání dotazu */ SqlDataReader reader = conn.executereader();... } catch( Exception e ) { Console.WriteLine(e.Message); } T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 10 / 16
Zabraňování útokům typu SQL Injection VI Používání placeholderů III PHP modul MySQL nemá standardně podporu pro placeholdery. Podobná funkcionalita zde: T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 11 / 16
Zabraňování útokům typu SQL Injection VII Vytváření bezpečných SQL procedur I Podobně jako jsme ošetřovali parametry vstupních dat pro příkazy, nejlépe pomocí parametrizovaných příkazů, je třeba ošetřit vstupní data do SQL procedur. Všechny vstupní parametry pro procedury, nebo data, která načtete z tabulky obalte QUOTENAME(@var [, quotechar]) (MS) nebo REPLACE(@var,, ). QUOTENAME lze použít max pro délku 128 znaků. Příklad Procedura se zranitelností [MSDN] CREATE PROCEDURE sp_mysetpassword @loginname sysname, @old sysname, @new sysname AS DECLARE @command varchar(200) SET @command = UPDATE Users SET password= + @new + WHERE username= + @loginname + AND password= + @old -- Execute the command. EXEC (@command) GO T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 12 / 16
Zabraňování útokům typu SQL Injection VII Vytváření bezpečných SQL procedur I Podobně jako jsme ošetřovali parametry vstupních dat pro příkazy, nejlépe pomocí parametrizovaných příkazů, je třeba ošetřit vstupní data do SQL procedur. Všechny vstupní parametry pro procedury, nebo data, která načtete z tabulky obalte QUOTENAME(@var [, quotechar]) (MS) nebo REPLACE(@var,, ). QUOTENAME lze použít max pro délku 128 znaků. Příklad [MSDN] CREATE PROCEDURE sp_mysetpassword @loginname sysname, @old sysname, @new sysname AS DECLARE @command varchar(2000) SET @command = UPDATE Users SET password= + QUOTENAME(@new, ) + WHERE username= + QUOTENAME(@loginname, ) + AND password= + QUOTENAME(@old, ) -- Execute the command. EXEC (@command) GO T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 12 / 16
Zabraňování útokům typu SQL Injection VIII Vytváření bezpečných SQL procedur II Proceduru bychom zavolali pomocí EXEC sp_mysetpassword( pepa, 12345, 54321 );. SQL placeholdery lze použít pomocí zabudované funkce sp_executesql, která umožňuje u každého parametru určit jeho typ: DECLARE @name varchar(200) SET @name = pepa ; EXEC sp_executesql N SELECT * FROM tabulka WHERE name = @name, N @name varchar(200), @name T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 13 / 16
Shrnující příklad [1] [SqlClientPermissionAttribute(SecurityAction.PermitOnly, AllowBlankPassword=false)] [RegistryPermissionAttribute(SecurityAction.PermitOnly, Read=@"HKEY_LOCAL_MACHINE\SOFTWARE\Client")] static string GetName(string Id) { SqlCommand cmd = null; string Status = "Name Unknown"; try { /* Check for valid shipping ID. */ Regex r = new Regex(@"^\d{4,10}$"); if(!r.match(id).success) throw new Exception("Invalid ID"); /* Get connection string from registry. */ SqlConnection sqlconn= new SqlConnection(ConnectionString); /* Add shipping ID parameter. */ string str="sp_getname"; cmd = new SqlCommand(str,sqlConn); cmd.commandtype = CommandType.StoredProcedure; cmd.parameters.add("@id",convert.toint64(id)); cmd.connection.open(); Status = cmd.executescalar().tostring(); } catch (Exception e) { if( HttpContext.Current.Request.UserHostAddress == "127.0.0.1" ) Status = e.tostring(); else Status = "Error Processing Request"; } finally { /* Shut down connection--even on failure. */ if (cmd!= null) cmd.connection.close(); } return Status; } T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 14 / 16
Komentáře k příkladu I Příklad používá více vrstev bezpečnosti, a to: V žádném případě nepovoluje použití prázdných hesel. Kódu dovolíme pouze číst jediný klíč v registrech, ve kterém je uložen SQL connection string. Connection string je uložen v registrech, tedy mimo prostor přístupný z www. Kód důkladně testuje vstupní parametr ID, který může být pouze 4-10 číslic. Ostatní vstupy jsou odmítnuty. Kód používá SQL proceduru čímž nevystavuje žádnou informaci o logice aplikace a struktuře tabulek databáze. Kód používá placeholdery a nepoužívá spojování řetezů k tomu, aby formuloval SQL dotazy. Když nastane chyba, kód nesdělí útočníkovi žádné užitečné informace. T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 15 / 16
Bibliografie Howard M., LeBlanc D.: Writing Secure Code, 2 nd edition, Microsoft Press, 2003. T. Zahradnický (ČVUT FIT) Databáze a bezpečnost MI-BPR, 2011, Předn. 9 16 / 16