webmaster webmastering webdesign

Jesteś tutaj: webmade.org >> porady >> php + mysql

Bezpieczeństwo skryptów PHP

autor: Konrad 'knrdk' Kopciuch, ostatnia modyfikacja: 2007-09-06

szukaj: bezpieczeństwo PHP bezpieczeństwo PHP zabezpieczanie PHP zabezpieczanie PHP SQL Injection SQL Injection Cross-site scripting Cross-site scripting XSS XSS Cross-site request forgery Cross-site request forgery CSRF CSRF Session Fixation Session Fixation Session Hijacking Session Hijacking register_globals register_globals include include

Strony internetowe są szczególnie narażone na włamania, niestety wielu webmasterów zapomina o ich bezpieczeństwie. W tym krótkim artykule postaram się zapoznać wszystkich czytelników z podstawowymi typami ataków, i sposobach zabezpieczania się przed nimi.

Zmienne globalne, register_globals

Gdy w pliku konfiguracyjnym PHP, dyrektywa register_globals jest ustawiona na wartość on, możemy do zmienny odwoływać się w wygodnym, krótkim stylu. Przykładowo jeśli w formularzu mamy pole o nazwie „zmienna” to w skrypcie PHP automatycznie zostaje zainicjowana zmienna o nazwie $zmienna, początkującym webmasterom taki sposób dostępu do zmiennych może wydawać się wygodny, jednak znacząco obniża on poziom bezpieczeństwa skryptu.

Najlepiej pokazać na to prostym przykładzie, mamy plik formularz.php w którym z listy wyboru wybieramy odpowiednie zapytanie bazy danych usuwające odpowiedni artykuł, wybrana opcja jest przesyłana do pliku zapytanie.php gdzie jest wykonywane odpowiednie zapytanie do bazy danych. Użytkownik miał do wyboru opcje:

DELETE FROM articles WHERE id='XHTML'

lub

DELETE FROM srticles WHERE id='CSS'

Co się stanie gdy w skrypcie zapytanie.php korzystamy z krótkiego stylu nazywania zmiennych, a użytkownik dopisze do adresu:

http://twojadomena.eu/zapytanie.php?zapytanie=DELETE FROM ARTICLES WHERE ID='JS'

Skasowany zostanie artykuł o identyfikatorze JS. Jest to działanie do którego aktualnie zalogowany użytkownik nie miał odpowiednich uprawnień, a jednak przez błąd programisty mógł wykonać dowolne zapytanie do bazy danych.

Skoro wyjaśniliśmy już sobie że stosowanie krótkiego stylu nazywania zmiennych jest mało bezpieczne wyjaśnię co zrobić aby to bezpieczeństwo podnieść. Po pierwsze gdy mamy dostęp do plików konfiguracyjnych serwera należy ustawić wartość register_globals na off (takie jest ustawienie domyślne). Ponieważ jednak stosowanie długich nazw zmiennych jest mało wygodne, na początku każdego skryptu można zainicjować krótkie nazwy zmiennych, na przykład:

<?php
$zmienna = $_POST['zmienna'];
?>

Teraz gdy nawet zostanie metodą GET przesłana jakaś zmienna to i tak nie wpłynie to na jej wartość w skrypcie (ponieważ ma przypisaną wartość z metody POST).

Przechowanie danych w plikach tekstowych

Często, zwłaszcza mniejsze serwisy zamiast bazy SQL używają plików jednorodnych do przechowywania danych. Najczęściej jest to zwykła treść strony (artykuły, newsy), niestety zdarza się także że są tam przechowywane loginy i hasła użytkowników serwisu. Osoba znająca nazwę tego pliku, może wyświetlić jego zawartość w przeglądarce internetowej. Aby uniemożliwić dostęp do plików należy je przechowywać poza drzewem katalogów. Najczęściej wygląda to tak że logując się do serwera FTP, wszystko co jest w katalogu www jest dostępne z poziomu przeglądarki internetowej, a wszystko co znajduje się wyżej od niego jedynie poprzez klienta FTP. Jeśli z plikiem łączyliśmy się w taki sposób:

$plik = fopen("users.txt", 'rb');

to po przeniesieniu go poza katalog www (wyżej od niego), będzie trzeba łączyć się w taki sposób:

$plik = fopen("$_SERVER['DOCUMENT_ROOT']/../users.txt", 'rb');

gdzie zmienna $_SERVER['DOCUMENT_ROOT'] wskazuje na podstawowy element drzewa katalogów serwera WWW (czyli najczęściej katalog www), a ".." wskazują na katalog nadrzędny. Dzięki temu prostemu sposobowi uniemożliwimy dostęp do plików tekstowych przez WWW w sposób inny niż dołączony przez nas interfejs (skrypt).

Jeśli mamy możliwość używania plików .htaccess można też utworzyć specjalny katalog do przechowywania plików tekstowych, a w nim wpisać do pliku .htaccess

Order Allow,Deny 
Deny from All 

Skrypty będą mieć swobodny dostęp do tego katalogu, natomiast serwer zablokuje próbę wyświetlenia jego zawartości poprzez przeglądarkę internetową.

Kolejnym sposobem jest zapisanie plików z rozszerzeniem *.php, i umieszczenie treści pomiędzy znacznikami:

<? /* 
//tresc
*/ ?>

Serwer potraktuje wtedy wszystko co jest między tymi znacznikami jako komentarz PHP i w przeglądarce wyświetli pustą stronę.

W tym miejscu warto dodać że cześć webmasterów pliki includowane zapisuje z rozszerzeniem *.inc (skrót od include) , niestety niektóre serwery po wpisaniu bezpośredniego adresu takiego pliku do przeglądarki wyświetlają jego zawartość, dlatego także w tym przypadku należy korzystać z porady zamieszczonej w tym punkcie.

SQL Injection

Błąd ten umożliwia modyfikacje zapytania, czy nawet wykonanie dodatkowego zapytania (np. usuwającego rekordy). Załóżmy że mamy takie zapytanie MySQL

$zapytanie = "DELETE FROM users WHERE uzytkownik='$_GET['$del_user']'";

Wszystko przebiegnie poprawnie jeśli zmienna $_GET['$del_user'] przyjmie wartość „Nowak”, co jedna gdy jakiś złośliwy użytkownik poprzez modyfikację adresu przypisze tej zmiennej wartość "Nowak' OR '1'='1 "? Wszystkie rekordy z tabeli users zostaną usunięte.

Aby takim atakom zapobiec należy sprawdzać wszystkie dane pochodzące od użytkownika. W przypadku bazy MySQL do tego celu należy stosować funkcję (która działa podobnie jak funkcja addslashes() , aczkolwiek do tego celu nadaje się lepiej )

mysql_real_escape_string();

Ponadto w swoim zapytaniu (w przypadku MySQL), niezależnie od typu danych obejmuj je w pojedynczych cudzysłowach, należy też dokładnie filtrować wszystkie dane pochodzące od użytkowników skryptu.

Filtrowanie Danych

Wszystkie dane przesłane do skryptu przez użytkowników powinny być dokładnie filtrowane pod kątem obecności w nich kodu HTML i PHP, do tego celu należy używać funkcji:

htmlspecialchars()
strip_tags()

Warto także używać funkcji (odpowiednie dodający i usuwający znak \ przed znakami: ' , ” , / , NULL) :

addslashes() //przed zapisaniem tekstu do bazy/pliku
stripslashes() //przed wyświetleniem tekstu z bazy/pliku

Należy też sprawdzać typ danych przekazanych przez użytkownika do skryptu, jeśli oczekujemy zmiennej typu int (liczby) to sprawdzajmy czy rzeczywiście otrzymaliśmy liczbę. Służą do tego funkcje:

is_double(),is_float() //sprawdza czy zmienna jest liczbą rzeczywistą 
is_int(),is_integer() //sprawdza czy zmienna jest liczbą całkowitą
is_string() //sprawdza czy zmienna jest ciągiem znaków 

Jeśli includujemy pliki pochodzące ze zmienny $_GET należy używać instrukcji:

$plik = basename ($_GET['plik']);

funkcja ta zwraca jedynie nazwę pliku, czyli jeśli wartość zmiennej $_GET['plik'] to C:\Windows\system32\explorer.exe, to ta funkcja zwróci jedynie nazwę pliku (explorer.exe). Dzięki temu zapobiegniemy includowaniu plików pochodzących z innych serwerów niż nasz. Dobrym sposobem na zabezpieczenie się przed dołączeniem pliku który w zamyśle programisty nie miał być dołączony, jest użycie instrukcji warunkowej switch i w zależności od wartości zmiennej includowanie odpowiedniego pliku. Przykładowy kod:

switch($_GET['inc']){
case 'wyswietl.php' :
	include('wyswietl.php');
	break;
case 'logowanie.php' :
	include('logowanie.php');
	break;
default :
	echo 'Błąd: Niepoprawna nazwa pliku';
	break;
}

Dzięki temu prostemu sposobowi unikniemy dołączeniu do skryptu potencjalnie niebezpiecznego kodu.

Cross-site scripting (XSS)

Atak polegający na wstawieniu spreparowanego kody do skryptu PHP (najczęściej w HTML lub JavaScript). Często ten błąd spotykany jest w wyszukiwarkach różnych serwisów, gdzie dane pochodzące z formularza nie są sprawdzane pod kątem obecności kodu HTML. Aby się przed tym zabezpieczyć należy filtrować dane (pod kątem obecności kodu HTML) co zostało wyjaśnione w punkcie 4.

Cross-site request forgery (CSRF)

Bardzo niebezpieczny typ ataku, polegający na wykorzystywaniu uprawnień osoby atakowanej. Polega on na przesłaniu osobie posiadającej uprawnienia do wykonania danej akcji (najczęściej administrator), linka który daną akcje wykonuję. Posłużmy się przykładem do opisania tego typu ataku: administrator do nadania przywileju administratora musi kliknąć w link przy danym użytkowniku nadający te uprawnienia, internauta przygotował odpowiedni link, który wygląda w następujący sposób:

http://moje_forum.com/index.php?action=update&amp;user=Kowalski&amp;entitlements=admin 

Oczywiście próba wpisania tego adresu przez zwykłego usera zakończy się błędem (brak uprawnień), ale gdy zrobi to administrator serwisu, użytkownikowi o nicku Nowak zostaną nadane uprawnienia administratora. Nie jest przy tym wymagane kliknięcie linku przez admina, wystarczy jako adres obrazka podać ten link, i zadbać żeby administrator odwiedził odpowiednią stronę.

Aby przed tym atakiem skutecznie się zabezpieczyć należy dokładnie sprawdzać dane, przy wpisywaniu linku do obrazka sprawdzać czy rzeczywiście on do niego prowadzi, filtrować dane i nie pozwalać na wpisywanie linków powiązanych z naszym serwisem. Radykalnym, aczkolwiek bardzo skutecznym rozwiązaniem jest wyłączenia BBCode (w przypadku forów dyskusyjnych). Dobrym pomysłem jest stosowanie metody POST (zamiast GET) dla ważnych funkcji panelu administracyjnego.

Session Fixation

Kiedy podamy skryptowi PHP nieistniejący ID sesji, zostanie on utworzony. Jak hakerzy mogą to wykorzystać ? Najprościej poprzez dodanie do adresu:

http://www.mojadomena.pl/index.php?PHPSESSID=xxxxxxxxxxxxxxxx

Następnie ten adres przysyłamy do ofiary, kiedy ta się zaloguje, my po odświeżeniu strony mamy takie same uprawnienia jak ofiara naszego ataku.

Żeby się przed tym zabezpieczyć należy w skrypcie dodać:

<?php 
session_start();        
if (!isset($_SESSION['sprawdz'])){
 	session_regenerate_id();
	$_SESSION['sprawdz'] = true;
}
?>

Dzięki temu serwer sam utworzy odpowiednią sesję, nie pozwalając użytkownikowi na nadanie jej własnego identyfikatora.

Session Hijacking

Typ ataku powiązany z poprzednim, kiedy ktoś ma wyłączone ciasteczka w przeglądarce to identyfikator sesji jest dołączany do adresu, gdy zalogowany użytkownik prześle ten identyfikator innemu użytkownikowi , to ten otrzymuje możliwość używania sesji zalogowanego użytkownika. Aby i przed tym atakiem się zabezpieczyć należy do poprzedniego skryptu dodać:

<?php 
session_start();        
if (!isset($_SESSION['sprawdz'])){
 	session_regenerate_id();
	$_SESSION['sprawdz'] = true;
	$_SESSION['adres_ip'] = $_SERVER['REMOTE_ADDR'];
}
if($_SESSION['adres_ip'] !== $_SERVER['REMOTE_ADDR']){
echo 'Błąd: Próba przejęcia sesji';
exit;
}
?>

Podsumowanie

Mam nadzieję że w tym artykule udało mi się omówić podstawy najczęściej występujących typów ataków i błędów programistycznych. Podsumowując to co napisałem można wywnioskować (i słusznie) że najważniejsze jest filtrowanie wszystkich danych pochodzących od użytkowników, nie wolno też programować w przekonaniu że i tak nikt nie będzie chciał się na naszą stronę włamać, bo z całą pewnością (zwłaszcza jeśli witryna będzie popularna) znajdą się tacy ludzie.

Konrad 'knrdk' Kopciuch

Osoby czytające tę publikację przeglądały również:

Chcesz zostać programistą?

Poznaj 6 kroków do efektywnej
nauki programowania!

 

valid XHTML
valid CSS
© 2004-2008 copyright by webmade.org