Bezpieczeństwo baz danych W dzisiejszych czasach bazy danych są kluczowymi komponentami każdej aplikacji internetowej, umożliwiającymi stronom internetowym dostarczanie różnych dynamicznych treści. Ponieważ w bazie danych mogą być przechowywane bardzo wrażliwe lub tajne informacje, należy poważnie podejść do kwestii ochrony swoich baz danych. Aby pobrać lub zapisać jakiekolwiek informacje, należy się połączyć do bazy danych, wysłać poprawne zapytanie, pobrać wynik i zamknąć połączenie. W obecnych czasach powszechnie stosowanym w tym celu językiem zapytań jest SQL (Structured Query Language). Zobacz, w jaki sposób atakujący może manipulować zapytaniami SQL. Jak możesz przypuszczać, PHP nie może ochronić Twojej bazy danych samej w sobie. Kolejne sekcje są wprowadzeniem do podstaw tego, jak uzyskiwać dostęp i zmieniać informacje w bazach danych z poziomu skryptów PHP. Pamiętaj o prostej zasadzie: głęboka ochrona. W im większej ilości miejsc zadbasz o zwiększenie ochrony swojej bazy danych, tym mniejsze prawdopodobieństwo, że atakujący da radę wykraść lub wykorzystać przechowywane w niej informacje. Dobry projekt schematu bazy danych oraz aplikacji poradzi sobie z większością problemów. Projektowanie baz danych Pierwszym krokiem jest zawsze stworzenie bazy danych, chyba że chcesz użyć bazy danych od strony trzeciej. Gdy baza danych jest tworzona, przypisywany jest jej właściciel, który wykonał żądanie tworzące bazę. Zazwyczaj tylko właściciel (lub superużytkownik) może operować na obiektach w tej bazie danych, a inni użytkownicy muszą otrzymać uprawnienia, aby mogli ich używać. Aplikacja nigdy nie powinna łączyć się do bazy danych jako jej właściciel lub superużytkownik, ponieważ ci użytkownicy mogą wykonać dowolne zapytania, na przykład zmieniające strukturę bazy danych (np. usuwające tabele) lub usuwające całą jej zawartość. Możesz stworzyć różnych użytkowników bazy danych dla każdego z zastosowań Twojej aplikacji z bardzo ograniczonymi uprawnieniami do obiektów w bazie danych. Powinieneś nadawać tylko niezbędne uprawnienia i unikać tego, by ten sam użytkownik mógł używać bazy danych w innych celach. Oznacza to, że jeśli atakujący zdobędą dostęp do Twojej bazy danych używając poświadczeń aplikacji, mogą wykonać tylko takie zmiany, które może wykonać Twoja aplikacja. Łączenie z bazą danych Możesz ustanowić połączenie z wykorzystaniem SSL aby zaszyfrować komunikację między klientem a serwerem i zwiększyć bezpieczeństwo, lub użyć ssh, aby zaszyfrować połączenie sieciowe między klientami a serwerem bazy danych. Przy zastosowaniu któregoś z tych rozwiązań monitorowanie ruchu i zdobycie informacji o Twojej bazie danych będzie utrudnione dla potencjalnego atakującego. Szyfrowany model zapisu SSL lub SSH chroni dane płynące z klienta do serwera, ale nie chroni danych już zapisanych w bazie. SSL jest protokołem szyfrującym transfer danych. Kiedy atakujący uzyska bezpośredni dostęp do Twojej bazy danych (omijając serwer WWW), przechowywane wrażliwe informacje mogą być ujawnione, chyba że informacja jest chroniona przez samą bazę danych. Szyfrowanie danych jest dobrym sposobem, aby zapobiec temu zagrożeniu, ale bardzo niewiele baz danych oferuje ten typ szyfrowania danych. Najprostszym sposobem obejścia tego problemu jest stworzenie swojej własnej paczki szyfrującej i wykorzystanie jej z poziomu skryptów PHP. PHP może Ci w tym pomóc dostarczając kilka rozszerzeń, takich jak OpenSSL i Sodium, które oferują szeroką gamę algorytmów szyfrujących. Skrypt może szyfrować dane przed ich umieszczeniem w bazie danych i odszyfrować je w chwili odczytu. Zobacz odniesienia, żeby zobaczyć dalsze przykłady tego, jak działa szyfrowanie. Haszowanie W wypadku naprawdę ukrytych danych, jeśli ich surowa reprezentacja nie jest potrzebna (tj. dane nie będą wyświetlane), można rozważyć ich haszowanie. Powszechnie znanym przykładem haszowania jest zapisywanie haszu kryptograficznego haseł w bazie danych, zamiast trzymania samych haseł. Funkcje modułu password oferują wygodny sposób haszowania wrażliwych danych i pracy z tymi haszami. Funkcja password_hash jest używana do haszowania podanego ciągu znaków używając najmocniejszego obecnie dostępnego algorytmu, a password_verify sprawdza czy podane hasło pasuje do haszu zapisanego w bazie danych. Haszowanie pola z hasłem ]]> SQL Injection (wstrzyknięcie SQL) SQL injection (wstrzyknięcie SQL) jest techniką, w której atakujący wykorzystuje luki w kodzie aplikacji odpowiedzialnym za budowanie dynamicznych zapytań SQL. Atakujący może zyskać dostęp do chronionych części aplikacji, pobrać wszystkie informacje z bazy danych, manipulować istniejącymi danymi lub nawet wykonywać niebezpieczne komendy systemowe na maszynie bazy danych. Podatność występuje, gdy programista łączy lub wplata dane wejściowe od użytkownika do zapytań SQL. Dzielenie wyników na strony... i tworzenie superużytkowników (PostgreSQL) W następującym przykładzie dane wejściowe od użytkownika są bezpośrednio wplatane w zapytanie SQL, pozwalając atakującemu na zdobycie konta superużytkownika w bazie danych. ]]> Normalni użytkownicy klikają na linki 'następna'/'poprzednia', które kodują wartość zmiennej $offset w adresie URL. Skrypt spodziewa się, że dane przychodzące w zmiennej $offset są liczbą. Jednak co jeśli ktoś spróbuje włamać się doklejając następującą rzecz do adresu URL? Jeśli tak się stanie, skrypt utworzyłby atakującemu konto superużytkownika. Zauważ, że 0; zostało podane aby dostarczyć poprawny offset do oryginalnego zapytania i je zakończyć. Częstą techniką wymuszenia na parserze SQL aby zignorował resztę zapytania napisanego przez programistę jest podanie --, czyli znaków rozpoczynających komentarz w SQL. Możliwym sposobem na uzyskanie haseł jest obejście stron wyników wyszukiwania. Jedynym co musi zrobić atakujący jest zobaczenie czy istnieją jakieś wysyłane zmienne używane w zapytaniu SQL, które nie są obsługiwane poprawnie. Takie filtry mogą być często ustawiane w poprzedzającym formularzu, aby dostosować klauzule WHERE, ORDER BY, LIMIT and OFFSET w zapytaniu SELECT. Jeśli Twoja baza danych wspiera konstrukcję UNION, atakujący może spróbować dokleić całe zapytanie do oryginalnego, aby otrzymać listę haseł z określonej tabeli. Jest zdecydowanie zalecane aby przetrzymywać jedynie bezpieczne hasze haseł zamiast samych haseł. Pobieranie listy artykułów... i haseł (dowolna baza danych) ]]> Statyczna część zapytania może zostać połączona z innym wyrażeniem SELECT, które ujawnia wszystkie hasła: Zapytania UPDATE i INSERT również są podatne na takie ataki. Formularz resetujący hasło... aby uzyskać więcej uprawnień (dowolna baza danych) ]]> Jeżeli złośliwy użytkownik wyśle wartość ' or uid like'%admin% jako zmienną $uid, aby zmienić hasło administratora lub po prostu ustawi $pwd na hehehe', trusted=100, admin='yes aby uzyskać więcej uprawnień, to zapytanie zostanie zmienione: ]]> Mimo, że jest oczywiste, że atakujący musi posiąść jakąś wiedzę na temat architektury bazy danych, aby przeprowadzić udany atak, to zdobycie tej informacji jest często bardzo proste. Przykładowo kod może być częścią otwartoźródłowego oprogramowania i być publicznie dostępny. Informacje te mogą również zostać ujawnione przez zamknięty kod źródłowy - nawet jeśli jest on zakodowany, zaciemniony lub skompilowany - a nawet przez Twój własny kod, poprzez wyświetlanie wiadomości błędów. Inne metody to np. użycie typowych nazw tabel i kolumn Na przykład formularz logowania, który używa taeli 'users' z kolumnami nazwanymi 'id', 'username', and 'password'. Atak na system operacyjny serwera baz danych (MSSQL Server) Przerażający przykład tego, jak na niektórych maszynach baz danych można uzyskać dostęp do komend systemowych. ]]> Jeśli atakujący wyśle wartość a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- jako zmienną $prod, to wartością $query będzie: ]]> MSSQL Server wykonuje zapytania SQL w grupach, w tym komendę dodającą nowego użytkownika do lokalnych kont bazy danych. Jeśli ta aplikacja była uruchomiona jako sa i usługa MSSQLSERVER została uruchomiona z wystarczającymi uprawnieniami, to atakujący miałby teraz konto z dostępem do tej maszyny. Niektóre przykłady powyżej są powiązane z konkretnym serwerem baz danych, ale nie oznacza to, że podobne ataki są niemożliwe względem innych produktów. Twój serwer baz danych może mieć podobną podatność wykorzystywaną w inny sposób. Zabawny przykład problemów związanych z SQL injection Obrazek dzięki uprzejmości xkcd Techniki unikania Rekomendowanym sposobem unikania SQL injection jest przypisanie wszystkich danych za pomocą przygotowanych instrukcji (ang. prepared statements). Użycie parametryzowanych zapytań nie jest wystarczające aby zupełnie uniknąć SQL injection, ale jest najbezpieczniejszym sposobem dostarczenia danych wejściowych od użytkownika do zapytań SQL. Wszystkie dynamiczne dane w zapytaniach WHERE, SET i VALUES muszą być zastąpione symbolami zastępczymi (placeholderami). Rzeczywiste dane zostaną podstawione podczas wykonywania i zostaną wysłane osobno od zapytania SQL. Podstawianie parametrów może być wykorzystane tylko dla danych. Inne dynamiczne części zapytań SQL muszą być filtrowane tak, aby pozwolić tylko na znaną listę dozwolonych wartości. Unikanie SQL injection przy użyciu przygotowanych instrukcji PDO prepare("SELECT * FROM products WHERE id LIKE ? ORDER BY price {$sortingOrder}"); // Wartość jest dostarczana z symbolami wieloznacznymi LIKE $stmt->execute(["%{$productId}%"]); ?> ]]> Przygotowane instrukcje są obsługiwane przez PDO, przez MySQLi i inne biblioteki baz danych. Ataki SQL injection są oparte głównie na wykorzystywaniu kodu, który nie został stworzony z myślą o bezpieczeństwie. Nigdy nie ufaj żadnym danym wejściowym, szczególnie pochodzącym od użytkownika, nawet jeśli pochodzi z listy wyboru, ukrytego pola formularza lub ciasteczka. Pierwszy przykład pokazuje, że takie proste zapytanie może spowodować prawdziwy chaos. Strategia głębokiej ochrony opiera się na kilku dobrych praktykach kodowania: Nigdy nie łącz się do bazy danych jako superużytkownik lub właściciel bazy danych. Zawsze używaj dostosowanych użytkowników z minimalnymi uprawnieniami. Sprawdzaj czy otrzymane dane wejściowe mają oczekiwany typ danych. PHP ma szeroki zakres funkcji sprawdzających dane, od najprostszych, które można znaleźć w funkcjach zmiennych i w funkcjach typu znaków (np. is_numeric czy ctype_digit) aż po wsparcie wyrażeń regularnych kompatybilnych z Perlem. Jeżeli aplikacja oczekuje danych numerycznych, rozważ ich weryfikację funkcją ctype_digit, po cichu zmień ich typ używając settype lub użyj reprezentacji numerycznej dzięki sprintf. Jeżeli warstwa bazy danych nie wspiera podstawiania parametrów, to ujmij w apostrofy każdą nienumeryczną wartość przekazaną przez użytkownika, która jest przekazywana do bazy danych. Zrób to za pomocą funkcji dodającej znaki ucieczki dla Twojej bazy danych (np. mysql_real_escape_string, sqlite_escape_string, itd.). Ogólne funkcje takie jak addslashes są przydatne tylko w bardzo określonych zastosowaniach (np. MySQL w jednobajtowym zestawie znaków z wyłączonym NO_BACKSLASH_ESCAPES), więc lepiej ich unikać. Nie wyświetlaj żadnych informacji o bazie danych, szczególnie jej strukturze — czy to umyślnie, czy przypadkiem. Zobacz też sekcje raportowanie błędów and funkcje raportowania i logowania błędów. Poza tym, warto też skorzystać z logowania zapytań w kodzie Twojej aplikacji lub przez samą bazę danych, jeśli wspiera ona takie logi. Oczywiście logowanie danych nie jest w stanie zapobiec szkodliwym sytuacjom, ale może być pomocne w wyśledzeniu, która aplikacja została zaatakowana. Log nie jest przydatny sam w sobie, lecz dzięki informacjom które zawiera. Ogólnie rzecz biorąc, im więcej szczegółów tym lepiej.