Czy uważasz, że Twoja baza danych SQL jest szczytem wydajności i fortecą nie do zdobycia? Niestety, podatność na ataki typu SQL Injection może Cię zaskoczyć!
Tak, mówimy o potencjalnej katastrofie, gdyż nie chcemy używać oklepanych frazesów o „wzmacnianiu zabezpieczeń” i „ochronie przed złośliwymi intruzami”. SQL Injection to problem tak stary jak samo programowanie, więc każdy programista powinien znać go na wylot, a co za tym idzie, umieć mu przeciwdziałać. Jednak, jak to bywa, chwila nieuwagi może mieć fatalne konsekwencje.
Jeśli temat SQL Injection nie jest Ci obcy, możesz przejść do dalszej części tekstu. Jeśli jednak dopiero rozpoczynasz swoją przygodę z tworzeniem stron internetowych i aspirujesz do poważniejszych ról, krótkie wprowadzenie będzie jak znalazł.
Czym właściwie jest SQL Injection?
Kluczem do zrozumienia istoty ataku SQL Injection jest jego nazwa: SQL + Injection, czyli wstrzyknięcie kodu SQL. Choć termin „wstrzyknięcie” kojarzy się z medycyną, w tym przypadku chodzi o umieszczenie, wprowadzenie kodu SQL do aplikacji internetowej.
Wprowadzenie kodu SQL do aplikacji… czy nie tym właśnie się zajmujemy? Tak, ale w kontrolowany sposób. Nie chcemy przecież dać atakującemu władzy nad naszą bazą danych. Spójrzmy na przykład, aby lepiej zrozumieć, o co chodzi.
Załóżmy, że tworzysz prostą stronę w PHP dla lokalnego sklepu i chcesz dodać formularz kontaktowy:
<form action="record_message.php" method="POST"> <label>Twoje imię</label> <input type="text" name="name"> <label>Twoja wiadomość</label> <textarea name="message" rows="5"></textarea> <input type="submit" value="Wyślij"> </form>
Plik send_message.php ma za zadanie zapisać dane w bazie, by właściciele sklepu mogli odczytywać wiadomości. Oto przykładowy kod:
<?php $name = $_POST['name']; $message = $_POST['message']; // sprawdzenie, czy użytkownik już wysłał wiadomość mysqli_query($conn, "SELECT * from messages where name = $name"); // dalszy kod
Kod najpierw sprawdza, czy dany użytkownik ma już nieprzeczytaną wiadomość. Zapytanie SELECT * z tabeli messages, gdzie name = $name wydaje się być w porządku, prawda?
Błąd! Potężny błąd!
W ten sposób nieświadomie otwieramy drzwi do katastrofy. Atakujący może wykorzystać sytuację, jeśli zostaną spełnione następujące warunki:
- Aplikacja korzysta z bazy danych SQL (obecnie to standard)
- Bieżące połączenie z bazą danych ma uprawnienia do modyfikacji i usuwania danych
- Można odgadnąć nazwy istotnych tabel
Ostatni punkt oznacza, że atakujący, wiedząc, że prowadzisz sklep internetowy, może przypuszczać, że dane o zamówieniach przechowujesz w tabeli o nazwie „orders”. Mając to wszystko, wystarczy, że jako imię poda następujący ciąg znaków:
Joe; TRUNCATE orders;? A potem patrz, jak wygląda zapytanie po przetworzeniu przez PHP:
SELECT * FROM messages WHERE name = Joe; TRUNCATE orders;
Pierwsza część zapytania ma błąd składni (brak cudzysłowów wokół „Joe”), jednak średnik wymusza na silniku MySQL rozpoczęcie interpretacji nowej instrukcji: TRUNCATE orders. W ten sposób, jednym prostym atakiem, cała historia zamówień zostaje usunięta!
Teraz, gdy już wiesz, jak działa SQL Injection, czas dowiedzieć się, jak mu zapobiegać. Dwa warunki niezbędne do skutecznego ataku to:
Jak zapobiegać SQL Injection w PHP?
Skoro połączenia z bazą, zapytania i dane od użytkowników są nieodzowne, jak możemy uniknąć SQL Injection? Na szczęście, jest to dość proste. Możemy to zrobić na dwa sposoby: 1) oczyszczać dane od użytkownika oraz 2) korzystać z przygotowanych zapytań.
Oczyszczanie danych od użytkownika
Jeśli używasz starszej wersji PHP (5.5 lub starszej, co często zdarza się na współdzielonych hostingach), powinieneś przepuścić wszystkie dane od użytkownika przez funkcję mysql_real_escape_string(). Jej zadaniem jest usunięcie znaków specjalnych z ciągu, tak by nie miały one specjalnego znaczenia dla bazy danych.
Przykładowo, w ciągu „I’m string”, apostrof (’) może zostać wykorzystany przez atakującego do manipulacji zapytaniem. Użycie funkcji mysql_real_escape_string() zmieni ten ciąg na „I\’m string”, dodając ukośnik przed apostrofem, który go „uciekł”. W rezultacie, cały ciąg będzie traktowany jako nieszkodliwy tekst, a nie część manipulowanego zapytania.
To rozwiązanie ma jednak wadę: jest to technika przestarzała, pasująca do starszych metod dostępu do bazy danych w PHP. Od PHP 7 ta funkcja już nie istnieje, co zmusza nas do szukania innych rozwiązań.
Używanie przygotowanych zapytań
Przygotowane zapytania to bezpieczniejsza i bardziej niezawodna metoda wykonywania zapytań do bazy danych. Zamiast wysyłać bezpośrednie zapytanie do bazy, informujemy ją najpierw o strukturze zapytania, które chcemy wykonać. To właśnie rozumiemy pod pojęciem „przygotowanie”. Po przygotowaniu zapytania, przesyłamy dane jako sparametryzowane wejście. Baza danych „wypełnia luki”, wstawiając przesłane dane do przygotowanej struktury. To odbiera danym specjalną moc, traktując je jako zwykłe zmienne. Przygotowane zapytania wyglądają mniej więcej tak:
<?php $servername = "localhost"; $username = "username"; $password = "password"; $dbname = "myDB"; // Utwórz połączenie $conn = new mysqli($servername, $username, $password, $dbname); // Sprawdź połączenie if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } // przygotuj i powiąż $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)"); $stmt->bind_param("sss", $firstname, $lastname, $email); // ustaw parametry i wykonaj $firstname = "John"; $lastname = "Doe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Mary"; $lastname = "Moe"; $email = "[email protected]"; $stmt->execute(); $firstname = "Julie"; $lastname = "Dooley"; $email = "[email protected]"; $stmt->execute(); echo "Nowe rekordy zostały utworzone pomyślnie"; $stmt->close(); $conn->close(); ?>
Może się to wydawać skomplikowane, ale warto zrozumieć tę koncepcję. Tu znajdziesz dobry opis.
Mam też wskazówkę dla tych, którzy znają rozszerzenie PHP PDO i używają go do tworzenia przygotowanych zapytań.
Uwaga: Ostrożnie z konfiguracją PDO
Korzystając z PDO do dostępu do bazy, możemy popaść w fałszywe poczucie bezpieczeństwa. „Skoro używam PDO, nie muszę się już niczym przejmować”. Choć PDO (lub przygotowane zapytania w MySQLi) są wystarczające do ochrony przed SQL Injection, należy uważać na konfigurację. Często kopiujemy i wklejamy kod z tutoriali, nie zastanawiając się nad tym, co ustawiamy. A pewna opcja może zniweczyć nasze wysiłki:
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
Ta opcja sprawia, że PDO emuluje przygotowane zapytania, zamiast korzystać z wbudowanych funkcji bazy. W rezultacie, PHP wysyła do bazy zwykłe ciągi zapytań, nawet jeśli kod wygląda jakby używał przygotowanych zapytań. Innymi słowy, nadal jesteśmy podatni na SQL Injection.
Rozwiązanie jest proste: upewnij się, że emulacja jest wyłączona.
$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
Teraz skrypt PHP będzie zmuszony do używania przygotowanych zapytań na poziomie bazy, skutecznie chroniąc przed SQL Injection.
Ochrona za pomocą WAF
Czy wiesz, że aplikacje internetowe można chronić przed SQL Injection również za pomocą zapory aplikacji internetowych (WAF)?
Nie tylko przed SQL Injection, ale też przed wieloma innymi atakami na warstwie 7, takimi jak XSS, zepsute uwierzytelnianie, CSRF, wyciek danych. Możesz użyć własnej zapory (np. Mod Security) lub zapory opartej na chmurze.
SQL Injection w nowoczesnych frameworkach PHP
SQL Injection to tak powszechny, prosty i niebezpieczny problem, że wszystkie nowoczesne frameworki PHP mają wbudowane mechanizmy obronne. Na przykład w WordPressie mamy funkcję $wpdb->prepare(), a frameworki MVC wykonują większość pracy za nas, chroniąc nas automatycznie przed SQL Injection. W WordPressie, przygotowanie zapytań jest jawne, ale to WordPress, więc nic nie poradzimy. 🙂
Współcześni programiści internetowi często nie myślą o SQL Injection, przez co nie mają świadomości zagrożenia. Jeśli pozostawią otwarte tylne drzwi (np. parametr w $_GET i stare przyzwyczajenia do tworzenia „brudnych” zapytań), konsekwencje mogą być tragiczne. Dlatego warto poznać podstawy.
Podsumowanie
SQL Injection to nieprzyjemny atak, ale łatwo go uniknąć. Jak pokazano w artykule, wystarczy ostrożnie podchodzić do danych od użytkowników (SQL Injection to tylko jedno z zagrożeń związanych z danymi od użytkownika) i dbać o poprawność zapytań. Poza tym, nie zawsze pracujemy w bezpiecznym środowisku, więc warto być świadomym zagrożenia i nie dać się zaskoczyć.
newsblog.pl
Maciej – redaktor, pasjonat technologii i samozwańczy pogromca błędów w systemie Windows. Zna Linuxa lepiej niż własną lodówkę, a kawa to jego główne źródło zasilania. Pisze, testuje, naprawia – i czasem nawet wyłącza i włącza ponownie. W wolnych chwilach udaje, że odpoczywa, ale i tak kończy z laptopem na kolanach.