6. Bezpieczeństwo przy współpracy z bazami danych

advertisement
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
6. Bezpieczeństwo przy współpracy z bazami danych
6.1. Idea ataku „SQL injection”
Atak znany jako „SQL injection” jest możliwy wtedy, gdy użytkownik ma bezpośredni
wpływ na postać zapytania wysyłanego do bazy danych. Dzieje się tak najczęściej wtedy,
gdy zapytanie zawiera elementy wstawiane z formularza HTML. Odpowiednio preparując
treść informacji wysyłanych przez formularz, wrogi użytkownik jest w stanie wejść w
posiadanie nie przeznaczonych dla niego informacji. Istnieje wiele sposobów wykonania
takiego ataku, tutaj omówię jedynie dwa najpopularniejsze, pierwszy za pomocą
polecenia OR języka SQL, drugi za pomocą polecenia UNION SELECT.
6.2. Prosty atak przy pomocy polecenia OR
Atak taki może być użyty np. w formularzu logowania na stronę internetową, jeżeli
użytkownicy i hasła przechowywane są w tabeli bazy danych. Dzięki niemu możemy
przejść pozytywnie proces logowania nie znając ani żadnej zarejestrowanej nazwy
użytkownika, ani żadnego hasła. Typowy fragment kodu wrażliwego na taki atak będzie
wyglądał następująco (jest to fragment skryptu odbierającego dane z formularza
logowania):
$user = $_POST['username'];
$pass = $_POST['password'];
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
$zapytanie = "SELECT userid FROM userzy WHERE userid=\"$user\" AND
pass=\"$pass\"";
$wynik = mysql_query($zapytanie);
$odpowiedz = mysql_fetch_assoc($wynik);
$zalogowany = $odpowiedz['userid'];
Powyższy fragment kodu sprawdza zgodność hasła i umieszcza nazwę zalogowanego
użytkownika w zmiennej $zalogowany. A o to w jaki prosty sposób można obejść stronę
logowania. Wrogi użytkownik wpisuje następujące teksty w formularzu logowania:
Użytkownik:
Hasło:
" OR ""="
" OR ""="
W efekcie do bazy danych zostanie wysłane następujące zapytanie:
SELECT userid FROM userzy WHERE userid="" OR ""="" AND pass="" OR ""=""
Takie zapytanie zwróci całą tabelę, w efekcie zostaniemy zalogowani jako pierwszy
użytkownik.
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
6.3. Atak przy pomocy UNION SELECT
Polecenie UNION SELECT pozwala na połączenie w jedną odpowiedź dwóch zapytań – w
przypadku wykorzystania tej możliwości do ataku, pierwsza część zapytania jest
nieistotna (jest to zapytanie do tabeli dostępnej użytkownikowi). Będąc wrogim
użytkownikiem formułujemy pierwszą część w taki sposób, aby zwróciła zero wierszy.
Druga część zapytania natomiast skierowana jest do tabeli, której dane chcemy przejąć.
Siłę takiego ataku zwiększa fakt, że o ile użytkownik skryptu ma (na skutek
niedopatrzenia administratora bazy i/lub serwera WWW) odpowiednie uprawnienia w
bazie danych, to może zdobyć w ten sposób dostęp do bazy systemowej (w MySQL jest
to baza 'mysql' z tabelami takimi jak 'mysql.user', 'mysql.db' i tak dalej).
Załóżmy, że mamy do czynienia z blogiem internetowym, do którego wpisów można się
dostać poprzez następujący URL:
http://jakis.host.pl/mojblog?wpis=xx
gdzie xx to numer wpisu. Zapytanie do bazy danych pobierające odpowiednie dane
dotyczące wpisu wygląda tak:
$zapytanie = "SELECT data,tytul,tresc FROM blog
WHERE wpis={$_GET['wpis']}";
Zaatakujmy stronę, wpisując jako adres URL następujący tekst:
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
http://jakis.host.pl/mojblog?wpis=-2 UNION SELECT user,pass FROM users
Spowoduje to wysłanie do bazy danych następującego zapytania:
SELECT data,tytul,tresc FROM blog WHERE wpis=-2 UNION SELECT user,pass FROM
users
Kolorem zielonym oznaczono tę część zapytania, która poprzez podanie nieistniejącego
wpisu daje zero wierszy, zatem „przyjazna” część zapytania została zneutralizowana.
Kolorem czerwonym natomiast oznaczona jest „wroga” część zapytania, sięgająca po
dane do zupełnie innej tablicy. Jej pierwszy wiersz zostanie pokazany jako wpis do bloga,
o ile użyto przy pobieraniu wiersza funkcji mysql_fetch_row(), ale i mysql_fetch_assoc()
nie stanowi przeszkody, można wszak zapytać:
UNION SELECT user AS data, pass AS tytul FROM users
W ten sposób mamy pierwszego użytkownika i jego hasło. A co z następnymi? Nic
prostszego, oto sięgamy po następnego:
http://jakis.host.pl/mojblog?wpis=-2 UNION SELECT user,pass FROM users
WHERE NOT userid="jan"
gdzie rzecz jasna jan to identyfikator pierwszego użytkownika. W ten sposób używając
dodatkowo AND, możemy powyciągać ilość użytkowników ograniczoną w zasadzie
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
dopuszczalną długością URL-a w przeglądarce (ale kto powiedział, że musimy generować
te zapytania przeglądarką?...). Wrogi użytkownik może w ten sam sposób sięgnąć do
tabel systemowych bazy, zawierających dane o użytkownikach bazy, bazach, tabelach
i prawach dostępu. Na przykład w MySQL mamy między innymi:
mysql – baza systemowa,
●
mysql.user – tabela użytkowników i ich przywilejów
●
mysql.db – tabela baz danych,
●
mysql.tables_priv – tabela praw dostępu do tabel.
●
O ile administrator nie był na tyle czujny, żeby zablokować SELECT do tych tabel (że nie
wspomnę o innych przywilejach) dla użytkownika systemu, w kontekście którego
wykonywany jest skrypt, wrogi użytkownik ma szerokie pole do działania...
6.4. Przeciwdziałanie
Uniemożliwienie ataku „SQL injection” polega na zablokowaniu możliwości
bezpośredniego umieszczania łańcuchów tekstowych podawanych poprzez metody GET i
POST protokołu HTTP w zapytaniach do bazy danych. Najskuteczniejszą metodą walki z
takimi atakami jest stosowanie wyłącznie zapytań o treści statycznej (niezależnej od
informacji przychodzących z sieci), jednak nie zawsze jest to możliwe. Można też
przeszukiwać teksty zewnętrzne umieszczane w zapytaniach do baz danych pod kątem
słów kluczowych języka SQL, jednak po pierwsze istnieje ryzyko pominięcia jakichś słów,
po drugie tekst może zawierać takie słowo w kontekście zwykłych danych (np.
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
użytkownik o nazwie 'union'). Generalnie wiadomo – im więcej sprawdzeń tym lepiej.
Istnieją również gotowe rozwiązania do walki z SQL injection w samym PHP.
6.5. Magic Quotes
Jest to automatyczna opcja w PHP, która w tablicach $_GET[] i $_POST[] automatycznie
poprzedza wszystkie apostrofy, cudzysłowy, odwrotne ukośniki i znaki o kodzie 0
odwrotnym ukośnikiem. Powoduje to, że znaki te nie są traktowane przez MySQL (i inne
bazy) jako znaki specjalne. Uniemożliwia to wykonanie większości ataków SQL Injection
(ale nie wszystkich). Jest to opcja włączana globalnie w ustawieniach PHP. Główną zaletą
Magic Quotes jest automatyzm – początkujący użytkownik nie zapomni o
zabezpieczeniu. Oprócz tego jednak znajdziemy szereg wad. Po pierwsze poczucie
fałszywego bezpieczeństwa – Magic Quotes nie zabezpieczają przed wszystkimi
atakami. Po drugie dane są przetwarzane niezależnie od tego, czy faktycznie kierowane
są do zapytań SQL – powoduje to niepotrzebny spadek wydajności skryptu i zwiększenie
zapotrzebowania na pamięć. Dodatkową wadą są problemy z przenośnością – w
przypadku gdy przenosimy skrypt z miejsca gdzie Magic Quotes są włączone do miejsca
gdzie są wyłączone i odwrotnie. Stan Magic Quotes można sprawdzić z poziomu skryptu
funkcją get_magic_quotes_gpc().
mgr inż. Grzegorz Kraszewski – TECHNOLOGIE INTERNETOWE 3 – wykład 6: „Bazy danych i bezpieczeństwo”.
6.6. mysql_real_escape_string()
Ta specyficzna dla MySQL funkcja powinna być stosowana do przetwarzania wszystkich
danych zewnętrznych (zwłaszcza z tablic $_GET[] i $_POST[]) wstawianych do zapytań
kierowanych do bazy MySQL. Pierwszym parametrem funkcji jest przetwarzany łańcuch
tekstowy, a drugim zasób identyfikujący nawiązane połączenie z bazą danych (wynik
mysql_connect()). Stąd wniosek, że funkcji tej można użyć tylko mając nawiązane
połączenie. Wymaganie to wynika z faktu, że funkcja bierze pod uwagę kodowanie
znaków używane przez bazę (np. może to być UTF-8). Jeżeli nie podamy połączenia,
przyjmowane jest domyślnie ostatnio nawiązane połączenie. Przykład użycia:
$user = mysql_real_escape_string($_POST['user']);
$pass = mysql_real_escape_string($_POST['pass']);
$zapytanie = "SELECT user FROM users WHERE user=\"$user\" AND
pass=\"$pass\"";
$wynik = mysql_query($zapytanie);
Download