Hlavní navigace

Oddělte od sebe uživatele serveru Apache a PHP

26. 2. 2010
Doba čtení: 9 minut

Sdílet

PHP je nejpoužívanějším jazykem na webu. Programují v něm firmy i jednotlivci a vyučuje se i na některých školách. Je tedy velmi důležité postarat se o bezpečnost webového serveru, protože často nevíte, co vám tam kdo nahraje. Nejbezpečnější je oddělit uživatele od sebe na úrovni operačního systému.

Když už jste okolnostmi donuceni starat se o webový server, měli byste si rozmyslet pro koho to děláte. U nás jsou velmi populární levné služby, kde se počet webů na jeden server pohybuje v tisících. Cena za jeden web se pohybuje třeba od dvaceti do padesáti korun. Méně populární jsou dražší služby, klidně s cenou od 150 Kč. Od mnohem menšího množství klientů dostanete možná i méně peněz, ale za to se o ně budete moci lépe postarat. Servery už nebudou obsahovat tisíce webů ale jen desítky, či stovky a spolehlivost i bezpečnost může být hnána na mnohem vyšší úroveň.

První popsaný případ řeší většina velkých poskytovatelů hostingových služeb a vývojáři PHP jim jdou se svými berličkami vstříc. Takovými berličkami byl dříve safe_mode a nyní open_basedir. Obojí nějak funguje, ale správný UNIXák cítí, že něco není v pořádku. Bezpečnost by měl řešit operační systém a ne skriptovací jazyk. Přeci jen koncept bezpečnosti v UNIXu tu je už hodně dlouho a snažit se ho obejít virtuálními uživateli je jako plavat proti rozbouřené řece. Nakonec to stejně nemusí skončit dobře.

Nicméně s počtem několika tisíc webů na jeden server se musí nějaké kompromisy udělat. Většinou to jde ruku v ruce s jednodušší správou a složitější situaci pro uživatele. U hostingu za dvacet korun na měsíc nemůžeme čekat zázraky a ani žádné zázraky nám nebudou nabízeny. Správce systému to pak musí vymyslet tak, aby se s co možná nejmenšími prostředky zahostovalo co nejvíce webů na jednom serveru.

Na to je PHP jako modul do Apache ideální, protože nikde nevisí nečinné procesy, které by „zbytečně“ zabíraly paměť a které by uměly zpracovat jen jeden web. Daní za to je běh pod jedním konkrétním uživatelem, a tudíž bezpečnost přenesena na bedra interpretru, kam v žádném případě nepatří.

open_basedir

Moderní způsob zabezpečení PHP skriptů je použití directivy open_basedir. Na tu berou ohledy funkce jako fopen(), opendir(), include nebo require a nedovolí skriptu fungovat mimo nastavený adresář. Když ještě zakážete funkci exec() pro spouštění příkazů shellu, tak je problém s bezpečností minimálně na povrchu vyřešen.

Nevýhodou tohoto řešení je, že některé nestandardní funkce nemusí open_basedir respektovat a pustí skripty, kam nesmějí. Při standardní instalaci PHP by se to nemělo stávat. Open_basedir je dnes doporučovanou formou velkých hostingů.

safe_mode

Starší a dnes už zastaralý způsob zabezpečení je direktiva safe_mode. Tato direktiva způsobuje mimo jiné to, že vám je v návodu různých hostingů doporučováno dávat souborům práva 777, tedy všichni všechno. Safe_mode mimo jiné způsobuje, že PHP kontroluje vlastníka skriptu a vlastníka souboru, se kterým pracuje, a pokud jsou obě hodnoty stejné, je přístup povolen. Další obranou proti uživatelům je vypnutí některých funkcí, spouštění spustitelných souborů jen z konkrétního adresáře a podobně. Jde docela o velký systém, který nemá mnoho open source projektů rádo.

CGI

Obě dvě formy bezpečnostních direktiv jsou bezpečné víceméně jen na oko. Stačí jediné přetečení zásobníku v kódu PHP a všichni uživatelé mají přístup ke všem datům ostatních. Jak už jsem řekl, o bezpečnost a oddělení uživatelů by se měl starat systém. Jako bonus pak získají uživatelé možnost spouštět externě i jiné programy a nemusíme se obtěžovat žádným safe_modem, zakázanými funkcemi ani omezovat uživatele na jeden adresář.

Skutečného oddělení na úrovni systému dosáhneme tak, že spustíme pro každý web samostatné procesy, pod samostatným uživatelem. Bezpečnost pak závisí na nastavení práv našeho systému a paranoidní správci mohou zapojit třeba i SELINUX. Nepřekonatelnou výhodou je to, že můžeme u každého uživatele jinak nastavit prostředí běhu, tedy systémové proměnné, které ovlivňují chování PHP. Nejčastěji to bude pro každého uživatele jiný php.ini, ale nevadí ani běh různých verzí PHP pro různé uživatele i pro různé weby.

K tomu nám poslouží CGI, případně jeho odnože, které razantním způsobem zvyšují výkon. CGI je protokol, který spojuje webový server a aplikaci. Je v něm definováno jak se má požadavek předat aplikaci a jak má aplikace webovému serveru odpovědět. Jednoduchý princip, vysoká míra bezpečnosti a to hlavně z toho důvodu, že program či skript může být spouštěn s jinými právy než těmi co má webový server. Největší slabinou CGI je jeho výkon. Ke každému požadavku se spustí zvlášť interpretr pro váš skript nebo váš binární program. Fáze spouštění čehokoli je náročná na prostředky systému, zvlášť když jde o interpretr nějakého jazyka. V devadesátých letech, když světu vládlo C/C++, se mohlo zdát CGI jako dobrý nápad, ale dnes se používá maximálně na nějakých routerech pro naservírování administrace systému.

Lidé kolem webových serverů to samozřejmě nenechali jen tak, CGI vylepšili a vzniklo FastCGI. Nevím, jestli zrovna FastCGI bylo první, kdo s podobným principem přišel, ale často o něm uslyšíte v souvislosti s Apachem a PHP a možná ještě častěji s PHP a webovým serverem Lighttpd. Existují i další varianty *CGI. O jedné jsem zde již psal v souvislosti s hostováním webů napsaných v Pythonu.

FastCGI funguje tak, že spustí aplikaci, která nahodí FastCGI rozhraní. Webový server se k ní připojí a deleguje požadavky pro konkrétní web na konkrétní FastCGI server. Odpadá tak neustálé spouštění a rychlost je srovnatelná s mod_php. Bohužel u serverů, které se neumí přizpůsobit, vyžaduje FastCGI více paměti. Pokud např. několik hodin nepřijde žádný požadavek pro jeden web, minimálně jeden proces se v paměti udržuje, a bere tak prostředky systému ostatním. U mod_php stačí nastavit počet subprocesů webového serveru, u FastCGI držíme v paměti pro každý web minimálně jeden proces. Při dnešní ceně pamětí je to vcelku zanedbatelné, ale pokud máme na serveru tisíce webů, které generují minimální přenosy, je to velké plýtvání a v tomto případě je mod_php výkonově lepší.

Nakonec zjistíme, že FastCGI není zas tak omezující a za zvýšenou bezpečnost nejen uživatelů ale i našeho serveru, a tudíž i ušetřené peníze za správu, rozhodně o něm stojí přemýšlet, pokud jsme s webovým serverem právě začali.

Jak na FastCGI a PHP

Apache je na FastCGI i jiná CGI výtečně připravený, takže pokud k tomu nemáte svoje důvody, je to nejjednodušší cesta k PHP přes FastCGI. Implementace máme k dispozici dvě. Jednu najdeme v modulu mod-fastcgi a druhou v mod_fcgid. Jsou navzájem binárně kompatibilní, ale mod_fcgid umí lépe pracovat s běžícími procesy.

To je důležité hlavně z toho důvodu, že Apache velmi rád vyhoví každému požadavku a přes určitou hranici, když už systém nestíhá a spouští se stále nový a nový PHP interpretr, systém nestíhá odbavovat jednotlivé požadavky a časy na odpověď se prodlužují. Je lepší mít omezený počet procesů, které dokážou zpracovávat rychle určité množství požadavků, než se snažit stihnout všechno najednou.

Začneme tedy tím, že nainstalujeme Apache, PHP, suexec a náš FastCGI modul. Suexec se postará o to, aby PHP běželo pod námi definovaným uživatelem. Je to klasický modul jako kterýkoli jiný.

$ sudo aptitude install apache2 libapache2-mod-fcgid php5-cgi apache2-suexec 

Vytvoříme v systému uživatele pro náš web:

$ sudo adduser webuser 

Přidělíme jeho home adresáři omezená práva:

$ sudo chmod 750 /home/webuser 

A přidáme Apache do skupiny k uživateli, aby mohl číst statická data:

$ sudo usermod -G webuser -a www-data 

Přísnější práva se pak řeší pro jednotlivé adresáře v home uživatele.

Abychom spustili PHP jako CGI a ještě pod naším uživatelem, musíme mít v nastaveném docrootu binárku php5-cgi nebo skript, který ji bude volat třeba s upraveným prostředím. Docroot není to samé co DocumentRoot v Apachi a je v suexec zakompilován, ale jelikož tam potřebujeme uložit jen jeden soubor, neměl by to být problém.

# /usr/lib/apache2/suexec -V
 -D AP_DOC_ROOT="/var/www"
 -D AP_GID_MIN=100
 -D AP_HTTPD_USER="www-data"
 -D AP_LOG_EXEC="/var/log/apache2/suexec.log"
 -D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
 -D AP_UID_MIN=100
 -D AP_USERDIR_SUFFIX="public_html" 

Víme, že budeme pracovat s adresářem „/var/www“. Vytvoříme v něm adresář:

$ sudo  mkdir /var/www/webuser 

A v něm vytvoříme skript, třeba „php5-wrap“, s tímto obsahem:

#!/bin/sh
PHP_FCGI_CHILDREN=1
export PHP_FCGI_CHILDREN
PHP_FCGI_MAX_REQUESTS=5000
export PHP_FCGI_MAX_REQUESTS
exec /usr/bin/php5-cgi 

To znamená, že PHP poběží v jednom procesu, který se ukončí po 5000 dotazech. Po těch se vytvoří nový proces. Je to obrana před memory leaky, aby se proces zbytečně nenafukoval, kdyby v něm byla takováto chyba. Zbývá nastavit práva celému adresáři:

$ sudo chown webuser:webuser /var/www/webuser -R 

Teď je potřeba si vybrat, kde bude uložený samotný web. Já volím téměř vždy „~/www“. Uživatelé mají data u sebe a práva stačí řešit pouze na jednom místě.

$ sudo mkdir /home/webuser/www 

Tam nahrajeme jakýkoli web nebo jen testovací skript a pokračujeme u Apache, kde povolíme všechny potřebné moduly.

$ sudo cd /etc/apache2/mods-enabled/
$ sudo ln -s ../mods-available/suexec.load .
$ sudo ln -s ../mods-available/fcgid.load .
$ sudo ln -s ../mods-available/fcgid.conf . 

Poslední soubor může vypadat třeba takto:

<IfModule mod_fcgid.c>
  MaxProcessCount             2
  MaxRequestsPerProcess       1000
  AddHandler    fcgid-script .fcgi
</IfModule> 

Znamená to, že se spustí maximálně dva procesy, které se obnoví (rozuměj ukončí a znovu spustí) po tisíci požadavcích. Dvojku samozřejmě můžete přepsat jakýmkoli číslem. Záleží na počtu uživatelů na vašem stroji a na jeho výkonu. Teď je čas vyzkoušet, jestli Apache všechny změny snesl:

$ sudo /etc/init.d/apache2 reload 

Pokud je vše v pořádku, včetně logů /var/log/apache2/e­rror.log a /var/log/apache2/su­exec.log, můžeme si nakonfigurovat VirtualHost, ten by měl vypadat takto:

CS24_early

<VirtualHost *:80>
    SuexecUserGroup webuser webuser

    ServerName example.com
    DocumentRoot /home/webuser/www

    PHP_Fix_Pathinfo_Enable 1
    <Directory /home/webuser/www>
        Options +Indexes +ExecCGI
        AllowOverride All
        AddHandler fcgid-script .php
        FCGIWrapper /var/www/webuser/php5-wrap .php
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost> 

Nakonec restartujeme Apache a máme hotovo:

$ sudo /etc/init.d/apache2 restart 

Závěr

Pokud se vše povedlo, tak vám PHP běží pod konkrétním uživatelem a jsou na něj tedy aplikována klasická UNIXovská práva, případně i další systém pro omezení oprávnění. Jako bonus můžete každému uživateli spustit jakékoli PHP. V kombinaci s open_basedir si dovolím tvrdit, že můžete postavit velmi neprůstřelný webový server, ale rozhodně se nedá na podobných direktivách stavět bezpečnost webového serveru. Nezapomeňte se také podívat do dokumentace na další možnosti fcgid.

Odkazy

Byl pro vás článek přínosný?

Autor článku

Adam Štrauch je redaktorem serveru Root.cz a svobodný software nasazuje jak na desktopech tak i na routerech a serverech. Ve svém volném čase se stará o komunitní síť, ve které je již přes 100 členů.