1
Referat
Temat: Apache i skrypty PHP
Wykonali:
Zbigniew Kiliański
Paweł Ścipień
Artur Zając
Prowadzący zajęcia:
mgr inż. Bogusław Juza
Data:
28-05-2002
Rok studiów:
IV
Rok akademicki:
2001/2002
2
Spis tresci:
1.
Wstęp
.................................................................................................................................................................. 3
2.
Metody uruchomienia programu napisanego w PHP
........................................................................................... 4
2.1.
Obsługa PHP w kompilowana w Apache statycznie,
.................................................................................. 4
2.2.
Moduł PHP ładowany dynamicznie do Apache,
......................................................................................... 5
2.3.
Skrypty PHP uruchamiane jako tzw. skrypty CGI,
...................................................................................... 7
2.4.
Skrypty PHP uruchamiane z wykorzystaniem biblioteki "fastcgi",
............................................................. 9
3.
Tryb safe mode
.................................................................................................................................................. 10
4.
Analiza kodu suexec
.......................................................................................................................................... 12
5.
Pomysły na rozbudowanie kodu suexec
............................................................................................................ 21
5.1.
Modyfikacja suexec tak, aby uruchamiać pliki PHP z uprawnieniami właściciela pliku:
......................... 21
5.2.
4.2 Obsługa limitów setrlimit().
............................................................................................................... 22
6.
Wnioski
............................................................................................................................................................. 28
7.
Literatura
........................................................................................................................................................... 29
3
1. Wstęp
PHP jest szeroko stosowanym językiem, który służy do tworzenia witryn z dynamicznie,
zmieniającymi się stronami WWW. Język ten umożliwia generowanie stron po stronie serwera i
przekazywanie efektów działania programów w postaci kodu HTML interpretowanego przez
przeglądarki po stronie klienta. Swoją popularność język ten zawdzięcza swojej prostocie,
bogatym zbiorze funkcji operujących na danych, dużym możliwością podłączenia się z bazami
danych i doskonalej dokumentacji dostępnej na firmowej stronie WWW.
Strony WWW udostępniane są przez demona HTTPD działającego na serwerze.
Najpopularniejszym demonem świadczącym tego typu usługi jest demon Apache dostępny na
stronie (www.apache.org). Demon ten odpowiedzialny jest za udostępnianie stron klientom, oraz
za uruchamianie skryptów generujących te strony.
Ze wzglądów bezpieczeństwa serwer Apache nie jest uruchamiany z uprawnieniami
administratora systemu. Użytkownik, z którego uprawnieniami działa serwer Apache zwykle nie
posiada własnych plików i nie ma możliwości zapisu do plików, które nie są "zapisywalne" przez
każdego użytkownika. Taka konfiguracja systemu poprawia znacznie bezpieczeństwo aplikacji
działających na serwerze, ponieważ nawet złamanie demona nie daje możliwości zapisu lub
odczytu plików na serwerze, które maja zabezpieczony dostęp dla użytkowników innych niż
właściciel główny lub grupowy.
Czasami jednak istnieje konieczność uruchomienia skryptów, które posiadają uprawnienia
większe lub inne niż użytkownik, który jest właścicielem procesów Apache'a, ponieważ należy
np. zabezpieczyć dostęp do odczytu niektórych plików np. z hasłami do bazy danych. Problem
ten rozwiązuje tzw. wrapper o nazwie suexec. Plik programu suexec ma ustawiony bit suid dla
właściciela pliku i właścicielem pliku jest administrator systemu. Uruchomienie skryptów
wymagających specjalnych uprawnień polega na wywołaniu wrappera suexec przez proces
Apache, a następnie uruchomieniu przez wrappera właściwego skryptu. Dostęp do plików innych
użytkowników może być także zabezpieczony przez wykorzystanie trybu „safe mode” i
właściwej konfiguracji serwera.
Istnieje kilka sposobów uruchamiania skryptów PHP. Każdy ze sposobów ma swoje zalety
i wady, z którymi trzeba się pogodzić i w zależności od potrzeby należy wybrać odpowiednią
4
metodę uruchamiania skryptów. Analiza problemów związanych z uruchomieniem skryptów
PHP zajmuje się ten referat.
2. Metody uruchomienia programu napisanego w PHP
Istnieje wiele sposobów uruchamiania skryptów PHP. Każdy z nich umożliwia uzyskanie
pewnych właściwości, zazwyczaj kosztem innych. Najważniejszymi metodami uruchamiania
skryptów PHP są:
2.1. Obsługa PHP w kompilowana w Apache statycznie,
Obsługę PHP można włączyć do kodów serwera Apache podczas kompilacji serwera (jest
to tzw. kompilacja statyczna). Zaleta takiej konfiguracji jest względnie bardzo duża szybkość
działania skryptów PHP. Serwer taki konfiguruje się w ten sposób, ze najpierw kompiluje się
PHP włączając informacje o ścieżce do kodów źródłowych Apache'a, a następnie kompiluje się
Apache'a.
Kompilacja statyczna Apache i PHP
$ gunzip -c apache_1.3.x.tar.gz | tar xf -
$ cd apache_1.3.x
$ ./configure
$ cd ..
$ gunzip -c php-4.2.x.tar.gz | tar xf -
$ cd php-4.2.x
$ ./configure --with-mysql --with-apache=../apache_1.3.x \
--enable-track-vars
$ make
$ make install
$ cd ../apache_1.3.x
$ ./configure --prefix=/www --activate-module=
src/modules/php4/libphp4.a
$ make
$ cd ../php-4.2.x
5
$ cp php.ini-dist /usr/local/lib/php.ini
Konfiguracja php polega na edycji pliku /usr/local/lib/php.ini.
Następnie należy powiązać rozszerzenie pliku ze skryptem (np. .php) z interpreterem oraz
(opcjonalnie) rozszerzenie pliku (np. .phps) z interpreterem, który koloruje i wyświetla kod PHP
w przeglądarce. W tym celu należy zmodyfikować plik konfiguracyjny Apache httpd.conf lub
srm.conf i dodać:
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
W wyniku kompilacji Apache'a tworzony zostaje serwer, który posiada "zaszytą" w sobie
obsługę PHP. Korzystając z tej metody można uzyskać najszybsza z możliwych obsługę
skryptów PHP. Szybkość ta wynika z tego, że podczas wykonywania skryptów PHP nie trzeba
doładowywać bibliotek dynamicznych, ani tworzyć procesów, które uruchamiają się długo w
stosunku do całkowitego czasu działania skryptów.
Wadą tej metody jest bardzo mała elastyczność konfiguracji. Chcąc zmienić wersje
interpretera PHP musimy przekompilować cały serwer Apache. Wada także jest fakt, ze skrypty
PHP uruchamiane tą drogą muszą dziedziczyć uprawnienia procesu, który je wywołuje, a w tym
wypadku musi to być Apache. Nie ma więc możliwości zmiany uprawnień skryptów.
Ograniczenie to można ominąć uruchamiając oddzielny serwer Apache na innym porcie
przez innego użytkownika i przekierowując niektóre zapytania na ten serwer (wykorzystując
dyrektywę Redirect).
2.2. Moduł PHP ładowany dynamicznie do Apache,
Druga prezentowana metoda jest kompilacja obsługi PHP jako moduł dynamicznie
ładowany podczas uruchamiania skryptów PHP. Najpierw kompiluje się serwer Apache,
włączając opcje obsługi dynamicznie ładowanych modułów. W tym celu podczas
konfigurowania źródeł Apache'a należy dodać do polecenia ./configure opcje
--enable_module=so
.
Następnie należy dokonać kompilacji modułu PHP z wykorzystaniem programu apxs,
który umożliwia automatyczną konfiguracje modułu w zależności od wykorzystywanej
konfiguracji Apache (np. opcja -DEAPI, gdy korzystamy z modułu modssl).
6
Jeżeli mamy już zainstalowany serwer Apache wraz z obsługa dynamicznych modułów
PHP kompiluje się w nastepujący sposób:
$ gunzip -c php-4.2.x.tar.gz | tar xf -
$ cd php-4.2.x
$ ./configure --with-mysql --with-apxs
$ make
$ make install
Po skompilowaniu modułu PHP program instalacyjny umieszcza go w stosownym miejscu
(tj. w katalogu libexec/ pod nazwą libphp4.so).
Nast
ępnie należy skopiować i z edytować plik php.ini
$ cd ../php-4.2.x
$ cp php.ini-dist /usr/local/lib/php.ini
Następnie należy zmodyfikować plik httpd.conf.
Modyfikacja pliku httpd.conf polega na umieszczeniu dyrektyw ładujących moduł
PHP do Apacha oraz umieszczeniu dyrektyw informujących serwer, ze pliki o zadanym
rozszerzeniu powinny być interpretowane przez PHP.
Wiązanie plików z rozszerzeniem .php i .phps (rozszerzenia mogą być dowolne) z
interpreterem:
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
Ładowanie modułu PHP do serwera Apache (wiersze te powinny być automatycznie
dodane, przez apxs).
LoadModule php4_module libexec/libphp4.so
AddModule mod_php4.c
W przeciwieństwie do poprzedniego rozwiązania, to daje możliwość zmiany ustawień
modułu PHP (np. dodanie obsługi GD) bez konieczności ponownej kompilacji kodów serwera
Apache. Zaleta ta rekompensuje bardzo niewielką (około 5%) utratę szybkości interpretacji
skryptów. Poważną wadą rozwiązania jest brak możliwości uruchamiania skryptów z
uprawnieniami właściciela, ponieważ skrypty PHP są uruchamiane przez użytkownika, z
uprawnieniami serwera Apache.
7
Wadę tą da się ominąć uruchamiając serwer Apache na innym porcie przez odpowiedniego
użytkownika (podobnie jak miało to miejsce w poprzednio opisanej metodzie).
2.3. Skrypty PHP uruchamiane jako tzw. skrypty CGI,
Jedna z najczęściej używanych metod uruchamiania skryptów po stronie serwera jest
metoda zwana CGI. CGI jest specyfikacja interfejsu wymiany informacji pomiędzy aplikacją
serwera np. Apache, a programem/ skryptem napisanym przez użytkownika, którego głównym
(najczęstszym) zadaniem jest wygenerowanie strony HTML, która jest następnie przekazywana
użytkownikowi. Jedynym wymaganiem stawianym przez specyfikację CGI odnośnie języków
programowania jest to, ze program napisany w stosownym języku musi umieć odczytać dane ze
standardowego wejścia i musi umieć zapisywać dane na standardowym wyjściu. Ponieważ
praktycznie wszystkie języki spełniają te wymagania, istnieje możliwość napisania skryptów CGI
w dowolnym języku.
Główną wada skryptów CGI jest czas inicjacji programu i obciążenie systemu tym
spowodowane. Skrypt CGI, który jest uruchamiany przez program wymaga utworzenia tzw.
ciężkiego procesu. Narzut czasowy związany z tą czynnością jest niejednokrotnie większy niż
całkowity czas pracy skryptu, dlatego rozwiązanie to nie jest rozwiązaniem optymalnym w
przypadku skryptów PHP.
Czasem istnieje jednak konieczność uruchamiania skryptów PHP jako procesy CGI.
Najważniejsza zaleta tego rozwiązania jest możliwość uruchamiania skryptów z uprawnieniami
właściciela przy wykorzystaniu programu suexec.
Kompilacja kodów źródłowych PHP jest prostsza niż w przypadku wersji statycznie
wkompilowanej w kody Apacha i wersji dynamicznie ładowanego modułu, ponieważ
standardowo PHP kompiluje się jako interpreter, który powinien być uruchamiany jako CGI.
Kompilacje PHP jako interpreter CGI i 'uruchamiany z shella' przeprowadza się
następująco:
1) ./configure (parametry dołączanych bibliotek do PHP)
2) make
3) make install
8
W wyniku tych komend zostanie utworzony plik php, który jest interpreterem skryptów
PHP. Następnie kopiuje się go do katalogu, który się znajduje np. w zmiennej PATH lub
katalogu, który przechowuje skrypty CGI (np. jeden z aliasów /cgi-bin/).
Istnieje także wiele sposobów skonfigurowania serwera Apache, tak aby uruchamiał
skrypty PHP jako CGI.
Najprostszym z nich jest nazywanie skryptów z użyciem rozszerzenia .cgi lub innego
wyspecyfikowanego przez następujacą dyrektywę w httpd.conf;
AddHandler cgi-script .cgi
Zawartość plików uruchamianych w ten sposób powinna wyglądać na przykład
następująco:
#!/usr/local/bin/php
<? phpinfo(); ?>
Wadą tego rozwiązania jest konieczność umieszczania ścieżki w pierwszym wierszu
skryptu. Jest to pewnym kłopotem, jeśli chcemy zmienić uprawnienia napisanych już skryptów
ponieważ zmuszeni jesteśmy wtedy do modyfikacji każdego skryptu. W jednym z następnych
punktów opisano modyfikację programu suexec, która omija tą wadę.
Pewnym rozwiązaniem jest dodanie nowego handlera do rozszerzenia pliku i powiązania z
nim akcji:
AddHandler php-script php
Action php-script /cgi-bin/php
Niestety sposób ten, nie jest obsługiwany przez suexec. Wiec bez modyfikacji kodów
programu suexec i Apache nie jest uruchomienie skryptów z uprawnieniami właściciela ( w ten
sposób).
Istnieje możliwość zmiany właściciela wykonywanych skryptów na wirtualnych domenach.
Konfiguracja tego sposobu zmiany użytkownika polega na zadeklarowaniu użytkownika i grupy
(który jest właścicielem skryptów).
<VirtualHost 192.168.1.1:80>
9
ServerName www.test.com.pl
DocumentRoot /www/testcom
ErrorLog /www/log/testcom_error
TransferLog /www/log/testcom_transfer
User usrtest
Group grptest
</VirtualHost>
2.4. Skrypty PHP uruchamiane z wykorzystaniem biblioteki "fastcgi",
FastCGI jest biblioteka i modułem, który umożliwia przyspieszenie uruchamiania skryptów
CGI na serwerze. Więc przy użyciu FastCGI można częściowo rozwiązać problem z dużym
narzutem związanym z nakładem mocy obliczeniowej przeznaczonej na tworzenie procesu.
Przyspieszenie osiągnięte dzięki FastCgi wynika z tego, że moduł ten wczytuje z dysku i
inicjuje skrypty raz i przetrzymuje je w pamięci. Dzięki temu nie ma potrzeby tworzenia nowego
procesu dla każdego zapytania oddzielnie. Minimalizuje to w znaczący sposób obciążenie
komputera i przyspiesza czas reakcji na zapytanie.
Konfiguracja PHP różni się od wersji CGI jedynie opcja dodawana podczas
konfigurowania źródeł PHP. (moduł i biblioteka FastCgi powinna być już wcześniej
zainstalowana).
./configure --with-fastcgi
FastCGI jako moduł jest włączany do Apacha poprzez stosowne dyrektywy w httpd.conf.
LoadModule fastcgi_module libexec/mod_fastcgi.so
AddModule mod_fastcgi.c
Obsługę PHP jako FastCGI konfiguruje się wewnątrz pliku httpd.conf poprzez dodanie
dyrektyw:
Alias /fcgi-bin/ /www/fcgi-bin/
FastCgiSuexec /www/bin/suexec
AddType application/x-httpd-fcgi .fcgi
AppClass /www/htdocs/fcgi-bin/php.fcgi -processes 4
AddType application/x-httpd-fphp .fhtml
10
Action application/x-httpd-fphp /www/htdocs/fcgi-bin/php.fcgi
Taka konfiguracja powoduje, ze skrypty są uruchamiane o wiele szybciej niż przez CGI.
Niestety skrypty są jednak uruchamiane wolniej niż przy pomocy modułu wkompilowanego w
Apache lub modułu dynamicznie dołączanego do Apache. Wadą rozwiązania jest także fakt, ze
podczas normalnej pracy serwera uruchomionych jest wiele interpreterów php (wersji dla fast-
cgi), które niekoniecznie są w danej chwili potrzebne. Powodują one jednak to, ze przy obsłudze
zapytania nie ma konieczności natychmiastowego uruchamiania (ładowania, tworzenia procesu)
interpretera i przez to obciążenie systemu jest mniejsze i czas reakcji szybszy.
3. Tryb safe mode
W przypadku administrowania serwerem z wieloma użytkownikami, którym nie można w
pełni zaufać istnieje konieczność ograniczenia pewnych możliwości jakie daje korzystanie z
PHP. Najważniejszymi potencjalnie niebezpiecznymi możliwościami są udostępnianie plików
leżących poza drzewem Apache’a na stronach WWW, uruchamianie programów (niezależnie
od tego gdzie leżą), zmiana zmiennych środowiskowych, korzystanie z potencjalnie
niebezpiecznych funkcji.
Aby ograniczyć niektóre prawa użytkownika korzystającego z PHP administrator może
wykorzystać tryb safe mode uruchamiania skryptów PHP. Tryb ten włączany jest przy pomocy
dyrektywy safe_mode w pliku konfiguracyjnym php.ini lub w pliku httpd.conf.
Przykład php.ini:
safe_mode = On
Przykład httpd.conf:
php_flag safe_mode on
Włączenie trybu safe mode powoduje zmodyfikowanie działania niektórych funkcji. W
większości są to funkcje wejścia/wyjścia. Modyfikacja ich polega w większości na tym, że przed
wykorzystaniem ich właściwej opcji np. na pliku sprawdzane jest czy plik należy do tej samej
osoby co właściciel. W przypadku funkcji uruchamiających inne skrypty lub programy
sprawdzane jest czy znajdują się one we właściwym miejscu.
Na działanie skryptu PHP w trybie safe mode wpływają następujące dyrektywy:
11
safe_mode
Dyrektywa ta włącza lub wyłącza tryb safe mode oraz wpływ innych dyrektyw trybu safe
mode na działanie programu.
safe_mode_gid
Podczas operacji plikowych sprawdzane jest czy właściciel pliku, na którym wykonywana
jest operacja jest taki sam jak właściciel skryptu. W przypadku niezgodności właściciela pliku i
skryptów wykonanie operacji zostaje zabronione.
Jeżeli dyrektywa safe_mode_gid jest włączona w przypadku niezgodności właścicieli
pliku i skryptów sprawdzane jest także czy właściciel grupowy pliku i skryptów są takie same.
Jeżeli właściciel grupowy jest ten sam to operacja wejścia/wyjścia jest wykonywana.
open_basedir
Dyrektywa, ta specyfikuje katalogi, z których użytkownikowi wolno otwierać pliki. Przy
pomocy tej dyrektywy możemy ograniczyć użytkownikowi prawa do otwierania plików poza
wybranym drzewem katalogów.
safe_mode_include_dir
Dyrektywa ta specyfikuje katalogi (wraz z ich podkatalogami), z których użytkownikowi
wolno includować pliki, które nie są jego własnością. Pliki includowane położone poza tym
drzewem muszą mieć tego samego właściciela co uruchamiany skrypt .
safe_mode_exec_dir
Dyrektywa ta specyfikuje katalogi (wraz z ich podkatalogami), z których użytkownikowi
wolno uruchamiać zewnętrzne programy np. przy użyciu funkcji system.
safe_mode_allowed_env_vars
Przy pomocy tej dyrektywy możemy wyszczególnić prefixy zmiennych środowiskowych,
które mogą być zmieniane przez użytkownika wykorzystującego funkcję putenv(). Można podać
więcej prefixów oddzielając je przecinkami.
safe_mode_protected_env_vars
Przy pomocy tej dyrektywy można podać listę zmiennych środowiskowych, które nie mogą
być modyfikowane przez użytkownika funkcją putenv(). Zmienne takie nie będą mogły być
modyfikowane nawet jeżeli objęte zostały dyrektywą safe_mode_allowed_env_vars.
disable_functions
12
Ta dyrektywa umożliwia podanie nam listy funkcji, które zostają zablokowane i
użytkownik nie może z nich skorzystać. Nazwy funkcji są oddzielone przecinkami. Nie można
korzystać z tej dyrektywy wyłącznie wewnątrz pliku php.ini.
Większość dyrektyw tych można definiować także wewnątrz pliku httpd.conf (w
konfiguracji Apache’a) . Daje nam to możliwość zmiany właściwości trybu safe mode dla
poszczególnych wirtualnych domen, a także dla poszczególnych katalogów.
Przykład takiej definicji w pliku httpd.conf wygląda następująco:
php_flag safe_mode on
<Directory /docroot>
php_admin_value open_basedir /docroot
</Directory>
4. Analiza kodu suexec
Poniżej zamieszczono dokładny opis kodu programu suexec, który służy jako pośrednik do
uruchamiania skryptów CGI.
Deklaracja bezpiecznych zmiennych środowiskowych, które mogą zostać przekazane
skryptowi uruchamianemu, przy pomocy suexec.
char *safe_env_lst[] =
{
"AUTH_TYPE",
"CONTENT_LENGTH",
"CONTENT_TYPE",
"DATE_GMT",
"DATE_LOCAL",
"DOCUMENT_NAME",
"DOCUMENT_PATH_INFO",
"DOCUMENT_ROOT",
"DOCUMENT_URI",
"FILEPATH_INFO",
13
"GATEWAY_INTERFACE",
"LAST_MODIFIED",
"PATH_INFO",
"PATH_TRANSLATED",
"QUERY_STRING",
"QUERY_STRING_UNESCAPED",
"REMOTE_ADDR",
"REMOTE_HOST",
"REMOTE_IDENT",
"REMOTE_PORT",
"REMOTE_USER",
"REDIRECT_QUERY_STRING",
"REDIRECT_STATUS",
"REDIRECT_URL",
"REQUEST_METHOD",
"REQUEST_URI",
"SCRIPT_FILENAME",
"SCRIPT_NAME",
"SCRIPT_URI",
"SCRIPT_URL",
"SERVER_ADMIN",
"SERVER_NAME",
"SERVER_ADDR",
"SERVER_PORT",
"SERVER_PROTOCOL",
"SERVER_SOFTWARE",
"UNIQUE_ID",
"USER_NAME",
"TZ",
NULL
};
Funkcja wyprowadzająca komunikaty diagnostyczne do pliku logów. Plik ten jest
zdefiniowany
podczas
kompilacji
Apacha
przez
ustawienie
--suexec-
logfile=NAZWAPLIKU
.
static void err_output(const char *fmt, va_list ap)
14
Funkcja przetwarzająca zmienną liczbę argumentów i wywołująca funkcje
err_output()
w celu wypisania komunikatu do plików logów.
static void log_err(const char *fmt,...)
Funkcja oczyszcza środowisko ze zmiennych, niebezpiecznych. Za zmienne bezpieczne
uznawane są tylko zmienne, których nazwy zostały umieszczone w tablicy safe_env_lst[]
oraz zmienne zaczynające się prefiksem HTTP_. Maksymalna ilość zmiennych jest umieszczona
w stałej AP_ENVBUF zdefiniowanej na początku pliku.
Funkcja main(int argc, char *argv[]) uruchamia polecenie ze zmienionymi uprawnieniami.
Działanie funkcji:
Sprawdza czy użytkownik, który wywołuje program istnieje w pliku /etc/passwd
uid = getuid();
if ((pw = getpwuid(uid)) == NULL) {
log_err("crit: invalid uid: (%ld)\n", uid);
exit(102);
}
Sprawdza czy można wyświetlić info. Info jest wyświetlane wtedy jeżeli: liczba
argumentów jest większa niż 1, parametr przekazany do funkcji jest równy "-V" oraz osoba
wywołującą program jest root lub użytkownik HTTPD_USER (czyli użytkownik, z
uprawnieniami którego działa Apache i uprawniony jest do uruchamiania programu suexec). Po
wyświetleniu informacji program jest zatrzymywany.
Jeżeli liczba parametrów jest mniejsza od 4, program jest kończony z błędem i
komunikatem do logów.
if (argc < 4) {
log_err("alert: too few arguments\n");
exit(101);
}
15
Następnie pobierana jest z argumentów wejściowych nazwa użytkownika, grupy i
programu uruchamianego przez tego użytkownika.
target_uname = argv[1];
target_gname = argv[2];
cmd = argv[3];
Sprawdza czy użytkownik, który próbuje uruchomić program ma do tego uprawnienia.
Użytkownik, który wywołuje program definiowany jest w trakcie kompilacji lub w pliku
suexec.h
.
if (strcmp(HTTPD_USER, pw->pw_name)) {
log_err("crit: calling user mismatch (%s instead of %s)\n",
pw->pw_name, HTTPD_USER);
exit(103);
}
Sprawdza czy łańcuch komendy wykonywanej zaczyna się "/" lub "../" lub zawiera "/../'.
Zabezpieczenie to ma na celu wyeliminowanie możliwości uruchamiania programu z korzenia
systemu plików lub "cofania" się w hierarchii systemu plików.
if ((cmd[0] == '/') || (!strncmp(cmd, "../", 3))
|| (strstr(cmd, "/../") != NULL)) {
log_err("error: invalid command (%s)\n", cmd);
exit(104);
}
Sprawdza czy suexec ma do czynienia z wywołaniem programu z konta użytkownika.
Jeżeli tak to usuwa z nazwy użytkownika pierwszy znak tj. "~" (tyldê).
if (!strncmp("~", target_uname, 1)) {
target_uname++;
userdir = 1;
}
Sprawdza czy docelowy użytkownik istnieje w systemie.
if ((pw = getpwnam(target_uname)) == NULL) {
16
log_err("crit: invalid target user name: (%s)\n",
target_uname);
exit(105);
}
Sprawdza poprawność grupy docelowej (numeru, ewentualnie nazwy).
if (strspn(target_gname, "1234567890") != strlen(target_gname))
{
if ((gr = getgrnam(target_gname)) == NULL) {
log_err("crit: invalid target group name: (%s)\n",
target_gname);
exit(106);
}
gid = gr->gr_gid;
actual_gname = strdup(gr->gr_name);
}
else {
gid = atoi(target_gname);
actual_gname = strdup(target_gname);
}
Zapisanie id użytkownika, nazwy użytkownika i docelowego katalogu w zmiennych
pomocniczych.
uid = pw->pw_uid;
actual_uname = strdup(pw->pw_name);
target_homedir = strdup(pw->pw_dir);
Otwarcie pliku logów i zalogowanie o zaistniałym zdarzeniu:
log_err("info: (target/actual) uid: (%s/%s) gid: (%s/%s) cmd:
%s\n",
target_uname, actual_uname,
target_gname, actual_gname,
cmd);
17
Sprawdzenie czy użytkownik docelowy nie jest rootem i czy jego uid nie jest mniejszy od
zmiennej UID_MIN, która jest ustawiana w pliku suexec.h lub w trakcie konfigurowania
Apache'a.
if ((uid == 0) || (uid < UID_MIN)) {
log_err("crit: cannot run as forbidden uid (%d/%s)\n",
uid, cmd);
exit(107);
}
To samo dla grupy docelowej.
if ((gid == 0) || (gid < GID_MIN)) {
log_err("crit: cannot run as forbidden gid (%d/%s)\n", gid,
cmd);
exit(108);
}
Zmienia uprawnienia procesu na uprawnienia użytkownika docelowego. Najpierw setgid(),
a pózniej setuid().
if (((setgid(gid)) != 0) || (initgroups(actual_uname, gid) !=
0)) {
log_err("emerg: failed to setgid (%ld: %s)\n", gid, cmd);
exit(109);
}
if ((setuid(uid)) != 0) {
log_err("emerg: failed to setuid (%ld: %s)\n", uid, cmd);
exit(110);
}
Pobranie nazwy aktualnego katalogu.
if (getcwd(cwd, AP_MAXPATH) == NULL) {
log_err("emerg: cannot get current working directory\n");
exit(111);
}
18
Sprawdzenie czy osiągalny jest docelowy katalog roboczy dla programu uruchamianego.
Dla programu uruchamianego w normalnym trybie sprawdzana jest także czy katalog
docelowy jest w poddrzewie katalogu zawartego w stałej DOC_ROOT ustawianej podczas
konfiguracji lub w pliku suexec.h.
if (userdir) {
if (((chdir(target_homedir)) != 0) ||
((chdir(USERDIR_SUFFIX)) != 0) ||
((getcwd(dwd, AP_MAXPATH)) == NULL) ||
((chdir(cwd)) != 0)) {
log_err("emerg: cannot get docroot information
(%s)\n",target_homedir);
exit(112);
}
}
else {
if (((chdir(DOC_ROOT)) != 0) ||
((getcwd(dwd, AP_MAXPATH)) == NULL) ||
((chdir(cwd)) != 0)) {
log_err("emerg: cannot get docroot information (%s)\n",
DOC_ROOT);
exit(113);
}
}
if ((strncmp(cwd, dwd, strlen(dwd))) != 0) {
log_err("error: command not in docroot (%s/%s)\n", cwd,
cmd);
exit(114);
}
Pobranie informacji o katalogu aktualnym, w którym będzie uruchamiany program
docelowy.
if (((lstat(cwd, &dir_info)) != 0) ||
!(S_ISDIR(dir_info.st_mode))) {
19
log_err("error: cannot stat directory: (%s)\n", cwd);
exit(115);
}
Sprawdzenie czy katalog może być zapisywany przez innych użytkowników lub
użytkowników w tej samej grupie (jeśli tak to błąd).
if ((dir_info.st_mode & S_IWOTH) || (dir_info.st_mode &
S_IWGRP)) {
log_err("error: directory is writable by others: (%s)\n",
cwd);
exit(116);
}
Pobranie informacji o programie.
if (((lstat(cmd, &prg_info)) != 0) ||
(S_ISLNK(prg_info.st_mode))) {
log_err("error: cannot stat program: (%s)\n", cmd);
exit(117);
}
Sprawdza czy program uruchamiany, może być zapisywany przez innych (jeśli tak to błąd).
if ((prg_info.st_mode & S_IWOTH) || (prg_info.st_mode &
S_IWGRP)) {
log_err("error: file is writable by others: (%s/%s)\n", cwd,
cmd);
exit(118);
}
Błąd jeżeli program ma ustawiony bit SGID lub SUID.
if ((prg_info.st_mode & S_ISUID) || (prg_info.st_mode &
S_ISGID)) {
log_err("error: file is either setuid or setgid: (%s/%s)\n",
cwd, cmd);
exit(119);
}
20
Sprawdza czy docelowa grupa i użytkownik są właścicielami programu uruchamianego i
katalogu, w którym ten program jest uruchamiany.
if ((uid != dir_info.st_uid) ||
(gid != dir_info.st_gid) ||
(uid != prg_info.st_uid) ||
(gid != prg_info.st_gid)) {
log_err("error: target uid/gid (%ld/%ld) mismatch "
"with directory (%ld/%ld) or program (%ld/%ld)\n",
uid, gid,
dir_info.st_uid, dir_info.st_gid,
prg_info.st_uid, prg_info.st_gid);
exit(120);
}
Sprawdza, czy program jest uruchamialny przez właściciela
if (!(prg_info.st_mode & S_IXUSR)) {
log_err("error: file has no execute permission: (%s/%s)\n",
cwd, cmd);
exit(121);
}
Sprawdza poprawne ustawienie maski dla tworzonych plików tak, aby pliki utworzone
przez wywoływany program nie mogły byś zapisywane przez innych użytkowników systemu.
if ((~SUEXEC_UMASK) & 0022) {
log_err("notice: SUEXEC_UMASK of %03o allows "
"write permission to group and/or other\n",
SUEXEC_UMASK);
}
umask(SUEXEC_UMASK);
Czyści zmienne środowiskowe uruchamianego programu.
clean_env();
Zamyka plik logów.
21
if (log != NULL) {
fclose(log);
log = NULL;
}
Uruchomienie programu.
#ifdef NEED_HASHBANG_EMUL
/* We need the #! emulation when we want to execute scripts */
{
extern char **environ;
ap_execve(cmd, &argv[3], environ);
}
#else /*NEED_HASHBANG_EMUL*/
execv(cmd, &argv[3]);
#endif /*NEED_HASHBANG_EMUL*/
Zapisanie informacji do logów w przypadku nieprawid
łowego
uruchomienia skryptu.
log_err("emerg: (%d)%s: exec failed (%s)\n", errno,
strerror(errno), cmd);
exit(255);
5. Pomysły na rozbudowanie kodu suexec
5.1. Modyfikacja suexec tak, aby uruchamiać pliki PHP z uprawnieniami
właściciela pliku:
Z jednym ze wcześniejszych punktów zasygnalizowano możliwość uruchamiania skryptów
z wykorzystaniem suexec. Celem modyfikacji jest umożliwienie uruchamiania plików PHP bez
konieczności wpisywania w pierwszym wierszu każdego skryptu ścieżki do interpretera PHP tak
aby uruchamiały się one z uprawnieniami właściciela pliku.
Modyfikacja ta polega na rozpoznawaniu po rozszerzeniu czy jest to plik PHP (oraz wersji)
oraz uruchomieniu odpowiedniego interpretera skryptów.
22
Najważniejsza modyfikacja źródeł suexe.c jest następująca:
if (czy_php(cmd))
if ((newargv[0] = getPhpInterpreter(cmd)) == NULL) {
fprintf(stderr, "interpreter nie znaleziony\n");
exit(130);
}
newargv[1] = cmd;
newargv[2] = NULL;
Powyższy kod sprawdza na postawie rozszerzenia czy polecenie jest plikiem PHP. W
zależności od tego czy plik ma rozszerzenie .php, .php3 lub .php4. dobiera odpowiedni
interpreter. Oraz buduje tablice zawierającą parametry do uruchomienia interpretera wraz z
odpowiednim skryptem.
Tablica newargv[] przekazywana jest następnie do funkcji ap_execve(), która odpowiedzialna
jest za uruchomienia skryptu.
5.2. 4.2 Obsługa limitów setrlimit().
Obecnie
serwer
Apache
nie
udostępnia
możliwości
ograniczenia
zasobów
wykorzystywanych przez programy CGI. Zasoby te mogą być ograniczone poprzez standardowe
ustawienia w systemie, które są stosowane dla każdego programu. Ograniczenia te można
zmieniać wykorzystując polecenie ulimit/limit (w zależności od shella). Administrator nie ma
jednak możliwości ograniczenia w ten sposób tylko do skryptów CGI i dlatego potrzebne wydaje
się zmodyfikowanie serwera tak, aby mógł wpływać na ograniczenia w przydzielaniu zasobów
systemowych. Aby zrealizować ten cel można zmodyfikować kod programu suexec, który jest
odpowiedzialny za uruchamianie skryptów CGI. Modyfikacja programu będzie polegać na
odczytaniu pliku konfiguracyjnego, zinterpretowaniu jego zawartości i ustawieniu limitów dla
niego samego, a takze dla wszystkich innych programów, które suexec uruchomi.
Ponieważ w naszym rozwiązaniu korzystamy z funkcji systemowej setrlimit() dlatego
mamy możliwość nadania programowi następujących ograniczeń.
RLIMIT_CPU - czas procesora
RLIMIT_FSIZE – maksymalny rozmiar pliku
RLIMIT_DATA - maksymalny rozmiar segmentu danych
RLIMIT_STACK - maksymalny rozmiar segmentu stosu
RLIMIT_CORE – maksymalny rozmiar pliku core (tworzonego gdy w skrypcie
wyst
ąpi błąd)
RLIMIT_RSS maksymalna wielko
ść pamięci rezydentnej
23
RLIMIT_NPROC - maksymalna ilo
ść towrzonych procesów
RLIMIT_NOFILE – maksymalna ilo
ść otworzonych plików
RLIMIT_MEMLOCK – maksymalna wielkoœæ obszaru locked-in-memory
Ponieważ jest to zadanie modelowe plik konfiguracyjny ma postać możliwie najprostsza.
Dane są w pliku oddzielone przecinkami i znajdują się w jednym wierszu. Wartość ujemna
oznacza brak limitu.
Kolejne wartości w pliku oznaczają:
RLIMIT_CPU, RLIMIT_FSIZE, RLIMIT_DATA, RLIMIT_STACK, RLIMIT_CORE, RLIMIT_RSS,
RLIMIT_NPROC, RLIMIT_NOFILE,RLIMIT_MEMLOCK
Przykład:
5,1223423,-1,1324214,2224313,14242223,12234233,13242234,-1
Modyfikacja kodów programu suexec sprowadza się do dodania dwóch dyrektyw define
oznaczających ścieżkę do pliku konfiguracyjnego i maksymalny jego rozmiar (który powinien
być niewielki z powodu niewielu informacji w nim trzymanych), funkcji odczytującej plik i
interpretującej jego zawartość oraz kilka wierszy sprawdzających uprawnienia do pliku
konfiguracyjnego i ustawiających limity. Ponadto trzeba „zaincludować” stosowne pliki.
Wiersze definiujące maksymalny rozmiar pliku konfiguracyjnego oraz ścieżkę do tego
pliku:
#define MAXCONFBUF 1000
#define MAXCONFPATH "/etc/param.conf"
Funkcja odczytująca i interpretująca zawartość pliku konfiguracyjnego. W przypadku błędu
funkcja ta zwraca –1 i wypisuje komunikat do pliku logów.
int odczytaj_limit(char *nazwa_pliku,struct rlimit *rl_CPU, struct rlimit
*rl_FSIZE,struct rlimit *rl_DATA,struct rlimit *rl_STACK,struct rlimit
*rl_CORE,struct rlimit *rl_RSS,struct rlimit *rl_NPROC,struct rlimit
*rl_NOFILE,struct rlimit *rl_MEMLOCK){
int fd,odczytano;
char bufor[MAXCONFBUF];
char *poz,*first=bufor;
24
fd=open(nazwa_pliku,O_RDONLY);
if(fd>0){
odczytano=read(fd,(void *)bufor,MAXCONFBUF);
if(odczytano>0){
poz=bufor;
if(atoi(poz)=>0){
rl_CPU->rlim_cur=atoi(poz);
rl_CPU->rlim_max=rl_CPU->rlim_cur;
}
else rl_CPU->rlim_max=rl_CPU->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_FSIZE->rlim_cur=atoi(poz);
rl_FSIZE->rlim_max=rl_FSIZE->rlim_cur;
}
else rl_FSIZE->rlim_max=rl_FSIZE->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_DATA->rlim_cur=atoi(poz);
rl_DATA->rlim_max=rl_DATA->rlim_cur;
}
else rl_DATA->rlim_max=rl_DATA->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_STACK->rlim_cur=atoi(poz);
rl_STACK->rlim_max=rl_STACK->rlim_cur;
}
else rl_STACK->rlim_max=rl_STACK->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_CORE->rlim_cur=atoi(poz);
rl_CORE->rlim_max=rl_CORE->rlim_cur;
}
else rl_CORE->rlim_max=rl_CORE->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_RSS->rlim_cur=atoi(poz);
25
rl_RSS->rlim_max=rl_RSS->rlim_cur;
}
else rl_RSS->rlim_max=rl_RSS->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_NPROC->rlim_cur=atoi(poz);
rl_NPROC->rlim_max=rl_NPROC->rlim_cur;
}
else rl_NPROC->rlim_max=rl_NPROC->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_NOFILE->rlim_cur=atoi(poz);
rl_NOFILE->rlim_max=rl_NOFILE->rlim_cur;
}
else rl_NOFILE->rlim_max=rl_NOFILE->rlim_cur=RLIM_INFINITY;
poz=index(poz,',');if(!poz) return -1; poz++;
if(atoi(poz)=>0){
rl_MEMLOCK->rlim_cur=atoi(poz);
rl_MEMLOCK->rlim_max=rl_MEMLOCK->rlim_cur;
}
else rl_MEMLOCK->rlim_max=rl_MEMLOCK->rlim_cur=RLIM_INFINITY;
return 1;
}
else{ log_err("blad odczytu informacji\n"); return -1; }
close(fd);
}
else{
log_err("crit: nie mozna znalezc pliku konfiguracyjnego\n");
return -1;
}
}
Deklaracja zmiennych przetrzymujących informacje o pliku konfiguracyjnym i informacje
o limitach odczytanych z tego pliku.
struct stat prg_info1; /* konfiguracja - limit info holder */
struct rlimit rl_CPU, rl_FSIZE, rl_DATA, rl_STACK, rl_CORE, rl_RSS, rl_NPROC,
rl_NOFILE, rl_MEMLOCK; /* Limity programu */
26
Sprawdzenie czy pliku konfiguracyjny limitów nie jest zapisywany przez użytkowników
innych niż właściciel:
if (((lstat(M
AXCONFPATH, &prg_info1)) != 0) ||
(S_ISLNK(prg_info1.st_mode))) {
log_err("error: Nie moge uzyskac informacji o pliku : (%s)\n",MAXCONFPATH);
exit(127);
}
if ((prg_info1.st_mode & S_IWOTH) || (prg_info1.st_mode & S_IWGRP)) {
log_err("error: plik jest zapisywalny przez innych niz
wlasciciel\n",MAXCONFPATH);
exit(128);
}
Odczytanie pliku konfiguracyjnego i ustawienie limitów. W przypadku błędu wypisywany
jest komunikat o błędzie do logów i kończony jest program (nie sprawdzana jest poprawność
wykonania funkcji setrlimit() – rozbudowując kod można dodać obsługę błędów, którym
źródłem jest funkcja setrlimit()).
if(odczytaj_limit(MAXCONFPATH, &rl_CPU, &rl_FSIZE, &rl_DATA, &rl_STACK,
&rl_CORE, &rl_RSS, &rl_NPROC, &rl_NOFILE, &rl_MEMLOCK)>0){
//ustawianie limitow
setrlimit(RLIMIT_CPU,&rl_CPU);
setrlimit(RLIMIT_FSIZE,&rl_FSIZE);
setrlimit(RLIMIT_DATA,&rl_DATA);
setrlimit(RLIMIT_STACK,&rl_STACK);
setrlimit(RLIMIT_CORE,&rl_CORE);
setrlimit(RLIMIT_RSS,&rl_RSS);
setrlimit(RLIMIT_NPROC,&rl_NPROC);
setrlimit(RLIMIT_NOFILE,&rl_NOFILE);
setrlimit(RLIMIT_MEMLOCK,&rl_MEMLOCK);
}
else{
log_err("crit: blad w konfiguracji limitow\n");
exit(126);
}
27
28
6. Wnioski
Niestety nie ma uniwersalnej metody uruchamiania skryptów PHP, która nie posiadała by
wad. Wybierając metodę uruchamiania skryptów PHP należy dokonać oceny jak bardzo zależy
nam na uruchamianiu skryptów z uprawnieniami właściciela oraz jak duże i jakie koszty
jesteśmy w stanie poświęcić chcąc rozwiązać ten problem. Uruchamianie programów z
wykorzystaniem suexec wiąże się z narzutami związanymi z uruchamianiem nowego procesu
wady tej nie mają skrypty uruchamiane przez moduł. Ten jednak sposób uniemożliwia bez
uruchamiania nowego serwera na innym porcie uruchomienia skryptów z uprawnieniami
właściciela. Pewne problemy z narzutem związanym uruchamianiem procesów rozwiązuje
wykorzystanie modułu i biblioteki FastCGI, ale ta przechowuje w pamięci dużo procesów, które
cały czas zajmują pamięć.
Analiza kodu suexec daje nam do zrozumienia jak bardzo ważną częścią w
oprogramowaniu serwera są kody poświęcone bezpieczeństwu. W zasadzie cały kod programu
suexec służy do sprawdzenia czy został on wywołany w prawidłowy sposób nie dając
możliwości włamania się do systemu.
Tryb safe mode daje nam pewną możliwość poprawy bezpieczeństwa pracy na serwerze z
wieloma użytkownikami, którym nie można zaufać. Tryb safe mode staje się cennym narzędziem
w przypadku, gdy użytkownicy piszący skrypty nie mają dostępu do całego systemu plików
serwera (np. dostęp tylko po przez ftp z chrootem) oraz możliwości wykorzystania innych
narzędzi np. skryptów CGI napisanych w Perlu, które pozwolą na dostęp do obszarów serwera
poza swoim katalogiem domowym (lub wyznaczonym obszarem systemu plików). Zabezpiecza
to np. przed dostępem do skryptów lub plików oraz np. haseł w nich zawartych innego
użytkownika. Trudno jest tego dokonać inną metodą np. modyfikując uprawnienia w systemie
plików ponieważ skrypty muszą być dostępne do odczytu dla serwera Apache (czyli praktycznie
dla każdego użytkownika). Można oczywiście wykorzystać skrypty CGI oraz suexec i zapisywać
hasła w plikach tylko do odczytu dla właściciela, ale wiąże się to z narzutami na tworzenie
nowych procesów.
Podsumowując. Aby wybrać właściwy sposób uruchamiania skryptów PHP i co za tym
idzie konfiguracji serwera należy nie tylko znać konfigurowane oprogramowanie, ale także cel,
zasadę działania oprogramowania, które będzie uruchamiane przez serwer.
29
7. Literatura
8. Dokumentacja serwera Apache: http://httpd.apache.org/docs/
9. Dokumentacja PHP: http://www.php.net/manual/en/
10. Dokumentacja FastCGI http://www.fastcgi.com/devkit/doc/fcgi-perf.htm
11. Ben, Peter Laurie „Apache przewodnik encyklopedyczny” - Helion 2000
12. Listy dyskusyjne