e-Informatica Software Engineering Journal Filtrowanie zapytań SQL w środowisku aplikacji internetowych

Filtrowanie zapytań SQL w środowisku aplikacji internetowych

Krzysztof Ciebiera
Instytut Informatyki,  Uniwersytet Warszawski
ciebie@mimuw.edu.pl
Streszczenie

W rozdziale przedstawione zostały wybrane zagrożenia związane z bezpieczeństwem funkcjonowania serwisów WWW opartych na bazach SQLowych. Została również zaprezentowana nowa metoda pozwalająca na unikanie tych zagrożeń poprzez dokładną specyfikację danych dostępnych dla użytkowników w specjalnie do tego celu stworzonym języku SQLshield.

Wstęp#

Jednym z problemów występujących podczas tworzenia serwisów komputerowych jest konieczność zabezpieczenia ich przed atakami włamywaczy komputerowych. Ponieważ protokół HTTP jest protokołem bezstanowym, więc część logiki aplikacji zazwyczaj zostaje przeniesiona na stronę klienta. Włamywacze często wykorzystują to, że programiści, przyzwyczajeni do pisania aplikacji jednowarstwowych, nie zabezpieczają się przed atakami polegającymi na manipulowaniu danymi znajdującymi się po stronie przeglądarki internetowej. Podczas tworzenia serwisów WWW często korzysta się z SQLowych baz danych jako źródła informacji wyświetlanych przez serwis, co daje włamywaczom dodatkowe możliwości, polegające na manipulowaniu zapytaniami SQLowymi przekazywanymi do bazy danych.Do najczęściej występujących problemów związanych z bezpieczeństwem serwisów WWW [WWW2002] zalicza się:

  • brak walidacji danych wejściowych po stronie serwera,
  • niepoprawną obsługę ciasteczek (ang. cookie),
  • złe formułowanie zapytań SQL.

Sesje, zmienne sesyjne i przekazywanie parametrów#

Ponieważ HTTP jest protokołem bezstanowym, więc konieczne stało się stworzenie mechanizmu sesji (ang. session). Polega on na tym, że przeglądarce przekazywana jest duża liczba generowana losowo zwana identyfikatorem sesji (zazwyczaj w postaci ciasteczka), za pomocą której przeglądarka przedstawia się serwisowi, natomiast serwis przechowuje dla każdego ciasteczka wartości zmiennych. Ma to za zadanie uniemożliwić użytkownikowi manipulowanie zmiennymi, na podstawie których generowane są strony.Parametry przekazywane przez przeglądarkę serwisowi są w niektórych językach, traktowane jako zwykłe zmienne dostępne w programie.

Stwarza to zagrożenie polegające na tym, że jeśli zmienna nie jest zarejestrowana jako sesyjna (czyli taka na której wartość nie ma wpływu użytkownik), to użytkownik może ustawić dowolnie jej wartość poprzez odpowiednie spreparowanie adresu HTTP (doklejenie pary &zmienna=wartość do adresu).

Przykład zagrożenia SQL inject#

Problem zagrożenia atakami typu SQL inject jest szeroko poruszany w czasopismach informatycznych [LUZ20003]. Ataki te polegają na preparowaniu wartości zmiennych przekazywanych serwisom WWW, aby zmieniały one sens wykonywanych przez ten serwis zapytań SQL.Wyobraźmy sobie następujący kod formularza HTML (funkcja validate() napisana w języku JavaScript sprawdza, czy hasło ma poprawną postać)

 <form action="login.php" onSubmit="validate()" >
 Login  <input name=login > <br >
 Hasło  <input type=password size=10 name=hasło > <br >
  <input type="submit" value="Login" >
 </form >

i fragment kodu służący do sprawdzania, czy użytkownik podał poprawny login i hasło

$logged = pg_num_rows(
 pg_query("select * from users where login = `".$login.
 "' and password = `".$password."'")) = 1

Gdyby użytkownik serwisu mógł podać jako wartość zmiennej $password wartość

' or true or password = '

wtedy wykonywane zapytanie przyjęłoby następującą postać

select * from users where login = `login' and password = `' or
true or password = `'

i użytkownik mógłby się zalogować do systemu nie znając hasła. Wydawać by się mogło, że wystarczy, żeby funkcja validate() sprawdzała, czy w zmiennej $password nie występuje znak ‘, ale niestety takie rozwiązanie nie jest doskonałe. Wystarczy, żeby użytkownik przeglądarki wyłączył w niej obsługę JavaScriptu i zabezpieczenie przestanie działać.

Przykład zagrożenia błędną obsługą parametrów adresu#

Zakładając, że zmienna sesyjna $current_user przechowuje nazwę aktualnie zalogowanego użytkownika, programista może napisać następujący kod:

select * from news where user = $current_user

W sytuacji kiedy ta zmienna jest nie jest ustawiona jako sesyjna, użytkownik może ściągnąć stronę zawierającą takie zapytanie, doklejając do adresu HTTP napis &current_user=login i uzyskując zachowanie strony takie, jakie jest oczekiwane w przypadku gdyby zalogowany był użytkownik login.

Łatwość techniczna#

Wydawać by się mogło, że tworzenie narzędzi pozwalających na ,,oszukiwanie serwisów internetowych jest trudne i dostępne jedynie dla wybranych. Tak jednak nie jest. W najprostszym przypadku można się w tym celu posłużyć prostymi programami zainstalowanymi na prawie każdym komputerze z dostępem do Internetu. Przykładowa próba włamania może wyglądać tak

telnet serwer 80
....
GET /index.php?debug=yes HTTP/1.0

Równie prostą metodą jest zapisanie strony źródłowej na dysku, edytowanie jej za pomocą dowolnego edytora, a następnie użycie tak spreparowanego kodu do dostępu do strony docelowej.

Dotychczasowe rozwiązania#

Oczywiście gdyby programiści pisali bezbłędne programy, nie byłoby żadnych problemów z bezpieczeństwem wykonywania zapytań do baz danych. Niestety programiści popełniają błędy ale można próbować im zapobiegać poprzez użycie technik z zakresu inżynierii oprogramowania, technologii bazodanowych i internetowych.

Filtrowanie wartości#

Wszystkie parametry dostarczane przez użytkownika serwisu powinny być filtrowane. Idealnie byłoby, gdyby każdy z parametrów był sprawdzany pod kątem dopuszczalnego zbioru wartości, które powinien przyjmować. Istnieją na rynku programy [SCO2002], zwane firewallami aplikacyjnymi, które pozwalają na automatyzację procesu sprawdzania poprawności przekazywanych serwisowi parametrów. Niektóre z rozwiązań [CIE2003] pozwalają na automatyczne ustalanie zestawu reguł przy użyciu technik z zakresu sztucznej inteligencji, inne natomiast wymagają wprowadzenia zestawu reguł. Część z rozwiązań wspiera jedynie sprawdzanie oryginalności przekazywanych parametrów bez sprawdzania ich poprawności, czyli uniemożliwia manipulowanie parametrami ukrytymi (pełniącymi rolę stałych) oraz wprowadzanie parametrów nie występujących w formularzach.

Separacja warstwy prezentacyjnej i dostępu do danych#

Dostęp do bazy danych może być zorganizowany za pomocą oddzielnej warstwy np. przy użyciu biblioteki dostępu do bazy danych. Część z tego typu bibliotek [AD02003] pozwala na sprawdzanie uprawnień do wyświetlania strony, ale nie daje możliwości weryfikowania poprawności zapytań przesyłanych do bazy danych.

Uprawnienia#

Najprostszą ze stosowanych metod zabezpieczania aplikacji webowych przed nieautoryzowanym dostępem do danych jest używanie uprawnień bazodanowych. W zależności od stosowanego silnika bazy danych uprawnienia te mogą się znacznie różnić. Niektóre bazy danych, np. MySQL [MSQ2003], wspierają jedynie ograniczanie dostępu do tabel i dokonywanych na nich operacji. Inne bazy, np. Oracle[ORA2003] , pozwalają na definiowanie zarówno uprawnień pionowych (do tabel i kolumn), jak i poziomych (do poszczególnych wierszy).Uprawnienia bazodanowe są zazwyczaj ustalane podczas logowania się użytkownika do bazy danych. Oznacza to, że użytkownik na całą sesję uzyskuje te same uprawnienia i nie zmieniają się one podczas, gdy wykonuje on zapytania. Nie ma niestety w SQLu instrukcji, które pozwalałyby na tymczasowe uzyskiwanie jakichś uprawnień i oddawanie ich po wykonaniu zapytania. Oznacza to niestety, że jeśli chcemy używać uprawnień bazodanowych jako środka do zabezpieczania serwisu WWW przed nieautoryzowanym dostępem do bazy danych, to musimy dla każdego użytkownika serwisu założyć oddzielnego użytkownika w bazie danych.

Rozwiązanie takie niekiedy jest kłopotliwe ze względu na wymagania licencyjne baz danych, ale prawie zawsze jest niewydajne ze względu na narzut komunikacyjny pomiędzy serwerem WWW, a serwerem bazodanowym. Podczas wyświetlania strony serwer WWW musi najpierw nawiązać połączenie z bazą danych, następnie zadać zapytanie, a potem zwolnić zasoby uzyskane podczas nawiązywania połączenia. Dlatego też większość serwerów WWW stosuje mechanizm trwałych połączeń (ang. persistent connections). Polega on na tym, że serwer WWW nawiązuje jedno lub kilka połączeń z bazą danych, a następnie używa ich podczas wyświetlania stron. Serwer WWW musi w celu wyświetlenia strony pobrać jedno z nawiązanych połączeń z puli, wykorzystać je do zadania zapytania, a następnie zwrócić je do puli. Ponieważ operacje pobierania i oddawania połączeń do puli są zdecydowanie szybsze niż nawiązywanie i kończenie połączenia z bazą danych, więc obserwuje się znaczny (czasami nawet kilkukrotny) wzrost wydajności serwisu.

Niestety, jeśli chce się używać trwałych połączeń, nie można wykorzystywać różnych uprawnień bazodanowych, dla różnych użytkowników serwisu. Wszystkie strony generowane są więc przy wykorzystaniu uprawnień jednego użytkownika bazy danych, co sprawia, że program generujący stronę dla jednej osoby ma możliwość odczytywania informacji przeznaczonych dla innych użytkowników.

Automatyczne formatowanie zapytań#

Część z języków służących do tworzenia serwisów WWW wspiera automatyczne formatowanie zapytań przekazywanych do bazy danych. Rozwiązania te możemy podzielić na:

  • biblioteki funkcji służących do dostępu do baz danych,
  • rozwiązania systemowe.

Do rozwiązań systemowych można zaliczyć np. stosowany w PHP mechanizm magic_quotes. Polega on na tym, że w parametrach przekazywanych stronie wszystkie znaki potencjalnie niebezpieczne są poprzedzane przez znak (backslash). Oznacza to, że np. potencjalnie niebezpieczne zapytanie

select * from users where login = `login' and password = `' or
true or password = ''

zamienia się na zupełnie bezpieczne

select * from users where login = `login\' and password = \`\' or
true or password = \`'

Niestety automatyczne formatowanie zapytań nie zapewnia żadnej ochrony przed błędnym formułowaniem zapytań przez programistów. Dodatkowo w niektórych przypadkach programiści chcą mieć dostęp do parametrów wejściowych zawierających znaki potencjalnie niebezpieczne, więc często wyłączają tego typu mechanizmy.Jeśli zapytanie wyliczane jest w trakcie działania programu, to może się zdarzyć, że pomimo iż parametr nie zawiera bezpośrednio żadnego z niebezpiecznych znaków, to po wykonaniu obliczenia do bazy danych zostanie przesłane zapytanie niebezpieczne. Sytuacja taka może mieć miejsce np. wtedy, kiedy po stronie przeglądarki przechowywana jest w postaci ciasteczka nazwa użytkownika serwisu, zakodowana prostą funkcją i dekodowana przez serwer przed wyświetleniem każdej ze stron. Użytkownik może wtedy zastąpić oryginalną zawartość ciasteczka, przez niebezpieczny napis zakodowany tą funkcją i taki parametr swobodnie ominie mechanizm sprawdzający niebezpieczne znaki, a następnie po zdekodowaniu ułatwi włamanie się do systemu.

Rozwiązanie#

Proponujemy stworzenie dodatkowej warstwy dostępu do danych, która będzie z jednej strony śledzić, które strony i z jakimi parametrami użytkownik przegląda i na tej podstawie będzie przydzielać temu użytkownikowi uprawnienia, a z drugiej strony będzie filtrować zapytania SQL, które są przekazywane z serwera WWW do bazy danych. Rozwiązanie takie można zaadaptować do dowolnego serwera WWW i języka programowania. Nie wymaga ono żadnej modyfikacji kodu serwisu. Warstwa ta będzie korzystać z opisu polityki bezpieczeństwa serwisu wyrażonego w postaci programów w języku SQLshield.

Pomysł#

Rozwiązanie opiera się na pomyśle aby przed zadaniem zapytania bazie danych, zmodyfikować je w taki sposób, żeby nie mogły one zwracać danych, do których użytkownik nie ma dostępu. Przykładowo, jeśli programista wpisze zapytanie

select * from news

to system automatycznie zamienia je na

select * from news where login = current_user

Dzięki takiemu filtrowaniu niemożliwe stają się ataki zarówno typu sql inject, jak i takie, które używają błędnego rozpoznawania zmiennych sesyjnych.

Architektura#

System jest oparty o architekturę pośrednika (ang. proxy), który zarówno przekazuje żądania nadchodzące z Internetu, jak i filtruje zapytania zadawane bazie danych.

fig1.png

Rys. 1. Architektura systemu

Aby uzyskać większą efektywność wdrażania systemu, może on pracować w dwóch trybach.W trybie logowania bazie danych zadawane są dwa zapytania. Jedno przefiltrowane, a drugie w postaci oryginalnej. Jeśli zapytania te zwrócą różne wyniki, to informacja o tym jest umieszczana w dzienniku aplikacji, jako przypadek potencjalnie błędnego zaprogramowania systemu. Tryb ten powinien być używany podczas testowania aplikacji.

W trybie filtrowania wszystkie zapytania SQL są filtrowane przed wysłaniem ich do bazy.

Aby jeszcze bardziej ułatwić wdrażanie systemu, SQLshield został zaprojektowany w taki sposób, że projektant aplikacji w każdej chwili może sprawdzić do jakich danych ma dostęp program generujący stronę. Jeśli aplikacja jest w trybie logowania, to wystarczy dokleić &debug=on do adresu strony i w przeglądarce zostanie wyświetlona informacja o wszystkich danych, do których aplikacja ma dostęp.

Metodologia#

Oprócz stworzenia mechanizmu zabezpieczającego dane przed nieautoryzowanym dostępem musimy opracować metodologię służącą do wdrażania tego rozwiązania. Sugerujemy aby poszczególne etapy pracy nad zabezpieczaniem systemu wyglądały następująco:

  • przygotowanie opisu polityki bezpieczeństwa w języku SQLshield
  • testowanie aplikacji w trybie logowania
  • dopracowanie polityki bezpieczeństwa
  • testowanie aplikacji w trybie logowania z jednoczesnym przeglądaniem dostępnych dla poszczególnych stron danych
  • zamknięcie polityki bezpieczeństwa
  • uruchomienie aplikacji w trybie filtrowania

Język dostępu do danych – SQLshield#

Opis#

Ze względu na ograniczoną objętość pracy przedstawimy jedynie główne idee, a nie pełną formalną specyfikację języka.Pierwsza sekcja programu w SQLshield zawiera informacje o tym, do jakich tabel i na których stronach dostęp powinien być filtrowany. Służą do tego instrukcje page oraz protect. Po słowie kluczowym page podaje się listę stron, na których dostęp do bazy danych ma być filtrowany (zazwyczaj nie są to wszystkie strony, gdyż do filtrowania dostępu do części ze stron używa się innych mechanizmów systemowych, np. ograniczania dostępu na podstawie logowania do systemu operacyjnego lub ograniczania dostępu tylko z pewnej podsieci fizycznej). W liście tej można używać wyrażeń regularnych. Podobnie po słowie kluczowym protect podaje się listę tabel, do których dostęp ma być filtrowany.

Następnie podaje się, jakie są możliwe do uzyskania uprawnienia, kiedy się je uzyskuje, a kiedy traci. Niektóre z uprawnień mogą być uzyskiwane na określony czas. Do specyfikowania uprawnień służą słowa kluczowe gain i revoke. Uprawnienia nazywane są słowami zaczynającymi się od wielkiej litery i mogą w swojej definicji zawierać dodatkowe parametry. Zarówno podczas uzyskiwania jak i oddawania uprawnień istnieje dostęp do parametrów przekazywanych stronie (nazwę parametru należy poprzedzić znakiem $) oraz do nazwy strony, która jest wyświetlana. Możliwe jest również zadanie zapytanie bazie danych, pod warunkiem, że zwróci ono tylko jeden wiersz.

W kolejnej sekcji określamy do których kolumn i wierszy mamy dostęp. Służą do tego konstrukcje access column i access table, po których należy podać do których kolumn lub tabel dostęp jest ograniczany, a następnie warunek, który może być użyty w konstrukcji SQL in. Warunek ten będzie później wklejany do zapytań SQL.

Możliwe jest wyspecyfikowanie zależności hierarchicznych (mamy dostęp do tabeli A, jeśli wartość kolumny y z tabeli B jest taka sama jak kolumny x z tabeli A, oraz wartość kolumny v z tabeli B ma taką samą wartość jak wartość pola z uprawnienia), jak również różnych uprawnień dla różnych stron.

Przykład#

Poniżej przedstawiamy opis polityki bezpieczeństwa podsystemu Uniwersyteckiego Systemu Obsługi Studiów służącego do obsługi systemu płatności. System płatności składa się z trzech głównych tabel:

  • osoby (users) — przechowująca dane osobowe poszczególnych studentów,
  • transakcje (transactions) — przechowująca informacje o poszczególnych transakcjach, zarówno wpłatach, jak i należnościach,
  • rozliczenia (clearings) — przechowująca informacje o tym, które z transakcji zostały rozliczone i jaka transakcja powstała z ich rozliczenia.

Każda z transakcji ma przypisaną osobę, która jej dokonała. Osoba powinna widzieć swoje i tylko swoje transakcje oraz rozliczenia. Skrypt w SQLshield wygląda następująco

kom: jakie strony obsługujemy
page .*
kom: jakie tabele chronimy
protect users, transactions, clearings
kom: kiedy uzyskujemy uprawnienia
gain Logged($user) when login.php3
 and (1) = (select count(*) from users where user = $user
 and password = $password)
kom: kiedy tracimy uprawnienia
revoke Logged($user) when logout.php3
kom: do ktorych kolumn mamy dostęp
access column users.* when Logged($user)
 and login in $user
access column transactions.* when Logged($user)
 and user in $user
access column clearings.* when Loggedd($user)
 and user in $user
kom: do których wierszy mamy dostęp
access table users when Logged($user)
 and login in $user
access table transactions when Logged($user)
 and user in $user
access table clearings when Loggedd($user)
 and user in $user

Jak to działa?#

Załóżmy, że w programie SQLshield znajduje się tylko jedna linia ograniczająca dostęp do danych (access table users when Logged($user) and user in $user). Jeśli serwis WWW przekazuje do bazy pytanie zawierające tabelę users, to jest ono automatycznie uzupełniane o warunek users.user=$user. Czyli

select * from users - > select * from users where user = $user;
select * from users where user = 'a' - > select * from users
 where (user='a') and user = $user;
select count(*) from users - > select * from users where user = $user.

Gdyby warunek brzmiał (access table users when Logged($user) and user in (select login from sp_users)), to odpowiednie zapytania byłyby przekształcane następująco

select * from users - > select * from users where user in (select
login from sp_users);

Pozostałe zapytania są przekształcane analogicznie.Oczywiście, jeśli zapytanie jest oparte na więcej niż jednej tabeli, to warunki są sprawdzane oddzielnie dla każdej z nich.

Problemy techniczne#

Ponieważ HTTP jest protokołem bezstanowym, więc nie można wiązać nabytych uprawnień z połączeniem, ale konieczne jest wiązanie ich z innym obiektem. W naszej implementacji przyjęliśmy, że takim obiektem jest sesja, co niestety zmusza nas do stosowania różnych funkcji w sytuacji kiedy serwis jest napisany w wielu językach programowania. W rzeczywistości sytuacja taka jest rzadka, więc ten problem nie jest bolesny.Największym z występujących problemów technicznych, na który napotkaliśmy podczas implementacji systemu, była konieczność powiązania identyfikatora sesji z zapytaniem zadawanym bazie danych. Powiązanie takie jest konieczne, gdyż w tym samym momencie serwis może obsługiwać wiele żądań, z których każde może być zadane przez innego użytkownika z innymi uprawnieniami. W celu rozwiązania tego problemu musieliśmy zmodyfikować funkcje dostępu do bazy danych tak, aby jako jeden ze swoich parametrów podawały one identyfikator sesji. Na szczęście takie rozwiązanie jest akceptowalne, gdyż nie wymaga ono zmiany kodu programów, a jedynie zmiany (i to w miarę prostej) biblioteki dostępu do bazy danych.

Wydajność#

W trakcie przeprowadzonych eksperymentów nie zauważyliśmy wprowadzania przez nasz system opóźnień. Co więcej, w niektórych przypadkach wydajność wzrosła! Działo się to wtedy, kiedy programiści niepotrzebnie pobierali za dużo danych z bazy, np. sprawdzając w programie (zamiast w zapytaniu), czy użytkownik ma oczekiwany login.

Podsumowanie#

Przedstawiliśmy język opisu polityk bezpiecznego dostępu do danych SQLshield oraz aplikację korzystającą z opisów stworzonych w tym języku. Pokazaliśmy, że możliwe jest zabezpieczenie się przed kilkoma często występującymi problemami związanymi z bezpieczeństwem serwisów WWW w sposób automatyczny. Przedstawiliśmy również metodologię pracy podczas wdrażania takiego systemu zabezpieczeń.

Bibliografia#

[AD02003] AD0db Database Library for PHP, php.weblogs.com/AD0DB.
[CIE2003] K. Ciebiera, Inteligenty system autoryzowania dostępu do stron internetowych, 2003, V Krajowa Konferencja Naukowa Inżynieria Wiedzy i Systemy Ekspertowe.
[LUZ2003] Ł. Luzar, Zastrzyk prosto w serce, 20-21, 03 2003, Computerworld.
[MSQ2003] MySQL Homepage, www.mysql.org.
[ORA2003] Oracle Corporation, www.oracle.com.
[SCO2002] R. Sharp i D. Scott, Abstracting Applicatin-Level Web Security, 2002, The Eleventh International WWW Conference.
[WWW2002] Top 10 Web App Vulnerabilities, www.owasp.org.

©2015 e-Informatyka.pl, All rights reserved.

Built on WordPress Theme: Mediaphase Lite by ThemeFurnace.