Postanowiłem na własne potrzeby stworzyć pewien szablon konfiguracji PHP opartej o Apache. Podstawowym celem było bezpieczeństwo rozwiązania, a jak udało mi się to zrealizować – życie pokaże. W prezentowanych przykładach pominąłem większość parametrów konfiguracyjnych, a więc podczas tworzenia własnego środowiska na bazie niniejszego artykuły zalecam dostosowanie ich do własnych potrzeb.

Na potrzeby niniejszej konfiguracji użyłem:

  • Linux Slackware w wersji 12. Starałem się nie używać ustawień i pakietów pochodzących z dystrybucji, żeby rozwiązanie było jak najbardziej uniwersalne
  • Apache w wersji 2.2.6
  • FastCGI w wersji 2.4.6
  • PHP w wersji 4.4.7 i 5.2.5

Przyjąłem również kilka założeń:

  • Skrypty PHP powinny być uruchamiane w zamkniętym środowisku (np: chroot() albo open_basedir())
  • Skrypty PHP powinny być uruchamiane z uprawnieniami właściciela pliku/virtualhosta
  • Każdy użytkownik powinien mieć indywidualny folder tmp i folder przechowywania plików sesji
  • Każdy użytkownik sam może wybrać wersji PHP
  • Dla każdego virtualhosta powinien istnieć oddzielny plik php.ini
  • Tworzone rozwiązanie powinno być jak najbardziej uniwersalne. Jednocześnie powinno oferować administratorowi wygodę zarządzania

Instalacja Apache:

Apache wraz z modułem suexec potrafi uruchamiać skrypty CGI z uprawnieniami użytkownika. Model bezpieczeństwa suexec wymaga deklarowania zmiennych środowiskowych, które mają być przekazywane do CGI. Większość zmiennych środowiskowych np.: REQUEST_URL, DOCUMENT_ROOT itp. są wpisane na stałe do pliku support/suexec.c, jednak brakuje tam zmiennych dla PHP, które pomagają mu współpracować z modułem FastCGI. Musimy je ręcznie dopisać, a więc otwieramy plik support/suexec.c w ulubionym edytorze tekstowym i znajdujemy kod:

Dopisujemy przed linijką zawierającą AUTH_TYPE:

Plik wynikowy powinien wyglądać następująco:

Dzięki wykonanej modyfikacji suexec będzie przekazywał zmienne środowiskowe, które ustawi FastCGI, do wrappera PHP (w dalszej części artykuły opisze obie zmienne i dlaczego trzeba było wprowadzić tą poprawkę).

Teraz możemy przystąpić do rzeczywistej konfiguracji Apache. Uruchamiamy komendę:

Opis parametrów:

  • –enable-so: włączenie mechanizmu DSO
  • –with-suexec-caller: użytkownik z którego będzie wywoływany suexec. Należy tutaj wpisać nazwę użytkownika na którym działa Apache
  • –with-suexec-docroot: ścieżka w której znajdują się wszystkie skrypty PHP uruchamiane przez suexec
  • –with-suexec-logfile: plik do którego suexec będzie logował. Należy pamiętać o utworzeniu takiego pliku i nadaniu mu uprawnień do zapisu dla użytkownika na którym działa Apache. Należy mieć też na uwadze konieczność rotowania tego pliku – Apache się tym nie zajmuje
  • –enable-suexec: włączenie mechanizmu suexec
  • –enable-cgi:włącza obsługę CGI – niezbędne do konfiguracji PHP jako CGI
  • –with-suexec-bin: ścieżka do pliku suexec w naszym systemie. Plik ten może znajdować się w dowolnym miejscu

Jeżeli nie dodałeś żadnego layoutu ustawień (–enable-layout=LAYOUT) podczas konfigurowania Apache, pliki zostaną umieszczone w katalogu /usr/local/apache2. Jeżeli zmieniłeś layout, pamiętaj o zmianie ścieżki do pliku suexec (–with-suexec-bin).

Instalacja FastCGI:

Konfiguracja FastCGI sprowadza się do wykonywania instrukcji instalacyjnych z pliku INSTALL.AP2. Należy pamiętać o zmianie ścieżki top_dir w przypadku innej konfiguracji Apache. We wskazanym katalogu powinien się znaleźć plik build/special.mk:

Instalacja PHP:

Instalacja obu wersji PHP przebiega podobnie dlatego opiszę tylko jedną wersję w tym miejscu. W przypadku PHP 4 zalecał bym tylko zmianę prefixu na /usr/local/php4. Dalsza część artykułu zawiera konfiguracje dla systemu z obiema wersjami PHP.

Celowo nie dodawałem tutaj parametru –sysconfdir, który wskazuje na miejsce szukania domyślnego pliku php.ini. Będę używał parametru -c do wskazania pliku php.ini. Dowiedz się więcej o kolejności ładowania plików php.ini z mojego artykułu.

Opis parametrów:

  • –prefix: prefix instalacji ustawiłem na /usr/local/php5 gdyż równolegle będziemy instalować php4 i dobrze by było gdyby dwie wersji nie wchodziły sobie w drogę
  • –enable-force-cgi-redirect: parametr ten zabezpiecza przed wywołaniem skryptu PHP poprzez link bezpośredni do skryptu FastCGI w folderze fcgi. Skrypt PHP zostanie wykonany tylko i wyłącznie jeżeli odwołanie do parsera zostało wykonane przez Apache
  • –enable-fastcgi: włączenie obsługi FastCGI w parserze PHP

Zanim wykonasz poniższe komendy utwórz katalog /usr/local/php5.

W tym momencie zakończyliśmy instalacje i podstawową konfigurację niezbędnego programowania. Przystąpmy zatem do dalszej konfiguracji.

Konfiguracja Apache:

Plik konfiguracyjny Apache (httpd.conf) powinien zawierać między innymi takie ustawienia:

Opis parametrów:

  • FastCgiWrapper: w tym miejscu deklarujemy wrapper dla FastCGI. Zamiast suexec możemy użyć np. cgiwrap albo sbox. Jeżeli chcemy użyć tego samego wrapper co Apache możemy ustawić tą opcję na “On”
  • FastCgiConfig: deklarujemy w jaki sposób ma się zachowywać FastCGI
  • -initial-env PHP_FCGI_CHILDREN=1: Ustawia zmienną środowiskową PHP_FCGI_CHILDREN która poprzez wrapper (np.: suexec) przekazujemy do PHP. Jeżeli nie zmodyfikowali byśmy źródeł suexec , zmienna ta została by wycięta. PHP_FCGI_CHILDREN określa ile procesów potomnych ma obsługiwać żądania PHP dla jednego virtualhosta
  • -initial-env PHP_FCGI_MAX_REQUEST=5000: Zmienna ta określa maksymalną ilość żądań po których dany proces zostanie zakończony i uruchomiony w razie potrzeby od nowa
  • -singleThreshold 90: Parametr przyjmuje wartości liczbowe od 0 do 100. FastCGI na tej podstawie określa czy dany proces ma być utrzymywany (wartość bliższa 1) czy zabity (bliżej 100) jeżeli nie jest już wykorzystywany. Jeżeli zależy Ci się na oszczędności pamięci ustaw ten parametr bliżej 100. Ustawienie na 0 zapobiegnie wyłączaniu ostatniego procesu potomnego
  • -killInterval 300: Wartość parametru w sekundach, określa po jakim czasie bezczynności proces ma zostać zabity
  • -autoUpdate: Powoduje sprawdzanie daty skryptów FastCGI. Jeżeli któryś z nich zostanie zaktualizowany, FastCGI zabije proces i uruchomi go od nowa
  • -idle-timeout 240: Czas w sekundach który ma skrypt PHP na uruchomienie i rozpoczęcie zwracania wyników użytkownikowi
  • -pass-header HTTP_AUTHORIZATION: Przekazania nagłówków uwierzytelniania HTTP do CGI. Standardowo nagłówki te nie są przekazywane

Przyjąłem, że każdy virtualhost posiada swój katalog w /var/www. Struktura prezentuje się następująco:
/var/www/host1.example.com
/var/www/host1.example.com/htdocs – główny folder dostępny przez http
/var/www/host1.example.com/htdocs/fcgi – folder ze skryptami FastCGI
/var/www/host1.example.com/tmp – folder przeznaczony na pliki sesji i pliki tymczasowe

Konfiguracja virtualhosta:

Opis parametrów:

  • SuexecUserGroup v1 v1: określa systemowego użytkownika i grupę na którą suexec ma przenieść uprawnienia
  • <Location /fcgi/php5>…</Location>: Zarejestrowanie pliku /fcgi/php5 jako skryptu FastCGI
  • <Location /fcgi/php4>…</Location>: Jw. tylko, że dla pliku /fcgi/php4
  • <Directory>…</Directory>: Ustawienia katalogu fcgi. Należy tutaj szczególną uwagę zwrócić na “Options None”. Zabezpiecza to przed możliwością uruchamiania programów niż zadeklarowane w <Location …>. Dodatkowo opcja “AllowOverride None” zabezpiecza przed możliwością zmiany tych ustawień z poziomu pliku .htaccess
  • AddHandler php5-fastcgi .php: Ustawia domyślny interpretator dla plików .php na PHP 5. Gdyby użytkownik chciał zmienić wersje PHP na inna wystarczy, że sam we własnym zakresie stworzy plik .htaccess i wpisze do niego AddHandler php4-fastcgi .php
  • Action php5-fastcgi /fcgi/php5: Deklaracja skryptu FastCGI dla PHP 5
  • Action php4-fastcgi /fcgi/php4: Deklaracja skryptu FastCGI na PHP 5

Trzy opisane na końcu parametry można umieścić poza sekcją <VirtualHost>…</VirtualHost> czyniąc z nich ustawienia globalne dla serwera.

Teraz utworzymy skrypty FastCGI zadeklarowane w konfiguracji Apache. Skrypty te musimy utworzyć dla każdego virtualhosta w katalogu htdocs/fcgi.
Dla PHP 5 tworzymy plik htdocs/fcgi/php5:

Dla PHP tworzymy plik htdocs/fcgi/php4:

Niektórzy zadadzą sobie teraz pytanie “Po co dodawać każdemu katalog fcgi razem ze skryptami skoro mogę zrobić jeden katalog i podlinkować go każdemu?”.

Odpowiedź leży w źródłach suexec, które edytowaliśmy na początku. Model bezpieczeństwa suexec jest bardzo restrykcyjny i dokładnie porównuje uprawnienia uruchamianych programów razem z folderami virtualhosta. Jeżeli chodź jeden z testów nie będzie zgodny, skrypt nie zostanie uruchomiony. Rozwiązaniem jest modyfikacja źródeł suexec albo rozwiązanie problemu poprzez kopiowanie skryptów FastCGI do każdego virtualhosta. Pamiętaj o zmianie właściciela plików php4 i php5.

W tym miejscu powinienem powrócić jeszcze do modyfikacji suexec, którą wykonaliśmy na początku. Nic nie stało na przeszkodzie żebyśmy dopisali do utworzonych przed chwilą skryptów export zmiennych globalnych np.:

Modyfikację tą wprowadzaliśmy jednak, żeby istniała możliwość ustawienia tych parametrów globalnie. Jeżeli jeden z virtualhostow wymaga innej konfiguracji to możemy zmodyfikować te wartości właśnie poprzez export jak na ww. przykładzie.

Pliki konfiguracyjne php.ini:

Żeby cała ta konfiguracja zdała egzamin musimy w przygotować indywidualne pliki php.ini dla każdego virtualhosta. W przypadku PHP 5 sprawa jest odrobinę ułatwiona. Temat ten opisałem w artykule “Uproszczenie w konfiguracji PHP 5 per virtualhost“.W pliku konfiguracyjnym obu wersji PHP powinny się znaleźć:

Finalizacja:

Na koniec pozostaje nam kilka problemów do rozwiązania, a mianowicie:

  • użytkownik może zmodyfikować zawartość skryptów znajdujących się w htdocs/fcgi, a co za tym idzie, dajemy mu dostęp do powłoki systemu, czego z pewnością większość z Was wolała by uniknąć
  • użytkownik może zmienić/skasować plik php.ini. Ma wtedy dostęp do modyfikacji spersonalizowanych wartości open_basedir, error_log, session.save_path, disable_functions itp.

Żeby rozwiązać te problemy, należało by zabrać użytkownikowi możliwość edycji plików w katalogu htdocs/fcgi. Gdy zabierzemy mu uprawnienia poprzez zmianę właściciela, suexec bardzo szybko przypomni nam o tym fakcie. Rozwiązanie jakie znalazłem niestety nie jest eleganckie, ale za to jest skuteczne.

Użytkownicy systemu plików ext2/3 mogą nadać atrybut +i dla katalogu htdocs/fcgi poprzez chattr +i fcgi. Uniemożliwi to wprowadzanie zmian na folderze fcgi (razem z jego zawartością) dla wszystkich użytkowników systemu. Tak założone atrybut +i zdejmuje tylko i wyłącznie root.

Change log artykułu:

Będę się starał uaktualniać ten artykuł, a więc w tym miejscu opisze przyszłe zmiany.
25/02/2008 19:38 – Usunięcie niepoprawnych spacji z konfiguracji VirtualHost.
04/02/2008 17:41 – Zmieniłem nazwę “apache2.conf” na “httpd.conf” celem poprawienia zgodności z domyślną konfiguracją Apache.
30/01/2008 20:28 – Zmiana <IfModule> na </IfModule> w konfiguracji Apache.
25/01/2008 12:26 – Poprawka związana z błędną nazwa foldera w przykładowej konfiguracji Apache. Zamienione “fcgi-bin” na “fcgi”.
30/12/2007 16:45 – Publikacja pierwszej wersji


Podziel się tym newsem z innymi:

  • Wykop
  • del.icio.us
  • Gwar
16 odpowiedzi na “Konfiguracja Apache + FastCGI + SuExec + PHP 4/5 + kilka innych rozwiązań”
  1. jackpot napisał:

    Bardzo ciekawy artykul, podobna konfiguracje udalo mi sie wykonac na lighttpd. Przeczytalem Twoj pomysl na apache i chyba mniej/wiecej wszystko rozumiem ale…. :-)

    Action php5-fastcgi /fcgi-bin/php5
    Action php4-fastcgi /fcgi-bin/php4

    a potem piszesz, na temat wrappera php:
    “tworzymy plik htdocs/fcgi/php5 (…)
    tworzymy plik htdocs/fcgi/php4 (…)”

    _Chyba_ wkradla sie mala niescislosc co do sciezek, albo brakuje wpisu “Alias” w apache.
    Wszedzie odwolujesz sie do katalogu “fcgi” a w “Action” ustawiasz katalog “fcgi-bin”

    z gory dzieki za pare slow komentarza :)

    pozdrawiam
    Jacek

  2. Mariusz Dalewski napisał:

    @jackpot: Oczywiście masz racje. Dzięki. Nawet opis konfiguracji odnosił się do /fcgi/.

    Poprawione. Zmiana opisana w changelogu.

  3. kinek napisał:

    Dobry artykuł, ale mam pytanko,

    “Plik konfiguracyjny Apache (apache2.conf) powinien zawierać [..]”

    czy chodzi o plik httpd.conf ? Jeśli nie to gdzie znajduje sie plik apache2.conf ?

  4. Mariusz Dalewski napisał:

    @kinek: Tak, oczywiście chodzi o httpd.conf. W zależności od atrybutów konfiguracyjny, plik ten może nosić różne nazwy.
    Zmieniłem nazwy apache2.conf na httpd.conf celem poprawienia zgodności z domyślną konfiguracją.

  5. kustosz napisał:

    Świetny artykuł podsumowujący bezpieczeństwo apache i php! :) Gratulacje!

    Znalazłem błąd, w “Konfiguracja virtualhosta:” występują znaki spacji, których być nie powinno:

    […]
    Directory /var/www/ host1.example.com/htdocs/fcgi
    ——————–^^^^
    ErrorLog /var/log/httpd/ host1.example.com-error_log
    ————————^^^^
    CustomLog /var/log/httpd/ host1.example.com -access_log common
    —————————^^^^—————–^^^^
    […]

    pzdr…
    /kustosz

  6. Mariusz Dalewski napisał:

    @kustosz: Dziękuje za wyłapanie błędów. Oczywiście poprawione.

  7. Gutek napisał:

    czy ten artykuł należy “wdrażać” na serwerze do którego ma dostęp jedna osoba?? tzn mamy serwer firmowy ale jest tylko jedna osoba która dokonuje jakich kolwiek zmian w systemie. Mam wrażenie jakby to dotyczyło mass hostingu tak?

  8. Mariusz Dalewski napisał:

    @Gutek: Zależy jaką funkcje spełnia dany serwer. Rozwiązanie to zapewnia separacje posiadanej strony/stron www między sobą. Dodatkowo daje większe możliwości kontroli i podnosi bezpieczeństwo.

    Jeżeli masz kilka domen/subdomen (nawet przy jednym użytkowniku), to ja bym właśnie tak skonfigurował serwer.

  9. kustosz napisał:

    Mam jeszcze pytania:

    1. czy w przypadku takiej konfiguracji logi php i samego apache zapisują się z UID/GID danego użytkownika (SuexecUserGroup v1 v1), czy też z UID/GID użytkownika zdefinowanego w httpd.conf (User i Group) ???

    2. w samym systemie (np. za pomocą ps) widać procesy “httpd” jako uruchomione z UID/GID zgodne z httpd.conf (User i Group) ???

    pzdr…
    /kustosz

  10. Mariusz Dalewski napisał:

    @kustosz:
    1. Logi Apache (obsługiwane przez dyrektywy np ErrorLog albo CustomLog) zapisywane są z uprawnieniami użytkownika zdefiniowanego w httpd.conf przy użyciu dyrektyw User i Group. Logi PHP, jako że PHP uruchamiane jest z poziomu użytkownika, zapisywane są z uprawnieniami użytkownika zdefiniowanego przy użyciu dyrektywy SuexecUserGroup.

    2. Zgadza się. Główny proces nasłuchujący będzie działał z uprawnieniami roota, procesy potomne z uprawnieniami nadamy przez User i Group, a skrypty php będzie uruchamiane z uprawnieniami nadanymi przez SuexecUserGroup.

  11. Eozwiazanie napisał:

    htdocs/fcgi poprzez chattr +i fcgi. Uniemożliwi to wprowadzanie zmian na folderze fcgi (razem z jego zawartością) dla wszystkich użytkowników systemu. Tak założone atrybut +i zdejmuje tylko i wyłącznie root.

    Problem w tym, że użytkownik może zmienic nazwę kataogu htdocs i założyc własny htdocs/fcgi i wgrac sobie tam co chce, takie rozwiazania niestety nigdy nie będa 100% bezpieczne, chyba że katalog fcgi wyciagniemy poza katalog użytkowna ale za dużo z tym roboty biorąc pod uwagę, że dla każdej domeny ma byc osobny fcgi katalog najlepiej/

  12. Mariusz Dalewski napisał:

    @Eozwiazanie:
    Masz rację, że jest to luka, której rozwiązania nie ująłem w powyższym opisie, ale wszystko zależy od uprawnień użytkownika do htdocs. Postaram się na dniach znaleźć na to skuteczne rozwiązanie.

  13. Eozwiazanie napisał:

    Rozwiązaniem jest wyciągnąć katalog dla wrapperów fastcgi do katalogu powyżej którego użytkownik nie jest w stanie wyjść przez FTP czyli np do /home/LOGIN (przyjmując że FTP jest chrootniete do /home/login).

    Drugi problem to trzeba jakoś w .htaccess zablokować możliwość takiego wpisu:

    AddHandler php5-fastcgi .html
    Action php5-fastcgi /costam/php55

    Przy takim wpisie użytkownik sam sobie robi wrapper i znów obchodzi nam zabezpieczenia :(

  14. Mariusz Dalewski napisał:

    @Eozwiazanie:
    Drugi problem rozwiązujemy nie dodając dyrektywy “FileInfo” do “AllowOverride” w ustawieniach katalogu /home i mamy komplet.

  15. Eozwiazanie napisał:

    Tak ale wtedy zabieramy użytkownikowi wiele porzebnych opcji, które może sobie ustawic w .htaccess :(

  16. Eozwiazanie napisał:

    Teraz widzę, że własny wrapper działa tylko w katalogu cgi-bin (poza fcgi na który dałem chattr +i) w związku z tym że IncludesNOEXEC nie wiem dlaczego nic nie daje pozostaje tylko z apachea pokasować wszystkie
    ScriptAlias do cgi-bin mam racje?

Pozostaw odpowiedź