Rozdział 3. Zabezpieczanie baz danych i SQL

13 marca 2012
1 gwiadka2 gwiazdki3 gwiazdki4 gwiazdki5 gwiazdek

Ujawnione dane uwierzytelniające

Większość aplikacji PHP korzysta z baz danych. Najczęściej aplikacja łączy się z wybraną bazą, a następnie dokonuje w niej uwierzytelnienia przy użyciu danych uwierzytelniających:

<?php

$host = 'example.org';
$username = 'nazwaużytkownika';
$password = 'hasło';

$db = mysql_connect($host, $username, $password);

?>

Kod ten może znajdować się w pliku o nazwie db.inc dołączanym do aplikacji zawsze, gdy potrzebne jest połączenie z bazą danych. Jest to wygodne rozwiązanie, które pozwala na przechowywanie danych uwierzytelniających w jednym pliku.

Jeśli jednak plik taki jest przechowywany w katalogu głównym dokumentów, mogą wystąpić pewne problemy. Technika ta jest popularna, ponieważ znacznie ułatwia korzystanie z instrukcji include i require, ale w pewnych sytuacjach może umożliwić ujawnienie danych uwierzytelniających.

Przypomnijmy, że wszystko, co znajduje się w katalogu głównym dokumentów ma określony adres URL. Jeśli na przykład ścieżką do katalogu głównego dokumentów jest /usr/local/apache/htdocs, to plik znajdujący się w katalogu /usr/local/apache/htdocs/inc/db.inc ma adres URL http://example.org/inc/db.inc.

Biorąc pod uwagę fakt, że większość serwerów sieciowych pliki z rozszerzeniem .inc serwuje jako zwykły tekst, nietrudno dostrzec, jak bardzo ta metoda jest niebezpieczna. Mówiąc ogólnie, istnieje ryzyko ujawnienia całości kodu znajdującego się w tych plikach, a ujawnienie danych uwierzytelniających jest po prostu wyjątkowo groźne.

Oczywiście prostym rozwiązaniem jest przeniesienie wszystkich tych modułów poza katalog główny i jest to bardzo dobry sposób. Instrukcje include i require przyjmują jako argumenty także ścieżki odnoszące się do systemu plików, a więc nie ma potrzeby udostępniać modułów poprzez URL. Jest to tylko niepotrzebne ryzyko.

Jeśli nie ma innej możliwości i pliki te muszą znajdować się w katalogu głównym, to można wpisać poniższe dyrektywy w pliku httpd.conf (dotyczy serwera Apache):

<Files ~ ".inc$">
    Order allow,deny
    Deny from all
</Files>

Moduły nie powinny być przetwarzane przez mechanizm PHP. Dotyczy to zarówno zmiany ich rozszerzeń .php jak i użycia dyrektyw AddType, aby pliki z rozszerzeniem .inc były traktowane jako pliki PHP. Wykonywanie kodu poza kontekstem może być bardzo niebezpieczne, ponieważ jest nieprzewidywalne i może zwracać niespodziewane wyniki. Jeśli jednak w modułach znajdują się np. tylko instrukcje przypisania wartości zmiennym, ryzyko to jest znacznie mniejsze.

Moja ulubiona metoda ochrony danych dostępu do baz danych jest opisana w książce PHP. Receptury. Wydanie II (Helion 2007) Davida Sklara i Adama Trachtenberga. Utwórz plik, /ścieżka/do/tajnych-danych, który może odczytać tylko root (nie nobody):

SetEnv DB_USER "użytkownik"
SetEnv DB_PASS "hasło"

Zdefiniuj dołączanie tego pliku w pliku httpd.conf w następujący sposób:

Include "/ścieżka/do/tajnych-danych"

Teraz możesz używać w swoim kodzie wartości $_SERVER['DB_USER'] i $_SERVER['DB_PASS']. Nie tylko nigdy więcej nie będziesz musieć wpisywać nazwy użytkownika i hasła w swoich skryptach, lecz również serwer nie będzie mógł odczytać pliku tajnych-danych , dzięki czemu nikt nie będzie mógł napisać skryptu odczytującego Twoje dane uwierzytelniające (niezależnie od języka). Uważaj tylko, aby nie ujawnić tych zmiennych za pomocą phpinfo(), print_r($_SERVER) albo czegoś innego w tym rodzaju.

SQL Injection

Obrona przed atakami typu SQL injection jest bardzo prosta, a mimo to wciąż są aplikacje, które są na nie podatne. Rozważmy poniższy przykład SQL:

<?php

$sql = "INSERT
        INTO   users (reg_username,
                      reg_password,
                      reg_email)
        VALUES ('{$_POST['reg_username']}',
                '$reg_password',
                '{$_POST['reg_email']}')";

?>

Użyte w tym skrypcie zapytanie zawiera zmienną $_POST, co od razu budzi podejrzenia.

Załóżmy, że tworzy ono nowe konto. Użytkownik podaje wybraną nazwę użytkownika i adres e-mail. Aplikacja rejestracyjna generuje tymczasowe hasło i wysyła je na podany adres e-mail w celu jego weryfikacji. Wyobraźmy sobie, że użytkownik wpisuje poniższą nazwę użytkownika:

bad_guy', 'mypass', ''), ('good_guy

Z pewnością to nie wygląda na poprawną nazwę użytkownika, ale jeśli nie zaimplementuje się żadnego filtru, to aplikacja nic nie zauważy. Jeśli zostanie podany poprawny adres e-mail (np. shiflett@php.net), a aplikacja wygeneruje hasło 1234, to powstanie następujące zapytanie SQL:

<?php

$sql = "INSERT
        INTO   users (reg_username,
                      reg_password,
                      reg_email)
        VALUES ('bad_guy', 'mypass', ''), ('good_guy',
                '1234',
                'shiflett@php.net')"; ?>

Przez ten podstęp, zamiast utworzenia jednego konta (good_guy) z poprawnym adresem e-mail, aplikacja utworzy dwa konta i użytkownik dostarczył wszystkie dane konta bad_guy.

Mimo iż ten konkretny przykład wydaje się nieszkodliwy, jest jasne, że jeśli atakujący otrzyma możliwość modyfikowania zapytań SQL, to z pewnością wykorzysta to do znacznie gorszych celów.

Na przykład w niektórych bazach danych możliwe jest wysłanie wielu zapytań w jednym wywołaniu. Użytkownik może to wykorzystać kończąc istniejące zapytanie średnikiem i wpisując za nim własne zapytanie.

Baza danych MySQL do niedawna nie zezwalała na wykonywanie wielu zapytań na raz, a więc w jej przypadku ryzyka tego nie było. W nowszych wersjach tej bazy jest to jednak już możliwe, ale rozszerzenie PHP o nazwie (ext/mysqli) wymaga, aby do wysyłania wielu żądań używać osobnej funkcji o nazwie (mysqli_multi_query() zamiast mysqli_query()). Zezwolenie na wykonanie tylko jednego zapytania na raz jest bezpieczniejszym rozwiązaniem, ponieważ ogranicza możliwości atakującego.

Ochrona przed atakami SQL injection jest łatwa:

  • Filtruj dane.

    Tego po prostu nie da się przecenić. Dzięki zastosowaniu dobrego filtra znika większość luk zabezpieczeń, a część zostaje praktycznie wyeliminowana.

  • Umieszczaj dane w cudzysłowach.

    Jeśli baza danych na to pozwala (MySQL pozwala), wszystkie wartości w instrukcjach SQL umieszczaj w pojedynczych cudzysłowach, niezależnie od typu danych.

  • Stosuj symbole zastępcze w danych.

    Czasami poprawne dane mogą przypadkowo kolidować z formatem instrukcji SQL. Użyj funkcji mysql_escape_string() lub innej właściwej dla bazy, której używasz. Jeśli taka nie istnieje, możesz w ostateczności poratować się funkcją addslashes().

Autor: PHP Security Consortium

Źródło: http://phpsec.org/projects/guide/3.html

Tłumaczenie: Łukasz Piwko

Treść tej strony dostępna jest na zasadach licencji CC BY-NC-SA 2.0

Dyskusja

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *