Ataki SQL Injection znane są od wielu lat. O dawna wiadomo także, jak im zapobiegać. Mogłoby się wydawać, że nie powinny obecnie stanowić większego problemu. Jednak badania pokazują, że wciąż tego typu ataki są wykorzystywane do forsowania zabezpieczeń aplikacji. Odkrywane są również nowe podatności związane z tym zagrożeniem.
Według OWASP Top Ten, ataki typu Injection plasują się na trzecim mijescu spośród największych zagrożeń [1]. Wzrasta także liczba znanych podatności z tym związanych [2].
Przy tworzeniu aplikacji kluczowe jest poświęcenie odpowiednio dużo uwagi kwestiom bezpieczeństwa. Jest to istotne już od samego początku projektu, podczas tworzenia architektury całego rozwiązania. Tym bardziej, że omawiane ataki stanowią poważne zagrożenie mogące nieść za sobą fatalne skutki. W ten sposób można uzyskać nieautoryzowany dostęp do aplikacji, pobrać wszystkie dane z bazy, a nawet całkowicie przejąć kontrolę nad systemem. Przyjrzyjmy się zatem w jaki sposób przeprowadzane są tego typu ataki, jak wykrywać luki w zabezpieczeniach i w jaki sposób zabezpieczać aplikacje przed tymi zagrożeniami.
Źródło: https://xkcd.com/327
Czym jest SQL Injection?
SQL Injection jest wstawieniem, za pomocą danych wejściowych, w pełni poprawnego fragmentu kodu SQL do logiki aplikacji, aby wymusić pożądaną odpowiedź z bazy. Jeśli dane wejściowe nie zostaną prawidłowo oczyszczone, wówczas może dojść do wykonania, przez silnik bazy danych, wstrzykniętego kodu.
Przez dane wejściowe najczęściej rozumiemy parametry przekazywane za pomocą metod GET lub POST (np. z formularza). Jednak mogą być do tego celu wykorzystywane także np. nagłówki HTTP Cookie lub HTTP User-Agent (w zależności od aplikacji i podatności).
Załóżmy, że mamy aplikację z następującym adresem URL:
http://192.168.10.10/products?cat_id=1
Adres ten niech zwraca listę produktów dla wskazanej kategorii wykorzystując następujące zapytanie SQL:
SELECT * FROM products WHERE category_id=cat_id AND active=1
Więc w przypadku powyższego adresu URL skutkuje to następującym zapytaniem:
SELECT * FROM products WHERE category_id=1 AND active=1
Powyższe zapytanie zwraca listę produktów dla kategorii, której id wynosi 1 oraz które spełniają warunek active = 1 , co ogranicza produkty tylko do tych, które są oznaczone, jako aktywne.
Przeczytaj również: Autoryzacja przez Facebook, Google, GitHub.
Jeśli jednak zmodyfikujemy powyższy adres URL w następujący sposób:
http://192.168.10.10/products?cat_id=1–
Poskutkuje to wykonaniem następującego zapytania SQL (w przypadku, gdy dane wejściowe nie zostaną prawidłowo oczyszczone):
SELECT * FROM products WHERE category_id = 1– AND active=1
Ponieważ — oznacza komentarz w SQL, to całość zapytania za komentarzem nie zostanie wykonana. W związku z tym, tak spreparowane zapytanie zwróci wszystkie produkty dla kategorii 1 (niezależnie od wartości active ).
Zmodyfikujmy ponownie nasz URL:
http://192.168.10.10/products?cat_id=1+OR+1=1–
Po przekazaniu parametrów do zapytania SQL otrzymamy:
SELECT * FROM products WHERE category_id=1 OR 1=1– AND active=1
Ponieważ warunek category_id = 1 OR 1=1 zwraca zawsze true , więc całe zapytanie zwróci wszystkie produkty, niezależnie od kategorii i wartości active .
To są najprostsze przykłady wykorzystania SQL Injection, pokazują jednak w jaki sposób w niezabezpieczonej aplikacji można pobrać dane, które nie powinny być dostępne. W rzeczywistości wykorzystywanych jest wiele różnych technik pozwalających na uzyskanie nieautoryzowanego dostępu do aplikacji lub danych.
Nieautoryzowany dostęp do aplikacji
Powszechnym wykorzystaniem SQL Injection jest uzyskanie nieautoryzowanego dostępu do aplikacji. Jest to typowa cecha bardzo często opisywana w literaturze dotyczącej cyberbezpieczeństwa.
Załóżmy, że do uwierzytelnienia wykorzystywane są dwa parametry email i password , które wprowadzane będą przez użytkownika w formularzu logowania i przesyłane metodą POST.
Załóżmy także, dla uproszczenia, że do uwierzytelniania użytkowników wykorzystywane jest następujące zapytanie SQL:
sql = „SELECT * FROM users WHERE email='” + email + „’ AND password='” + password + „'”
Oczywiście, jest to bardzo uproszczony scenariusz, a przy tym szczególnie niebezpieczny, bo nie zakłada szyfrowania hasła, jednak na potrzeby przykładu jest wystarczający. Co więcej, jak się za chwilę okaże, sam sposób przechowywania i porównywania hasła w tym przypadku traci na znaczeniu, bo atak SQL Injection zakłada nieznajomość hasła.
Jeżeli przesłane zostaną następujące wartości parametrów:
email = jan.kowalski@example.com
password = 'admin’
wówczas zapytanie wykonywane przez silnik bazy danych przyjmie następującą postać:
SELECT * FROM users WHERE email=’jan.kowalski@example.com’ AND password=’admin’
Jeżeli warunek w sekcji WHERE zostanie spełniony, to nastąpi uwierzytelnienie użytkownika.
Załóżmy jednak, że nie znamy hasła tego użytkownika. Zmodyfikujmy więc wartość przesyłanego parametru email w następujący sposób:
jan.kowalski@example.com’–
W takim przypadku zapytanie przesłane do bazy przyjmie następującą postać:
SELECT * FROM users WHERE email=’jan.kowalski@example.com’–’ AND password=”
Zwróćmy uwagę, że wprowadzenie sekwencji — spowoduje potraktowanie pozostałej części zapytania jako komentarz i hasło w ogóle nie będzie sprawdzane.
Atak SQL Injection może być przeprowadzony także wtedy, gdy nie znany jest nawet adres email użytkownika. Rozważmy przypadek, gdy jako parametr email prześlemy następującą wartość:
’ OR 1=1–
Wówczas zapytanie przesłane do bazy wyglądać będzie w taki sposób:
SELECT * FROM users WHERE email=” OR 1=1–’ AND password =”
Powyższy warunek WHERE zawsze będzie spełniony i zapytanie zwróci wszystkie rekordy z tabeli users . W wielu przypadkach umożliwi to zalogowanie na konto pierwszego zwróconego użytkownika.
Chcesz sprawdzić bezpieczeństwo swojej aplikacji?
{Audyt bezpieczeństwa aplikacji webowych}
Systemy informatyczne wymagają stałego monitorowania i powinny podlegać okresowym kontrolom bezpieczeństwa IT
Taki atak niesie za sobą katastrofalne skutki, dając nieuprawnionej osobie dostęp do chronionych zasobów. Równocześnie, zabezpieczenie aplikacji przed wspomnianymi działaniami jest stosunkowo proste, dlatego tak ważne jest stosowanie odpowiednich technik wytwarzania oprogramowania i zabezpieczenie programu już na poziomie tworzenia jej architektury.
Operator UNION – pobieranie danych z innej tabeli
Do tej pory analizowaliśmy przykłady, gdy, poprzez odpowiednie wstrzyknięcie fragmentu SQL, modyfikowaliśmy zapytanie operując jednak na tabeli, która była w oryginalnym zapytaniu. SQL Injection umożliwia jednak pobranie danych także z innych tabel, co czyni ten rodzaj podatności jeszcze bardziej niebezpiecznym.
Do pobrania danych z innej tabeli wykorzystywany jest operator UNION . Operator ten umożliwia wykonanie dodatkowego zapytania SELECT , które jest dodawane do pierwotnego zapytania.
Powróćmy do naszego wcześniejszego przykładu, gdzie oryginalne zapytanie zwracało listę aktywnych produktów dla danej kategorii. Tylko przyjmijmy, dla uproszczenia, że zapytanie zwraca tylko dane z dwóch kolumn name i description
SELECT name, description FROM products WHERE category_id=1 AND active=1
Jeżeli zamiast id kategorii do powyższego zapytania wstawimy:
1 UNION SELECT email, password FROM users —
otrzymamy wówczas zapytanie:
SELECT name, description FROM products WHERE category_id=1 UNION SELECT email, password FROM users — AND
active=1
W takim przypadku razem z nazwami i opisami produktów otrzymamy również adresy email użytkowników i ich hasła.
Nawet, jeżeli hasła są zahashowane z wykorzystaniem funkcji skrótów kryptograficznych (co jest standardem), to uzyskane hashe również mogą być wektorem ataku i także (w zależności od rodzaju funkcji skrótu) mogą doprowadzić do uzyskania haseł.
W naszym przykładzie przyjęliśmy jednak pewne uproszczenia. Po pierwsze, założyliśmy, że znamy liczbę kolumn w zapytaniu SELECT , a po drugie, założyliśmy, że znamy strukturę bazy danych, a przynajmniej wiemy o istnieniu tabeli users .
Nie jest to jednak większym problemem dla potencjalnego napastnika. Bez problemu, modyfikując zapytanie, można sprawdzić, jaka jest wymagana liczba kolumn w zapytaniu. Nazwę tabeli można odgadnąć w zależności od tego, jakie dane są poszukiwane. Co więcej, przy nieprawidłowo zabezpieczonej bazie danych, można, również za pomocą UNION , uzyskać wiedzę o zastosowanym silniku bazy danych. Na tej podstawie, wiedząc, jak w danym rodzaju bazy danych przechowywane są informacje o tabelach, można pobrać kompletny schemat bazy danych (także wykorzystując UNION ). Mając te informacje można pobrać całą bazę danych! Dlatego ten rodzaj ataku jest tak bardzo niebezpieczny.
W tym artykule skupiamy się wokół tego, jak istotne jest odpowiednie zabezpieczenie aplikacji przed atakami SQL Injection oraz jak wykrywać tego typu zagrożenia w tworzonych rozwiązaniach. Dlatego nie analizujemy dogłębnie wszystkich możliwości wykorzystania operatora UNION , a jedynie wskazujemy jak ważne jest przyjęcie właściwych standardów kodowania i na co zwracać uwagę podczas realizacji projektów.
Stacked queries – przejęcie kontroli
Ataki SQL Injection z wykorzystaniem techniki „Stacked queries” umożliwiają przejęcie kontroli nad bazą danych. Polega na dodaniu kolejnego zapytania do wcześniejszego. W tym przypadku wiele zależy od konkretnego silnika bazy danych, ponieważ nie każdy rodzaj bazy danych wspiera tego typu operacje.
W kontekście baz danych umożliwiających takie zapytania, wykorzystanie tej cechy, przy podatności aplikacji na ataki SQL Injection, może prowadzić do bardzo niebezpiecznych sytuacji.
Wracając do naszego przykładu z listą produktów, jeżeli jako wartość parametru określającego id kategorii zostanie przesłana następująca wartość:
1; DELETE FROM products
wówczas zapytanie przybierze następującą postać:
SELECT * FROM products WHERE category_id=1; DELETE FROM products
W takim przypadku po zapytaniu SELECT zostanie wywołane zapytanie DELETE usuwające wszelkie rekordy z tabeli products .
Omawiana podatność może być wykorzystana także do zmiany danych – za pomocą zapytań typu UPDATE .
Chcesz zrealizować bezpieczną aplikację webową?
Szukasz wykonawcy, który jakość kodu stawia na pierwszym miejscu?
Zapraszamy do bezpłatnej konsultacji – porozmawiamy i sprawdzimy, czy możemy w tym pomóc.
Zapobieganie atakom SQL Injection
Powyższe przykłady to tylko niektóre z technik możliwych do wykorzystania w atakach typu SQL Injection. Pokazują jednak skalę zagrożenia oraz wskazują, jak można identyfikować omawiane luki bezpieczeństwa.
Dlatego też niezwykle istotne jest prawidłowe zabezpieczenie aplikacji przed omawianymi podatnościami. Kluczowa jest, przede wszystkim, sanityzacja danych wejściowych. Wszystkie informacje wprowadzane do programu powinny być traktowane jako potencjalne zagrożenie.
Należy wystrzegać konstruowania zapytań SQL poprzez łączenie stringów, do których wstawiane są dane przesłane przez użytkownika. Jest to najkrótsza droga do wprowadzenia luki umożliwiającej przeprowadzenie ataku typu SQL Injection.
Zamiast tego, generując dynamiczne zapytania do bazy, należy korzystać z parametryzowanych zapytań, gdzie zmienne przekazywane są jako parametry. Dzięki czemu spreparowane dane wejściowe nie są traktowane jako część zapytania, nawet jeśli zawierają pełnoprawny fragment SQL. Ponadto, można korzystać z procedur składowanych, co również zapobiega wstrzyknięciu złośliwego kodu SQL.
Konieczne jest także odpowiednie zabezpieczenie na poziomie bazy danych. Absolutnym minimum jest wdrożenie zasady najmniejszych uprawnień. W takim przypadku użytkownik bazodanowy, za pomocą którego łączy się aplikacja, powinien mieć nadane tylko te uprawnienia, które są niezbędne do prawidłowego działania aplikacji.
Należy również zadbać o to, żeby komunikaty błędów nie ujawniały szczegółów aplikacji i zapytania bazodanowego. Zamiast tego należy dać ogólny komunikat informujący o wystąpieniu błędu.
To może Cię zainteresować: Hasła: Poznaj ich znaczenie oraz sposoby przechowywania.
Warto pomyśleć o wykorzystaniu systemów typu WAF (Web Application Firewall), które pomagają odfiltrować zapytania URL zawierające potencjalnie niebezpieczne ciągi znaków.
Z całą pewnością, przy realizacji aplikacji, powinno się korzystać z nowoczesnych rozwiązań stawiających w centrum uwagi kwestie bezpieczeństwa. Zastosowanie sprawdzonych technologii minimalizuje ryzyko popełnienia błędu, ponieważ zazwyczaj domyślnie zaimplementowane są w nich funkcje zapobiegające atakom typu SQL Injection.
Przykładowo Framework Laravel ma wbudowane mechanizmy zarówno do budowania zapytań (query builder), jak i bardzo wydajny ORM. Oba te mechanizmy bardzo skutecznie chronią przed wstrzyknięciem kodu SQL. Oczywiście, pod warunkiem ich prawidłowego wykorzystania. W Laravel również można wykonać czyste zapytanie SQL (raw query), które nie gwarantuje odporności na SQL Injection (przed czym ostrzega nawet oficjalna dokumentacja frameworka).
Wynika z tego, że samo zastosowanie bezpiecznych technologii jest niewystarczające, jeżeli nie idzie w parze z odpowiednimi standardami tworzenia kodu źródłowego i właściwym użyciem narzędzi oferowanych przez framework. Powinien być przyjęty jeden spójny sposób dostępu do danych dla projektu.
Podsumowując, ataki typu SQL Injection stanowią duże zagrożenie, jednak powszechnie znane są sposoby zabezpieczenia przed tymi zagrożeniami. Zapobieganie powstawaniu omawianych podatności jest stosunkowo proste w implementacji, dlatego tak ważna jest znajomość prawidłowego wdrożenia mechanizmów minimalizujących powstawanie tego typu luk w aplikacji.
Tworzysz aplikację webową i priorytetem jest dla Ciebie wysoka jakość kodu oraz bezpieczeństwo?
Umów się na bezpłatną konsultację. Sprawdźmy razem, jak możemy wesprzeć Twój projekt.