BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Tajemnice SQL Injection

27-11-2005 11:52 | User 83896
Artykuł opisuje, jakiego typu atakami są ataki typu SQL Injection, jak rozpoznawać narażone na takie ataki miejsca w kodzie w naszej aplikacji i jak im przeciwdziałać. Artykuł został napisany w kontekście technologii .NET Framework i SQL Server 2000, jednak poruszane tutaj problemy mogą zostać przeniesione na dowolną inną platformę, na której takie ataki występują.

SQL Injection

Artykuł opisuje, jakiego typu atakami są ataki typu SQL Injection, jak rozpoznawać narażone na takie ataki miejsca w kodzie w naszej aplikacji i jak im przeciwdziałać. Artykuł został napisany w kontekście technologii .NET Framework i SQL Server 2000, jednak poruszane tutaj problemy mogą zostać przeniesione na dowolną inną platformę, na której takie ataki występują.

 

Co to jest SQL Injection?

Każda poważna aplikacja posiada trójwarstwową architekturę: GUI-API-DataLayer. Warstwę danych (DataLayer) stanowi w 95 % baza danych. Przechowuje ona dane użytkowników, uprawnienia, informacje finansowe i biznesowe, oraz wiele innych ważnych danych. Dlatego tak ważne jest, aby dane były bezpieczne.

Pewnie niewielu z Was wie, co oznacza termin SQL Injection. Jest to atak, którego źródłem są niewalidowane dane wejściowe użytkownika aplikacji, które mogą być pobrane przez system np. z formularza WWW. Celem ataku SQL Injection jest wydobycie poufnych informacji z bazy danych i zakłócenie jej prawidłowego działania. Osobiście wyróżniam się dwa typy ataków SQL Injection:

  • Ataki, których rezultat, jest widoczny natychmiastowo;
  • Ataki, które powodują wstrzyknięcie infekujących danych do bazy danych, a których rezultat jest widoczny dopiero po pewnym czasie.

Artykuł omówi oba rodzaje ataków.

 

Przyjęta konwencja.

Przykładowe ataki będą przedstawione na przykładzie prostej aplikacji płatniczej. Aplikacja pozwala na analizowanie transakcji kartami płatniczymi przez użytkowników aplikacji. Użytkownik po podaniu swojego loginu i hasła loguje się do usługi, która wyświetla tabelkę z numerem karty kredytowej oraz sumą pieniężną, która w wyniku określonej transakcji została przelana na lub pobrana z konta płatniczego danego dnia. Formularz logowania składa się z dwóch kontolek tbUserName i tbUserPass typu TextBox oraz przycisku Zatwierdź. Proszę nie analizować sensu zastosowania takiej aplikacji, gdyż została ona stworzona tylko do celów poglądowych omówienia problemu bezpieczeństwa aplikacji internetowych.

Model bazy danych został przedstawiony na rysunku poniżej. Jak widać, baza danych jest bardzo prosta ale przechowuje dane ściśle poufne.

model.jpg

Rys. 1 - Model danych aplikacji

 

Aplikacja pobiera dane z bazy danych za pomocą obiektu SqlDataAdapter. Jak później pokażę, podejście to umożliwia łatwe atakowanie aplikacji, a jest dość często stosowane przez programistów. Założyłem, że moduł odpowiedzialny za pobieranie informacji z bazy danych korzysta z pewnej uniwersalnej metody SelectRows, która zwraca obiekt DataSet z rekordami pobranymi w wyniku zapytania.

public DataSet SelectRows(string connection, string query)

{

       SqlDataAdapter adapter = new SqlDataAdapter(query,connection);

       DataSet ds = new DataSet();

       adapter.Fill(ds);

return ds;

}

Ataki na aplikację zostaną przeanalizowany w kontekście logowania użytkownika do usługi

 

Ataki pierwszego typu…

Jak można atakować aplikację?

Prześledźmy proces autoryzacji użytkownika. Autoryzacja użytkownika systemu odbywa się poprzez prostą formatkę, gdzie należy podać login i hasło. Po naciśnięciu przycisku Zatwierdź, dane z formatki są wysyłane do strony serwerowej aplikacji. Tam tworzone jest zapytanie do bazy danych, które sprawdza, czy użytkownik o podanej nazwie i haśle istnieje. Jeśli tak, zwracane są dane tego użytkownika z tablicy TRANSACTIONS.

Proces autoryzacji może przebiegać w następujący sposób:

(1)

string query = "SELECT * FROM USERS ”+

“WHERE UserName = ‘” + tbUserName.ToString() + ”’ AND UserPass=’” + tbUserPass.ToString() + ”’”;

DataSet users = SelectRows (connection,query);

tbUserPass i tbUserPass to kontrolki służące do wprowadzania loginu i hasła użytkownika usługi.

Jeśli obiekt users posiada dokładnie jeden rekord, oznacza to, że autoryzacja użytkownika powiodła się i należy wyświetlić jego informacje o transakcjach. Do pobrania informacji o transakcjach tworzone jest zapytanie:

(2)

string query = "SELECT TransDate, CreditCardNum, TransIncome "+

    "FROM TRANSACTIONS " +

    "WHERE UserName = '"+userName';

DataSet trans = SelectRows (connection,query);

Użytkownikowi prezentowane są wszystkie rekordy obiektu trans.

Na pierwszy rzut oka wszystko działa poprawnie. Jeśli przykładowo istnieje użytkownik <login=Jacek, hasło=ala>  i wprowadzi on swoje dane w formatce logowania, klauzula WHERE w zapytaniu autoryzacji (1) będzie miała postać:

“WHERE UserName = ‘Jacek’ AND UserPass = ’ala’”;

Ale co się stanie, jeśli użytkownik wprowadzi niepoprawne dane?

Pierwszy atak, zakłada, że użytkownik wprowadzi dane następujące: <login=’ OR 1=1;-- , hasło=>. Wówczas klauzula WHERE w zapytaniu autoryzacji (1) przyjmie postać:

“WHERE UserName = ‘’ OR 1=1;--’ AND UserPass = ’’”;

Znacznik “--“ w zapytaniu SQL jest przez SQL Server 2000 interpretowany jako polecenie ignorowania wszystkiego, co po takim znaczniku występuje. Ponieważ w klauzuli WHERE występuje alternatywa, której człon ‘1=1’ jest zawsze prawdziwy, oznacza to, że zapytanie zwróci wszystkie rekordy z tablicy USERS (!). Zatem użytkownikowi udało się wprowadzić błędne dane (bez hasła), które przez aplikację zostały zinterpretowane jako poprawne. Na szczęście, jeżeli obiekt users posiada więcej niż jeden rekord, nie zostanie wyświetlona lista transakcji dla użytkownik (warunek pobrania obiektu trans). Jest to pewne zabezpieczenie, ale czy wystarczające?

Drugi atak, zakłada, że atakujący jest bardziej przebiegły i w formatce logowania może wpisać: <login=’ OR ID = 1;--, hasło=’’>. Wykonanie tego zapytanie spowoduje, zwrócenie dokładnie jednego rekordu w obiekcie users, a więc pociągnie za sobą wyświetlenie obiektu trans z transakcjami dla użytkownika o identyfikatorze 1. Jedynym ograniczeniem, przy takim scenariuszu działania jest to, że atakujący nie zna nazw kolumn tablicy USERS, w szczególności kolumny ID. Znalezienie nazwy kolumn, może jednak nastąpić najłatwiejszą metodą poszukiwania – metodą prób i błędów. Dodatkowo, niektóre serwisy internetowe wyświetlają całkowity kod błędu bazy danych, w którym są informacje, w jakiej tabeli i w wyniku operacji, na jakiej kolumnie wystąpił wyjątek. Niektórzy programiści nie myślą o konsekwencji, dostania się takiej informacji w niepowołane ręce.

Trzeci atak, pokazuje jak łatwo można usunąć tabelę USERS z całą jej zawartością. Wystarczy w pole login wpisać:

login:’; DROP TABLE users;

 

Uprawnienia dla dostępu do bazy danych

Niektóre aplikacje bazodanowe używają kont administracyjnych, do zarządzania aplikacją a głównie bazą danych z najwyższego poziomu administratora. Atakujący, któremu uda się podszyć pod administratora, ma nieograniczone możliwości manipulowania danymi. Poniższy przykład ataku zakłada, że aplikacja została uruchomiona z prawami dla sysadmin, db_dbowner lub db_dlladmin.

Pierwsza próba logowanie polega na stworzeniu tabeli, przechowującej dane. Pole hasło pozostaje puste, w polu login wpisujemy:

login: ‘ OR ID=1; CREATE TABLE mojaTabela( dysk varchar(31), pojemnosc int);

       INSERT INTO mojaTabela EXEC master..xp_fixeddrives;--

Powyższa komenda powoduje stworzenie tabeli do przechowania nazw dysków serwera z ich pojemnością w MB. W komendzie autoryzacyjnej zwróci jeden rekord użytkownika o identyfikatorze 1 i spowoduje wywołanie komendy (2), gdzie stworzy tabelę mojaTabela. Teraz wystarczy wypełnić tabelę danymi, postępując analogicznie:

login: ‘ OR ID=1; SELECT * FROM mojaTabela;--.

W tym momencie po zalogowaniu do usługi zobaczymy listę dysków z ich pojemnością w MB. Kolejna komenda pozwoli zobaczyć, co znajduje się na jednym z dysków z listy (załóżmy, że na liście jest dysk c). Należy ponownie spróbować się zalogować i wpisać:

login:’; CREATE TABLE mojaTabela2(dane varchar(127) NULL);

       INSERT INTO mojaTabela2 EXEC master..xp_cmdshell ‘dir c:\’;--

Przykład pokazuje, że ważne jest, aby aplikacja była uruchamiana z najmniejszą ilością przywilejów, niezbędnych do jej poprawnego działania. Zalecam, aby podczas instalacji SQL Server 2000, umożliwić dostęp tylko do określonych zasobów. Wówczas wywołanie xp_cmdshell nic poważnego zrobić nie powinno.

 

Ataki drugiego typu…

Atakami drugiego typu nazywam takie ataki, których rezultat jest widoczny dopiero po jakimś czasie, w wyniku wykonania pewnej akcji na bazie danych.

Poniższy przykład pokazuje, w jaki sposób takie ataki działają. Załóżmy, że usługa pozwala zmienić hasło użytkownikowi, np. poprzez wywołanie polecenia:

UPDATE USERS SET UserPass = ‘newPass’,

gdzie newPass to wartość pobrana z kontrolki TextBox. Jeśli użytkownik wpisze w polu nowego hasła ‘DROP TABLE TRANSACTIONS;--, podczas wywołania komendy, która będzie korzystała z tej informacji np. SELECT FROM users WHERE UserPass=’’ DROP TABLE TRANSACTIONS;--, spowoduje nieodwracalne usuniecie tablicy users!

 

Jak walczyć z SQL Injection?

Istnieje wiele metod walki z SQL Injection. Najważniejsze i najbardziej znane opiszę poniżej.

§   Walidacja danych wprowadzanych przez użytkownika

Jest to najprostszy sposób walki z SQL Injection, jednak nie zawsze wystarczający, a niekiedy niemożliwy do zastosowania. Polega na sprawdzaniu poprawności składniowej danych wprowadzanych przez użytkownika. Możliwe jest rozwiązanie poprzez stosowanie walidujących skryptów np. JavaScript po stronie klienta, jednak po stronie serwera, trzeba powtórzyć walidację. Po stronie serwera istnieje możliwość walidowania danych wprowadzanych do kontrolek TextBox, poprzez stosowanie obiektów typu Validator. Niestety takie rozwiązanie staje się kompletnie nieefektywne, gdy użytkownik może wprowadzić dowolny ciąg znaków.

§   Ograniczenie długości wprowadzanych danych

Aby uniemożliwić „wstrzykiwanie” do bazy danych niebezpiecznego kodu, można ograniczyć możliwą do wprowadzenia długość danych dla kontrolki TextBox. Często jednak, kontrolki muszą pozwolić na wprowadzanie wielu znaków, np 255. W takim przypadku i takie rozwiązanie staje się nieefektywne.

§   Parametryzowane wywołanie zapytań SQL

SQL Server i technologia .NET Framework, daje możliwość wywoływania zapytań na bazie danych, które nie pobierają bezpośrednio danych wpisanych do kontrolek TextBox jako parametrów, tylko jawnie wyspecyfikowane parametry. Takie rozwiązanie eliminuje możliwość „wstrzyknięcia” niebezpiecznego kodu do aplikacji. Poniższy kod pokazuje. w jaki sposób sparametryzować komendę wykorzystywaną podczas procesu autoryzacji użytkowników usługi.

string commandText = "SELECT COUNT(*) FROM Users "+

    "WHERE UserName=@userName AND UserPass=@userPass";

SqlCommand cmd = new SqlCommand(commandText, conn);

cmd.Parameters.Add("@userPass ", tbUserPass.ToString());

cmd.Parameters.Add("@userName ", tbUserName.ToString());

 

§   Wykorzystywanie procedur składowych bazy

Najbardziej elegancką i bezpieczną metodą wywoływania zapytań na bazie danych jest napisanie skryptów bazy danych i wywołanie ich w kodzie aplikacji. Rozwiązanie to jest równie poprawne jak parametryzowane wywoływanie zapytań SQL a dużo bardziej czytelne i łatwiejsze w zarządzaniu.

§   Ograniczenie praw dostępu do bazy danych

Aby uniemożliwić użytkownikowi korzystanie z wbudowanych komend np. xp_cmdshell należy uruchomić bazę danych z możliwie najmniejszą ilością uprawnień.

§   Ograniczenie informacji o błędzie prezentowanej użytkownikowi

Aby użytkownik nie był informowany o błędach po stronie bazy danych należy ograniczyć wyświetlanie informacji o błędach/wyjątkach do minimum lub podawać tylko kod błędu a w jakichś dodatkowych zasobach udostępnić opisy tych kodów. Takie rozwiązanie ograniczy możliwość uzyskania informacji o nazwach tabel lub nazwach kolumn osobom trzecim.

 

Podsumowanie

Artykuł opisał, w jaki sposób rozpoznawać i jak walczyć z atakami SQL Injection. Ataków SQL Injection nie można lekceważyć, gdyż mogą narobić wielu szkód w naszej aplikacji. Najlepszym rozwiązaniem jest stosowanie procedur składowych podczas odwoływania się do zasobów bazy danych. Jeżeli przechowujemy w bazie danych bardzo poufne dane, np. numery kart kredytowych użytkowników należy pomyśleć o ich szyfrowaniu (np. algorytmem MD5). Pozostałe sposoby zabezpieczeń wynikają z indywidualnego charakteru aplikacji.

tagi: SQL

Komentarze 0

pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.