Co to jest SQL Injection i jak mu zapobiegać w aplikacjach PHP?

Myślisz więc, że Twoja baza danych SQL jest wydajna i zabezpieczona przed natychmiastowym zniszczeniem? Cóż, SQL Injection się nie zgadza!

Tak, mówimy o natychmiastowej destrukcji, ponieważ nie chcę otwierać tego artykułu zwykłą kiepską terminologią „zaostrzania zabezpieczeń” i „zapobiegania złośliwemu dostępowi”. SQL Injection to tak stara sztuczka w książce, że każdy, każdy programista, wie o tym bardzo dobrze i doskonale wie, jak temu zapobiec. Z wyjątkiem tego jednego dziwnego razu, kiedy się poślizgną, a wyniki mogą być katastrofalne.

Jeśli już wiesz, czym jest SQL Injection, możesz przejść do drugiej części artykułu. Ale dla tych, którzy dopiero zaczynają przygodę z tworzeniem stron internetowych i marzą o objęciu wyższych stanowisk, przyda się wprowadzenie.

Co to jest wstrzykiwanie SQL?

Kluczem do zrozumienia SQL Injection jest jego nazwa: SQL + Injection. Słowo „wstrzyknięcie” nie ma tutaj żadnych konotacji medycznych, ale raczej użycie czasownika „wstrzyknięcie”. Razem te dwa słowa oddają ideę umieszczenia SQL w aplikacji internetowej.

Umieszczanie SQL w aplikacji internetowej. . . hmmm . . . Czyż nie tym właśnie się zajmujemy? Tak, ale nie chcemy, aby atakujący sterował naszą bazą danych. Zrozummy to na przykładzie.

Załóżmy, że budujesz typową witrynę PHP dla lokalnego sklepu internetowego, więc decydujesz się dodać taki formularz kontaktowy:

<form action="record_message.php" method="POST">
  <label>Your name</label>
  <input type="text" name="name">
  
  <label>Your message</label>
  <textarea name="message" rows="5"></textarea>
  
  <input type="submit" value="Send">
</form>

Załóżmy, że plik send_message.php przechowuje wszystko w bazie danych, aby właściciele sklepów mogli później czytać wiadomości użytkowników. Może mieć taki kod:

<?php

$name = $_POST['name'];
$message = $_POST['message'];

// check if this user already has a message
mysqli_query($conn, "SELECT * from messages where name = $name");

// Other code here

Więc najpierw próbujesz sprawdzić, czy ten użytkownik ma już nieprzeczytaną wiadomość. Zapytanie SELECT * z wiadomości, gdzie nazwa = $nazwa wydaje się dość proste, prawda?

ZŁO!

W naszej niewinności otworzyliśmy drzwi do natychmiastowego zniszczenia naszej bazy danych. Aby tak się stało, atakujący musi spełnić następujące warunki:

  • Aplikacja działa na bazie danych SQL (dzisiaj prawie każda aplikacja jest)
  • Bieżące połączenie z bazą danych ma uprawnienia „edytuj” i „usuwaj” w bazie danych
  • Nazwy ważnych tabel można odgadnąć

Trzeci punkt oznacza, że ​​teraz, gdy atakujący wie, że prowadzisz sklep e-commerce, najprawdopodobniej przechowujesz dane zamówienia w tabeli zamówień. Uzbrojony w to wszystko, wszystko co atakujący musi zrobić, to podać to jako swoje imię:

Joe; skrócić zamówienia;? Tak jest! Zobaczmy, czym stanie się zapytanie, gdy zostanie wykonane przez skrypt PHP:

WYBIERZ * Z wiadomości WHERE imię = Jan; skrócić zamówienia;

Dobra, pierwsza część zapytania zawiera błąd składniowy (brak cudzysłowów wokół „Joe”), ale średnik zmusza silnik MySQL do rozpoczęcia interpretacji nowego: skrócenia rozkazów. Tak po prostu, za jednym zamachem, cała historia zamówień zniknęła!

Teraz, gdy już wiesz, jak działa SQL Injection, nadszedł czas, aby przyjrzeć się, jak go zatrzymać. Dwa warunki, które muszą zostać spełnione, aby pomyślnie wstrzyknąć SQL, to:

  • Skrypt PHP powinien mieć uprawnienia do modyfikowania/usuwania w bazie danych. Myślę, że dotyczy to wszystkich aplikacji i nie będziesz w stanie ustawić aplikacji tylko do odczytu. 🙂 I zgadnij co, nawet jeśli usuniemy wszystkie uprawnienia do modyfikacji, wstrzyknięcie SQL może nadal pozwolić komuś na uruchamianie zapytań SELECT i przeglądanie całej bazy danych, w tym wrażliwych danych. Innymi słowy, zmniejszanie poziomu dostępu do bazy danych nie działa, a aplikacja i tak tego potrzebuje.
  • Dane wprowadzone przez użytkownika są przetwarzane. Jedynym sposobem, w jaki może działać iniekcja SQL, jest akceptacja danych od użytkowników. Po raz kolejny niepraktyczne jest zatrzymywanie wszystkich danych wejściowych dla aplikacji tylko dlatego, że martwisz się iniekcją SQL.
  • Zapobieganie iniekcji SQL w PHP

    Teraz, biorąc pod uwagę, że połączenia z bazą danych, zapytania i dane wprowadzane przez użytkowników są częścią życia, jak możemy zapobiec iniekcji SQL? Na szczęście jest to dość proste i można to zrobić na dwa sposoby: 1) oczyścić dane wprowadzane przez użytkownika i 2) użyć przygotowanych instrukcji.

    Oczyść dane wprowadzane przez użytkownika

    Jeśli używasz starszej wersji PHP (5.5 lub starszej, a zdarza się to często na współdzielonym hostingu), mądrze jest uruchamiać wszystkie dane wprowadzane przez użytkownika za pomocą funkcji o nazwie mysql_real_escape_string(). Zasadniczo to, co robi, usuwa wszystkie znaki specjalne w ciągu, dzięki czemu tracą znaczenie, gdy są używane przez bazę danych.

    Na przykład, jeśli masz ciąg taki jak I’m string, znak pojedynczego cudzysłowu (’) może zostać użyty przez osobę atakującą do manipulowania tworzonym zapytaniem do bazy danych i spowodowania iniekcji SQL. Uruchomienie go przez mysql_real_escape_string() tworzy ciąg I’m, który dodaje ukośnik odwrotny do pojedynczego cudzysłowu, uciekając przed nim. W rezultacie cały ciąg jest teraz przekazywany do bazy danych jako nieszkodliwy ciąg, zamiast brać udział w manipulowaniu zapytaniami.

    Podejście to ma jedną wadę: jest to bardzo, bardzo stara technika, która pasuje do starszych form dostępu do bazy danych w PHP. Od PHP 7 ta funkcja już nawet nie istnieje, co prowadzi nas do naszego następnego rozwiązania.

    Skorzystaj z gotowych oświadczeń

    Przygotowane zestawienia to sposób na bezpieczniejsze i bardziej niezawodne wykonywanie zapytań do baz danych. Pomysł polega na tym, że zamiast wysyłać surowe zapytanie do bazy danych, najpierw informujemy bazę danych o strukturze zapytania, które będziemy wysyłać. To właśnie rozumiemy przez „przygotowanie” oświadczenia. Po przygotowaniu instrukcji przekazujemy informacje jako sparametryzowane dane wejściowe, aby baza danych mogła „wypełnić luki” poprzez wpięcie danych wejściowych do struktury zapytania, którą wysłaliśmy wcześniej. Odbiera to jakąkolwiek specjalną moc, jaką mogą mieć dane wejściowe, powodując, że są one traktowane jako zwykłe zmienne (lub ładunki, jeśli wolisz) w całym procesie. Oto jak wyglądają przygotowane wyciągi:

    <?php
    $servername = "localhost";
    $username = "username";
    $password = "password";
    $dbname = "myDB";
    
    // Create connection
    $conn = new mysqli($servername, $username, $password, $dbname);
    
    // Check connection
    if ($conn->connect_error) {
        die("Connection failed: " . $conn->connect_error);
    }
    
    // prepare and bind
    $stmt = $conn->prepare("INSERT INTO MyGuests (firstname, lastname, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $firstname, $lastname, $email);
    
    // set parameters and execute
    $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 "New records created successfully";
    
    $stmt->close();
    $conn->close();
    ?>

    Wiem, że ten proces wydaje się niepotrzebnie skomplikowany, jeśli nie znasz przygotowanych oświadczeń, ale koncepcja jest warta wysiłku. Oto niezły wstęp do niego.

    Dla tych, którzy znają już rozszerzenie PHP PDO i używają go do tworzenia przygotowanych zestawień, mam małą radę.

    Ostrzeżenie: Zachowaj ostrożność podczas konfigurowania PDO

    Korzystając z PDO w celu uzyskania dostępu do bazy danych, możemy dać się wciągnąć w fałszywe poczucie bezpieczeństwa. „Ach, cóż, używam PDO. Teraz nie muszę myśleć o niczym innym” — tak ogólnie wygląda nasze myślenie. To prawda, że ​​PDO (lub instrukcje przygotowane przez MySQLi) są wystarczające, aby zapobiec wszelkiego rodzaju atakom typu SQL injection, ale należy zachować ostrożność podczas ich konfigurowania. Często wystarczy skopiować i wkleić kod z samouczków lub wcześniejszych projektów i przejść dalej, ale to ustawienie może cofnąć wszystko:

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

    To ustawienie mówi PDO, aby emulowało przygotowane instrukcje, a nie faktycznie korzystało z funkcji przygotowanych instrukcji w bazie danych. W rezultacie PHP wysyła proste ciągi zapytań do bazy danych, nawet jeśli twój kod wygląda tak, jakby tworzył przygotowane instrukcje i ustawiał parametry i tak dalej. Innymi słowy, jesteś tak samo podatny na iniekcję SQL jak wcześniej. 🙂

    Rozwiązanie jest proste: upewnij się, że ta emulacja jest ustawiona na fałsz.

    $dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

    Teraz skrypt PHP jest zmuszony używać przygotowanych instrukcji na poziomie bazy danych, zapobiegając wszelkiego rodzaju iniekcji SQL.

    Zapobieganie używaniu WAF

    Czy wiesz, że możesz również chronić aplikacje internetowe przed wstrzykiwaniem SQL za pomocą WAF (zapory aplikacji internetowych)?

    Cóż, nie tylko iniekcja SQL, ale wiele innych luk w warstwie 7, takich jak skrypty między witrynami, zepsute uwierzytelnianie, fałszowanie między witrynami, ekspozycja danych itp. Możesz użyć hostowania własnego, takiego jak Mod Security, lub opartego na chmurze, jak poniżej.

    SQL injection i nowoczesne frameworki PHP

    Wstrzyknięcie SQL jest tak powszechne, tak łatwe, tak frustrujące i tak niebezpieczne, że wszystkie nowoczesne frameworki PHP mają wbudowane środki zaradcze. Na przykład w WordPressie mamy funkcję $wpdb->prepare() , podczas gdy jeśli używasz frameworka MVC, wykonuje on całą brudną robotę za Ciebie i nie musisz nawet myśleć o zapobieganiu iniekcji SQL. To trochę irytujące, że w WordPress musisz jawnie przygotowywać instrukcje, ale hej, mówimy o WordPressie. 🙂

    W każdym razie chodzi mi o to, że współczesna rasa programistów internetowych nie musi myśleć o iniekcji SQL, w wyniku czego nawet nie są świadomi takiej możliwości. W związku z tym, nawet jeśli pozostawią otwarte jedno tylne wejście w swojej aplikacji (być może jest to parametr zapytania $_GET i stare nawyki uruchamiania brudnego zapytania), wyniki mogą być katastrofalne. Dlatego zawsze lepiej jest poświęcić trochę czasu, aby zagłębić się w podstawy.

    Wniosek

    SQL Injection to bardzo paskudny atak na aplikację internetową, ale można go łatwo uniknąć. Jak widzieliśmy w tym artykule, zachowanie ostrożności podczas przetwarzania danych wprowadzanych przez użytkownika (nawiasem mówiąc, SQL Injection nie jest jedynym zagrożeniem, jakie niesie ze sobą obsługa danych wprowadzanych przez użytkownika) i wysyłanie zapytań do bazy danych to wszystko. To powiedziawszy, nie zawsze pracujemy w bezpiecznym środowisku sieciowym, więc lepiej być świadomym tego typu ataków i nie dać się nabrać.