Treść dokumentu
Luka w zabezpieczeniach bazy danych adresów
Aby zwiększyć bezpieczeństwo w programie Dreamweaver 8.0.2 i CS3, usunęliśmy możliwość używania dynamicznych parametrów SQL w standardowych zestawach rekordów. Oryginalny kod narażał bazy danych na wstrzykiwanie kodu SQL. Korzystanie z przygotowanych instrukcji dla modeli serwerów ASP_VBS i ColdFusion pomaga w naprawie luki w zabezpieczeniach. Po zastosowaniu pierwszego zestawu rekordów konieczne było dodanie funkcji do strony, a następnie wywołanie tej funkcji ze wszystkich zestawów rekordów na stronie dla PHP_MySQL. Technika ta jest podobna do przygotowanych instrukcji ASP_VBS i ColdFusion.
W celu zamknięcia luki w zabezpieczeniach zrezygnowaliśmy z elastyczności na rzecz bezpieczeństwa. Chcemy dać możliwość edycji w programie Dreamweaver niestandardowych zestawów rekordów programistom, którzy już używają lub planują zaprojektować rozszerzenia wymagające dynamicznych zapytań SQL oraz użytkownikom końcowym chcącym dostosować swoje zestawy rekordów, aby akceptowały dynamiczne parametry.
Wstrzykiwanie kodu SQL i metody zapobiegania
Wstrzykiwanie kodu SQL to technika wykorzystująca lukę w zabezpieczeniach warstwy bazy danych aplikacji. Luka występuje, gdy dane wejściowe użytkownika są niepoprawnie filtrowane pod kątem ciągów alfabetycznych znaków ucieczki osadzonych w instrukcjach SQL lub dane wejściowe użytkownika nie są silnie typizowane, przez co następuje nieoczekiwane wykonywanie. Jest to w rzeczywistości bardziej ogólna klasa luk, które mogą wystąpić, gdy jeden język programowania lub skryptowy jest osadzony w innym. Więcej informacji można znaleźć w Wikipedii.
Przykład standardowej kwerendy SQL:
SELECT * FROM company_com WHERE name_com = '$companyName'
W powyższym przykładzie zmienna $companyName jest odczytywana z pola formularza danych wejściowych, więc użytkownik ma całkowitą kontrolę nad przesyłaną wartością. W najlepszym przypadku, gdy użytkownik wpisze „Adobe” (bez cudzysłowów) w danych wejściowych odpowiadających nazwie firmy, dana kwerenda SQL będzie wyglądać następująco:
SELECT * FROM company_com WHERE name_com = 'Adobe'
Użytkownicy mogą mieć jednak również na celu uszkodzenie witryny, kradzież poufnych danych i/lub zniszczenie bazy danych. W powyższym przykładzie, wstrzyknięcie kodu SQL jest łatwo osiągalne poprzez wprowadzenie ciągu „blabla' OR '1'='1” (bez otaczających go cudzysłowów). Podając tę sekwencję znaków, użytkownik będzie miał dostęp do wszystkich firm zamiast jednej firmy. Rzeczywiście wykonana kwerenda SQL to:
SELECT * FROM company_com WHERE name_com = 'blabla' OR '1'='1'
Ponadto, jeśli użytkownik chce usunąć całą tabelę company_com, może to zrobić po prostu przekazując ciąg „x'; DROP TABLE company_com; --”(bez otaczających cudzysłowów). Oto pełna kwerenda SQL:
SELECT * FROM company_com WHERE name_com = 'x'; DROP TABLE company_com; --'
Użyj jednego z następujących środków, aby zabezpieczyć witrynę przed wstrzykiwaniem kodu SQL:
- Ogranicz wprowadzanie do minimalnego zestawu dozwolonych znaków, automatycznie usuwając wszystkie inne znaki, które nie mieszczą się w podanym zakresie.
- Unikaj znaków, które mogą powodować problemy, gdy są używane w kwerendzie SQL (na przykład pojedyncze cudzysłowy).
- Używaj przygotowanych instrukcji
- Używaj uprawnień dostępu do bazy danych.
- Używaj przechowywanych procedur.
Zalecamy używanie przygotowanych instrukcji opisanych szczegółowo w sekcji Używanie przygotowanych instrukcji. Programy Dreamweaver 8.0.2 i CS3 używają metody przygotowanych instrukcji dla modeli serwerów ASP_VBS, ASP_JavaScript, Cold Fusion i JSP i metody unikania wprowadzania przez użytkownika dla modelu serwera PHP_MySQL. Więcej informacji na temat ataków wstrzykiwaniem kodu SQL można znaleźć w artykule Przykłady ataków wstrzykiwaniem kodu SQL.
Używaj przygotowanych instrukcji
Programy Dreamweaver 8.0.2 i CS używają przygotowanych instrukcji, ponieważ to rozwiązanie gwarantuje, że zastąpienie parametrów SQL danymi wartościami (które mogły zostać wysłane przez adres URL) nastąpi na serwerze, a nie na stronie. Przygotowane instrukcje gwarantują również, że wartość przekazywana do samej kwerendy SQL ma odpowiedni typ danych i używa odpowiedniego anulowania (jeśli parametr ma być typu int, wówczas użytkownik nie będzie mógł przesłać litery. Jeśli parametr jest typu tekstowego, wówczas cały przekazywany ciąg znaków zostanie poprawnie zmieniony i użytkownik nie będzie miał możliwości wykonania wstrzykiwania kodu SQL).
Przygotowane instrukcje dla modeli serwerów ASP_VBS i ASP_JS
W przypadku modeli ASP_VBS i ASP_JS korzystamy z warstwy bazy danych ADODB. Poniższy przykład pokazuje prosty zestaw rekordów ASP_VBS, który używa dynamicznego parametru SQL do filtrowania wyników:
... <% 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 %> ...Przygotowane instrukcje dla modelu serwera ColdFusion
Model ColdFusion oferuje wbudowane wsparcie dla przygotowanych instrukcji. Poniższy przykład pokazuje prosty zestaw rekordów ColdFusion, który używa dynamicznego parametru SQL do filtrowania wyników:
... <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> ...Symulowane przygotowane instrukcje dla modelu serwera PHP_MySQL
Przygotowane instrukcje dla modelu PHP stały się dostępne w wersji MySQL 4.1, ale ponieważ chcieliśmy również obsługiwać poprzednie wersje MySQL, postanowiliśmy zaimplementować niestandardową funkcję, która robi to samo. Poniższy przykład pokazuje prosty zestaw rekordów PHP, który używa dynamicznego parametru SQL do filtrowania wyników:
... <?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); ?> ...Zezwalaj na parametry dynamiczne w standardowych zestawach rekordów
Jak więc zabezpieczyć się przed wstrzykiwaniem kodu SQL, jednocześnie pozwalając na parametry dynamiczne i umożliwiając edytowanie wynikowego kodu za pomocą panelu Zachowania serwerowe w programie Dreamweaver? Rozwiązaniem jest wstawienie parametrów dynamicznych do samej kwerendy SQL, a nie przekazywanie ich jako klasycznych parametrów SQL (które mogłyby być zmienione lub dołączone do kwerendy SQL za pomocą przygotowanych instrukcji).
Uwaga: Użytkownicy nie mogą wygenerować takiego kodu przy użyciu obiektów i zachowań serwera dostarczanych z programem Dreamweaver 8.0.2 lub CS3 (np. wstawiania zestawu rekordów za pomocą panelu Zachowania serwerowe lub paska Wstaw). To rozwiązanie wymaga ręcznej edycji kodu lub instalacji rozszerzenia innej firmy, które tworzy taki kod. Odpowiedzialność za pisanie bezpiecznego kodu spoczywa na użytkowniku lub twórcy oprogramowania innej firmy.
Celem tej metody jest umożliwienie doświadczonym programistom tworzenia dynamicznych kwerend SQL. Niewłaściwe użycie tego rozwiązania daje hakerowi możliwość uzyskania niepożądanych uprawnień i/lub uszkodzenia stron internetowych oraz bazy danych. Należy zapoznać się z artykułem Wstrzykiwanie kodu SQL i metody zapobiegania, aby zminimalizować ryzyko wstrzyknięcia kodu SQL.
Główne sytuacje, w których to rozwiązanie może być przydatne, to:
- Ręczne tworzenie/aktualizowanie kwerendy SQL w celu akceptacji parametrów dynamicznych.
- Tworzenie/aktualizacja istniejących rozszerzeń w celu tworzenia parametrów dynamicznych.
Oba przypadki są szczegółowo opisane w poniższych akapitach.
Ręczne tworzenie/aktualizowanie kwerendy SQL w celu akceptacji parametrów dynamicznych przy jednoczesnym zachowaniu edytowalności zestawu rekordów w programie Dreamweaver
W poniższym przykładzie użytkownik chce dodać funkcję sortowania do tabeli dynamicznej. Postanawia ponownie załadować stronę, przekazując pliki, i sortować kierunek, który ma zostać użyty (rosnąco lub malejąco) w adresie URL. Przykładowy początkowy adres URL:
http://www.mydomain.com/index.php?sortCol=name_com&sortDir=ascDo osiągnięcia tego celu za pomocą Zachowań serwerowych w programie Dreamweaver jest konieczne ręczne kodowanie. Poniższe przykłady kodu pokazują zmiany, które należy wprowadzić dla każdego modelu serwera.
Uwaga: Pamiętaj, że żaden z poniższych przykładów nie został sprawdzony pod kątem wstrzykiwania kodu SQL. Mają cel edukacyjny i nie powinno się ich wykorzystywać w środowisku produkcyjnym. Przykłady nie są szczelnie zabezpieczone, ponieważ mają być proste. Należy stosować ochronę przed wstrzykiwaniem kodu SQL. Więcej informacji można znaleźć w artykule Wstrzykiwanie kodu SQL i sposoby zapobiegania.
ASP_VBS
Przed:
... <% 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
Przed:
... <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
Przed:
... <?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); ?> ...Tworzenie/aktualizowanie istniejących rozszerzeń w celu tworzenia parametrów dynamicznych przy jednoczesnym zachowaniu edytowalności zestawu rekordów w programie Dreamweaver
Rozważmy teraz ten sam scenariusz, co powyżej, z jednym wyjątkiem. Użytkownik chce utworzyć rozszerzenie, które wygeneruje odpowiedni kod, zamiast polegać na połączeniu wbudowanej funkcjonalności i edycji ręcznej. Nowe rozszerzenie powinno generować poprawny kod dla modeli serwerów ASP_VBS, ColdFusion i PHP_MySQL.
Uwaga: Poniższe sekcje skupiają się tylko na generowaniu kodu używającego wartości z parametrów adresu URL do utworzenia dynamicznej kwerendy SQL. Głównym celem jest wygenerowanie kodu, który zostanie rozpoznany przez program Dreamweaver i który będzie można edytować z poziomu standardowych interfejsów programu Dreamweaver. Ten samouczek nie dotyczy sprawdzania poprawności danych wejściowych (za pomocą parametrów adresu URL) ani nie chroni końcowego kodu SQL przed próbą wstrzyknięcia kodu SQL, ponieważ złożoność takiego kodu wykracza poza zakres tego samouczka. Programista ponosi wyłączną odpowiedzialność za ochronę końcowego kodu SQL przed wstrzyknięciami kodu SQL.
Zapewniamy wersję demo rozszerzenia, które pobiera połączenie i tabelę jako dane wejściowe i generuje dynamiczną tabelę wyświetlającą wszystkie rekordy. Aktualizuje również wygenerowany kod SQL, aby zawierał zmienną orderBy, jak pokazano w powyższych przykładach. Rozszerzenie zostało zaprojektowane dla programu Dreamweaver 8.0.2 i CS3. Wygenerowany kod nie jest całkowicie bezpieczny, bo jego celem jest tworzenie zestawów rekordów zawierających parametry dynamiczne które można edytować za pomocą standardowych interfejsów programu Dreamweaver. Kod źródłowy rozszerzenia znajduje się w Folderze konfiguracji użytkownika.
Do wszystkich poniższych przykładów kodu dodano wyróżnione sekcje, aby umożliwić sortowanie za pomocą parametrów URL sortCol i sortDir.
ASP_VBS
Kod generujący odpowiednie zapytanie SQL dla modelu serwera ASP_VBS znajduje się w pliku „[USER_CONFIGURATION_FOLDER]/Commands/My Dynamic Table.js” w Folderze konfiguracji użytkownika. Znacząca zmiana dotyczy wiersza 130:
... 130: paramObj.encodedSQL = "SELECT * FROM " + paramObj.table + " \" & orderBy & \""; ...Plik uczestników dotyczący zachowania serwera MyDynamicTable ASP_VBS zawiera dodatkowego uczestnika, który dodaje do strony definicję zmiennej „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
Ten sam plik „[USER_CONFIGURATION_FOLDER]/Commands/My Dynamic Table.js” w Folderze konfiguracji użytkownika zawiera też kod dla modelu serwera ColdFusion; odpowiednia zmiana znajduje się w wierszu 138:
... 138: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table + " #orderBy#"; ...Plik uczestników dotyczący zachowania serwera MyDynamicTable ColdFusion zawiera dodatkowego uczestnika, który dodaje do strony definicję zmiennej „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
Ostatni model serwera w rozszerzeniu to PHP_MySQL. Odpowiednia zmiana w pliku „[USER_CONFIGURATION_FOLDER]/Commands/My Dynamic Table.js” w Folderze konfiguracji użytkownika dotyczy wiersza 122:
... 122: paramObj.SQLStatement = "SELECT * FROM " + paramObj.table+ " \" . $orderBy . \""; ...Plik uczestników dotyczący zachowania serwera MyDynamicTable PHP_MySQL zawiera dodatkowego uczestnika, który dodaje do strony definicję zmiennej „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>Zalety
Opisana powyżej metoda ma kilka zalet, w tym:
- Bardziej doświadczeni programiści mogą skorzystać z podatności na wstrzykiwanie kodu SQL, aby dynamicznie tworzyć bardziej złożony kod SQL.
- Utworzone zestawy rekordów nadal będzie można edytować w programie Dreamweaver.
- Integracja z istniejącymi rozszerzeniami Dreamweaver, które generują zestawy rekordów z dynamicznymi parametrami SQL, nie jest trudna.
- Domyślny kod generowany przez program Dreamweaver pozostaje niezmieniony. Programiści, którzy nie potrzebują dynamicznych zapytań (i ich klienci), mogą być pewni, że kod napisany przez program Dreamweaver jest bezpieczny.
Wady
Ponieważ program Dreamweaver nie został domyślnie zaprojektowany do obsługi tej metody, ma ona również pewne wady, w tym:
- Kod wygenerowany przy użyciu metody opisanej w tym dokumencie jest podatny na wstrzykiwanie kodu SQL i należy go chronić na inne sposoby. Takie metody ochrony to między innymi:
- Upewnienie się, że użytkownik końcowy nie może wstrzyknąć kodu SQL, aby uszkodzić stronę i/lub bazę danych;
- Anulowanie argumentów, zanim dotrą do kodu SQL;
- Upewnienie się, że jeśli użytkownik złamie kod, nie będzie można wykraść poufnych danych ani uszkodzić bazy danych.
- Dwa znane problemy, których nie można naprawić przed wydaniem kolejnej wersji programu Dreamweaver:
- ColdFusion: Prosty interfejs użytkownika zestawu rekordów usuwa skróty wokół nazw zmiennych z zapytania SQL po edycji i ponownym zastosowaniu
- ColdFusion, ASP_VBS, ASP_JS: przycisk testowy z interfejsu zaawansowanego zestawu rekordów nie działa, gdy zapytanie SQL zawiera nazwy zmiennych
Znajdowanie folderu konfiguracji użytkownika
- Windows XP
C:\Documents and Settings\[nazwa użytkownika]\Application Data\Adobe\Dreamweaver 9\Configuration
- Windows Vista
C:\Users\[nazwa użytkownika]\AppData\Roaming\Adobe\Dreamweaver 9\Configuration
- Mac OS 10.4.x
/Users/[nazwa użytkownika]/Library/Application\ Support/Adobe/Dreamweaver\ 9/Configuration