Témata
Řešení chyby zabezpečení databáze
Pro vyšší zabezpečení aplikací Dreamweaver 8.0.2 a CS3 jsme odstranili možnost používat dynamické SQL parametry ve standardních sadách záznamů. Původní kód vystavoval databáze možnosti injektáže SQL. Tuto chybu zabezpečení jsme vyřešili použitím připravených příkazů pro modely serverů ASP_VBS a ColdFusion. Bylo nutné přidat na stránku funkci, kdy je použita první sada záznamů, a následně tuto funkci vyvolat ze všech sad záznamů na stránce pro PHP_MySQL. Tato technika je podobná připraveným příkazům ASP_VBS a ColdFusion.
Ve snaze odstranit chybu zabezpečení, a tedy zvýšit zabezpečení aplikace, jsme obětovali určitou flexibilitu. Chceme dát vývojářům, kteří již vyvíjejí nebo plánují vývoj rozšíření pracujících s dynamickými SQL dotazy, a koncovým uživatelům, kteří si chtějí přizpůsobit své vlastní sady záznamů tak, aby přijímaly dynamické parametry, schopnost tyto vlastní sady záznamů upravovat přímo z aplikace Dreamweaver.
Injektáž SQL a způsoby ochrany
Injektáž SQL představuje techniku, která využívá chybu zabezpečení databázové vrstvy aplikace. Tato chyba zabezpečení se vyskytne, když jsou údaje zadané uživatelem nesprávně filtrovány pro escape znaky v řetězcových literálech vložené do příkazů SQL, popř. když nejsou vstupy uživatele silně typovány, a tedy neočekávaně provedeny. Jde vlastně o příklad obecnější skupiny chyb zabezpečení, které se mohou objevit v případě, kdy je jeden programovací nebo skriptovací jazyk vložen do jiného. Další informace jsou k dispozici na Wikipedii.
Příklad standardního SQL dotazu:
SELECT * FROM company_com WHERE name_com = '$companyName'
Ve výše uvedeném příkladu je proměnná $companyName (název společnosti) načtena z pole zadávacího formuláře, takže uživatel má naprostou kontrolu nad tím, jaká hodnota bude odeslána. V nejlepším možném případě, kdy uživatel zadá na vstupu jako název společnosti „Adobe“ (bez uvozovek), bude daný SQL dotaz vypadat takto:
SELECT * FROM company_com WHERE name_com = 'Adobe'
Existují však případy, kdy se uživatelé snaží narušit webovou stránku a ukrást citlivé informace a/nebo zničit databázi. Ve výše uvedeném příkladu lze injektáž SQL snadno realizovat zadáním následujícího řetězce „blabla' NEBO '1'='1“ (bez dvojitých uvozovek). Zadáním této posloupnosti znaků bude mít uživatel přístup ke všem společnostem. Skutečně provedený SQL dotaz bude:
SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'
Navíc pokud bude chtít uživatel vymazat celou tabulku company_com, stačí mu jen provést následující řetězec „x'; DROP TABLE company_com; --“ (bez dvojitých uvozovek). zde je uveden kompletní SQL dotaz:
SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'
Na ochranu svých webových stránek před injektáží SQL použijte jedno z následujících opatření:
- Omezte vstup na minimální sadu povolených znaků tak, že automaticky odstraníte všechny ostatní znaky, které spadají mimo daný rozsah.
- Zamezte všem znakům, které mohou způsobit problémy při použití uvnitř SQL dotazu (například jednoduché uvozovky).
- Použití připravených příkazů.
- Použití přístupových práv k databázi.
- Použití uložených postupů.
Doporučujeme připravené příkazy, které jsou podrobněji popsány v části Použití připravených příkazů. Aplikace Dreamweaver 8.0.2 a CS3 využívají přístupu připravených příkazů pro modely serverů ASP_VBS, ASP_JavaScript, Cold Fusion a JSP a přístupu založeného na escapování vstupů uživatele pro model serveru PHP_MySQL. Další informace o útocích injektáží SQL jsou uvedeny v části Příklady útoků injektáží SQL.
Použití připravených příkazů
Aplikace Dreamweaver 8.0.2 a CS využívají připravených příkazů, protože toto řešení nám dává záruku, že skutečné SQL parametry budou nahrazeny danými hodnotami (případně zaslanými přes adresu URL) přímo na serveru, nikoli na stránce. Připravené příkazy také zaručují, že hodnota předaná samotnému SQL je vhodného datového typu a používá příslušné escapování (pokud je parametr označen jako typ int, pak uživatel nebude moci zadat písmeno, popř. pokud je parametr typu text, pak bude celý zadaný řetězec vhodně escapován a uživatel nebude mít možnost provést injektáž SQL).
Připravené příkazy pro modely serverů ASP_VBS a ASP_JS
V případě ASP_VBS a ASP_JS se spoléháme na databázovou vrstvu ADODB. Následující příklad ukazuje jednoduchý soubor záznamů ASP_VBS, který využívá k filtrování výsledků dynamický SQL parametr:
... <% Dim Recordset1__MMColParam Recordset1__MMColParam = "1" If (Request.QueryString("id_com") <> "") Then Recordset1__MMColParam = Request.QueryString("id_com") End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com WHERE id_com = ?" Recordset1_cmd.Prepared = true Recordset1_cmd.Parameters.Append Recordset1_cmd.CreateParameter("param1", 5, 1, -1, Recordset1__MMColParam) ' adDouble Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...Připravené příkazy pro model serveru ColdFusion
ColdFusion nabízí integrovanou podporu připravených příkazů. Následující příklad ukazuje jednoduchý soubor záznamů ColdFusion, který využívá k filtrování výsledků dynamický SQL parametr:
... <cfparam name="URL.id_com" default="1"> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com WHERE id_com = <cfqueryparam value="#URL.id_com#" cfsqltype="cf_sql_numeric"> </cfquery> ...Simulované připravené příkazy pro model serveru PHP_MySQL
Připravené příkazy pro PHP jsou k dispozici od verze MySQL 4.1, ale protože jsme chtěli zajistit podporu i předchozích verzí MySQL, rozhodli jsme se implementovat vlastní funkci, která dělá prakticky totéž. Následující příklad ukazuje jednoduchý soubor záznamů PHP, který využívá k filtrování výsledků dynamický SQL parametr:
... <?php if (!function_exists("GetSQLValueString")) { function GetSQLValueString($theValue, $theType, $theDefinedValue = "", $theNotDefinedValue = "") { $theValue = get_magic_quotes_gpc() ? stripslashes($theValue) : $theValue; $theValue = function_exists("mysql_real_escape_string") ? mysql_real_escape_string($theValue) : mysql_escape_string($theValue); switch ($theType) { case "text": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "long": case "int": $theValue = ($theValue != "") ? intval($theValue) : "NULL"; break; case "double": $theValue = ($theValue != "") ? "'" . doubleval($theValue) . "'" : "NULL"; break; case "date": $theValue = ($theValue != "") ? "'" . $theValue . "'" : "NULL"; break; case "defined": $theValue = ($theValue != "") ? $theDefinedValue : $theNotDefinedValue; break; } return $theValue; } } $colname_Recordset1 = "-1"; if (isset($_GET['id_com'])) { $colname_Recordset1 = $_GET['id_com']; } mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = sprintf("SELECT * FROM company_com WHERE id_com = %s", GetSQLValueString($colname_Recordset1, "int")); $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Povolení dynamických parametrů ve standardních sadách záznamů
Jak se tedy můžeme chránit před injektáží SQL a zároveň povolit dynamické parametry a zajistit upravitelnost výsledného kódu přes panel Serverová chování v aplikaci Dreamweaver? Řešením je vložit dynamické parametry přímo do samotného SQL dotazu, nikoli je předávat jako klasické SQL parametry (které by byly buď escapovány, nebo doplněny do SQL přes připravené příkazy).
Důležité: Uživatelé nemohou takový kód vygenerovat pomocí objektů a serverových chování dodávaných s aplikacemi Dreamweaver 8.0.2 nebo CS3 (příklad: vložení sady záznamů přes panel Serverová chování nebo panel Vložit). Toto řešení vyžaduje po uživateli manuální úpravu kódu nebo instalaci externího rozšíření, které takový kód vytvoří. Odpovědnost za tvorbu zabezpečeného kódu leží na uživateli nebo na vývojáři externího softwaru.
Účelem této metody je umožnit tvorbu dynamických SQL dotazů zkušeným vývojářům. Zneužití tohoto řešení dává hackerovi možnost získat nežádoucí oprávnění a/nebo narušit webové stránky a databázi. Informace o minimalizaci rizika injektáže SQL jsou uvedeny v části Injektáž SQL a způsoby ochrany.
Hlavní identifikované případy, ve kterých by bylo toto řešení použito, jsou:
- Manuální tvorba/aktualizace SQL dotazu pro přijetí dynamických parametrů.
- Tvorba/upgrade stávajících rozšíření pro generování dynamických parametrů.
Oba případy použití budou podrobně popsány v následujících odstavcích.
Manuální tvorba/aktualizace SQL dotazu pro přijetí dynamických parametrů, přičemž sada záznamů zůstane upravitelná přímo z aplikace Dreamweaver
V následujícím příkladu chce uživatel přidat do dynamické tabulky funkci třídění. Rozhodne se znovu načíst stránku při zadávání souborů a směru třídění (vzestupně nebo sestupně), které mají být použity, přímo v adrese URL. Příkladem počáteční adresy URL je:
http://www.mydomain.com/index.php?sortCol=name_com&sortDir=ascK provedení této úlohy v aplikaci Dreamweaver pomocí serverových chování je vyžadováno ruční kódování. Následující ukázky kódu ukazují změny, které musí být pro každý model serveru provedeny.
Důležité: Mějte na paměti, že žádný z níže uvedených příkladů není validován proti injektáži SQL. Jejich funkce je čistě vzdělávací a v této podobě by neměly být produkčně využívány. Příklady nejsou 100% zabezpečeny, abychom zajistili jejich jednoduchost. Musíte se chránit před injektáží SQL. Další informace jsou uvedeny v části Injektáž SQL a způsoby ochrany.
ASP_VBS
Před:
... <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...Po:
... <% Dim orderBy: orderBy = "" If (Request.QueryString("sortCol") <> "") Then orderBy = "ORDER BY " & Request.QueryString("id_com") If (Request.QueryString("sortDir") <> "") Then orderBy = orderBy & " " & Request.QueryString("sortDir") End If End If %> <% Dim Recordset1 Dim Recordset1_cmd Dim Recordset1_numRows Set Recordset1_cmd = Server.CreateObject ("ADODB.Command") Recordset1_cmd.ActiveConnection = MM_connContacts_STRING Recordset1_cmd.CommandText = "SELECT * FROM company_com " & orderBy & "" Recordset1_cmd.Prepared = true Set Recordset1 = Recordset1_cmd.Execute Recordset1_numRows = 0 %> ...ColdFusion
Před:
... <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com </cfquery> ...Po:
... <cfparam name="URL.sortCol" default=""> <cfparam name="URL.sortDir" default="ASC"> <cfset orderBy=""> <cfif (#URL.sortCol# NEQ "")> <cfset orderBy="ORDER BY #URL.sortCol# #URL.sortDir#"> </cfif> <cfquery name="Recordset1" datasource="company_employee"> SELECT * FROM company_com #orderBy# </cfquery> ...PHP_MySQL
Před:
... <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com"; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Po:
... <?php $orderBy = ""; if (isset($_GET['sortCol'])) { $orderBy = "ORDER BY " . $_GET['sortCol']; if (isset($_GET['sortDir'])) { $orderBy .= " " . $_GET['sortDir']; } } ?> <?php mysql_select_db($database_connContacts, $connContacts); $query_Recordset1 = "SELECT * FROM company_com " . $orderBy . ""; $Recordset1 = mysql_query($query_Recordset1, $connContacts) or die(mysql_error()); $row_Recordset1 = mysql_fetch_assoc($Recordset1); $totalRows_Recordset1 = mysql_num_rows($Recordset1); ?> ...Tvorba/upgrade stávajících rozšíření pro generování dynamických parametrů, přičemž sada záznamů zůstane upravitelná přímo z aplikace Dreamweaver
Nyní vezměte v úvahu stejný scénář jako předtím, ale s tím rozdílem, že uživatel chce vytvořit rozšíření, které vygeneruje příslušný kód a nebude se spoléhat na kombinaci integrovaných funkcí a manuálních úprav. Nové rozšíření by mělo vygenerovat správný kód pro modely serverů ASP_VBS, ColdFusion a PHP_MySQL.
Důležité: V následujících kapitolách se zaměříme pouze na vygenerování kódu, který k tvorbě dynamického SQL dotazu využije hodnot z parametrů URL. Hlavní důraz je kladen na generování kódu rozpoznatelného aplikací Dreamweaver, který bude upravitelný ze standardních rozhraní Dreamweaver. Tato výuková lekce se nebude zabývat validací vstupních dat (prostřednictvím parametrů URL) ani nebude chránit finální SQL před jakýmkoli pokusem o injektáž SQL, protože takový kód by byl velmi komplexní, a tudíž by nespadal do rozsahu platnosti této lekce. Za ochranu finálního SQL před injektáží SQL je výhradně odpovědný vývojář.
V rámci lekce poskytujeme vzorové rozšíření, které jako vstup bere připojení a tabulku a vygeneruje dynamickou tabulku obsahující všechny záznamy. Současně aktualizuje vygenerovaný SQL tak, aby obsahoval proměnnou orderBy, jak je znázorněno v příkladech výše. Rozšíření bylo vytvořeno pro aplikace Dreamweaver 8.0.2 a CS3. Vygenerovaný kód není 100% zabezpečený, protože cílem je vytvořit sady záznamů obsahující dynamické parametry, které zůstanou upravitelné přes standardních rozhraní aplikace Dreamweaver. Zdrojový kód rozšíření se nachází v konfigurační složka uživatele.
Ve všech následujících příkladech kódu byly přidány zvýrazněné oddíly, které umožňují třídění pomocí parametrů URL sortCol a sortDir.
ASP_VBS
Kód, který generuje příslušný SQL dotaz pro model serveru ASP_VBS, se nachází v souboru „[KONFIGURAČNÍ_SLOŽKA_UŽIVATELE]/Commands/My Dynamic Table.js" v konfigurační složce uživatele. K zajímavé změně došlo v řádku 130:
... 130: paramObj.encodedSQL = "SELECT * FROM " + paramObj.table + " \" & orderBy & \""; ...Soubor účastníků pro serverové chování MyDynamicTable ASP_VBS obsahuje dodatečného účastníka, který přidává na stránku definici proměnné „orderBy“:
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="connectionref_statement" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="recordset_main" /> <groupParticipant name="repeatedRegion_init2" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="recordset_close" /> </groupParticipants> </group>ColdFusion
Stejný soubor „[KONFIGURAČNÍ_SLOŽKA_ÚČASTNÍKA]/Commands/My Dynamic Table.js“ v konfigurační složce účastníka rovněž obsahuje kód pro model serveru ColdFusion; příslušná změna je v tomto případě na řádku 138:
... 138: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table + " #orderBy#"; ...Soubor účastníků pro serverové chování MyDynamicTable ColdFusion obsahuje dodatečného účastníka, který přidává na stránku definici proměnné „orderBy“:
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="RepeatedRegion_pageNum" /> <groupParticipant name="RepeatedRegion_maxRows" /> <groupParticipant name="RepeatedRegion_startRow" /> <groupParticipant name="RepeatedRegion_endRow" /> <groupParticipant name="RepeatedRegion_totalPages" /> </groupParticipants> </group>PHP_MySQL
Posledním modelem serveru v rozšíření je PHP_MySQL. Příslušná změna v souboru „[KONFIGURAČNÍ_SLOŽKA_ÚČASTNÍKA]/Commands/My Dynamic Table.js“ v konfigurační složce účastníka se nachází v řádku 122:
... 122: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table+ " \" . $orderBy . \""; ...Soubor účastníků pro serverové chování MyDynamicTable PHP_MySQL obsahuje dodatečného účastníka, který přidává na stránku definici proměnné „orderBy“:
<group name="MyDynamicTable" version="9.0"> <groupParticipants> <groupParticipant name="Connection_include" /> <groupParticipant name="MyDynamicTable_orderBy" /> <groupParticipant name="EditOps_SQLValueString" /> <groupParticipant name="Recordset_main" /> <groupParticipant name="DynamicTable_main" /> <groupParticipant name="Recordset_close" /> </groupParticipants> </group>Výhody
Výše popsaná metoda má několik výhod, včetně následujících:
- Pokročilejší vývojáři mohou využít chyby zabezpečení před injektáží SQL pro dynamickou tvorbu komplexnějších SQL dotazů.
- Vytvořené sady záznamů budou stále upravitelné z aplikace Dreamweaver.
- Není obtížné ji integrovat do stávajících rozšíření aplikace Dreamweaver, která generují sady záznamů s dynamickými SQL parametry.
- Výchozí kód vygenerovaný aplikací Dreamweaver zůstává nezměněn; vývojáři, kteří nevyžadují dynamické dotazy (a jejich klienti), si mohou být jisti zabezpečením kódu napsaného aplikací Dreamweaver.
Nevýhody
Jelikož aplikace Dreamweaver nebyla původně vytvořena s podporou této metody, jsou zde i některé nevýhody, včetně následujících:
- Kód vygenerovaný metodou popsanou v tomto dokumentu může být napaden technikami injektáže SQL, a proto musí být chráněn jinými způsoby. Seznam těchto metod ochrany zahrnuje mimo jiné:
- Zajistit, aby koncový uživatel nemohl používat metodu injektáže SQL k tvorbě webové stránky a/nebo databáze;
- Escape z argumentů předtím, než bude dosaženo vlastního SQL;
- Dbejte na to, aby uživatel nemohl ukrást citlivé informace a/nebo vytvořit databázi, i kdyby se mu podařilo narušit váš kód.
- Dva známé problémy, které nelze opravit, dokud nebude vydána další verze aplikace Dreamweaver:
- ColdFusion: Jednoduché uživatelské rozhraní sady záznamů odstraní při úpravě a následném opětovném použití z SQL dotazu hodnoty hash u názvů proměnných
- ColdFusion, ASP_VBS, ASP_JS: Tlačítko Test z pokročilého uživatelského rozhraní nefunguje, když SQL dotaz obsahuje názvy proměnných
Vyhledání konfigurační složky uživatele
- Windows XP
C:\Documents and Settings\[uživatelské jméno]\Application Data\Adobe\Dreamweaver 9\Configuration
- Windows Vista
C:\Users\[uživatelské jméno]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
- Mac OS 10.4.x
/Users/[uživatelské jméno]/Library/Application\ Support/Adobe/Dreamweaver\ 9/Configuration