FPD aneb Full Path Disclosure

FPD je jedna z přibližně 17576 třípísmenných zkratek používaných na Internetu a jedna z mála, kde písmeno F neznamená, hmm, třeba friend. Význam zkratky, o kterém bych vám rád povyprávěl je však důležitý pro bezpečnost webových aplikací. FPD totiž v oblasti webové bezpečnosti znamená Full Path Disclosure, do češtiny přeloženo například jako odhalení, nebo raději lépe prozrazení úplné cesty. Cestou je myšlena cesta k souboru, k právě spuštěnému skriptu, ne cesta domů z vašeho oblíbeného baru.
Full Path Disclosure na rapidog.com
Pokud něco podobného uvidíte na vlastním webu, pak váš web neodolá útoku Full Path Disclosure a plnou cestu vyzradí. A to by rozhodně neměl. Už vidím, jak se vaše dlaň blíží k vašemu obličeji a jak si říkáte, že cestu ke svým souborům přece znáte a jak nechápavě kroutíte hlavou. Zobrazená informace je totiž důležitá nejen pro vás, ale i pro zlé hochy, kteří chtějí na váš web nějakým způsobem zaútočit

Co všechno Full Path Disclosure vyzradí?

Ze zobrazené chybové hlášky lze kromě plné cesty ke skriptu search.php vyčíst i pár dalších důležitých informací:

  • web běží na PHP (No shit, Sherlock!)
  • má zapnuté zobrazování chybových hlášek (Thank you, Captain Obvious!)
  • operační systém serveru je nějaká varianta Unixu
  • web nepoužívá žádný rozšířený framework (adresářová struktura např. Nette nebo Zend Frameworku je jiná), řeší tedy veškeré zabezpečení “na zelené louce” a tudíž s pravděpodobnými chybami
  • strtolower() očekává řetězec jako první parametr (to kdybyste to náhodou zapomněli)

Jen tak mezi námi, i ta samotná plná cesta je důležitá, na některých serverech totiž cesty neodpovídají názvům domén a soubory jsou uloženy v adresářích s generovanými názvy (například /www/sites/8/site13148/public_html je cesta k souborům webu adriadatabanka.com) a tak se může stát, že sami nevíte, kde soubory vlastně máte. Full Path Disclosure vám je tedy pomůže najít (a nejspíš proto ani půl roku po nahlášení tato zranitelnost není opravena). A pomůže je najít nejenom vám, ale všem, kdo je najít chtějí, tedy i případným zlým hochům.

Někdy ze zobrazených informací lze vyčíst i verze použitých nástrojů a stack trace, tedy kompletní seznam souborů a funkcí, které se volaly do té doby, než došlo k chybě (třeba u Chrise Shifletta, autora knihy Essential PHP Security, na jeho Not Found stránce) nebo to, jaký databázový server a jaké rozšíření se používá pro přístup a jak je odesílání dotazů ošetřeno, to když uvidíte hlášku podobnou této:

mysql_real_escape_string() expects parameter 1 to be string, array given

Zkrátka Full Path Disclosure útok může vyzradit spoustu informací, které lze pak využít k plánování a provádění dalších útoků s mnohem větším a horším dopadem, jako třeba SQLIA (SQL Injection Attack, to je když vám někdo z databáze vyláme uživatelská jména a hesla) nebo LFI (Local File Inclusion, to je když vám někdo stáhne soubor, který nemá stahovat, třeba konfigurační soubor s přístupovými údaji k databázovému serveru). Tento útok by se tedy dal nazvat lépe a obecněji jako FPIADHYB (Full Path, Information and Architecture Disclosure, Hell Yeah, Baby). V otázkách bezpečnosti jsem se naučil zobecňovat a vidět svět černobíle a proto vždy říkám, že útok Full Path Disclosure je stejně závažný, jako třeba zmíněné SQLIA nebo LFI.

Jak vyrobit Full Path Disclosure

Full Path Disclosure je poměrně běžný útok a často je i úspěšný (a občas se dotkne i kováře a jeho kobyly nebo webu, který je napsán v nějakém známém frameworku), je proto dobré vědět, jak se dá takový útok provést, to abychom se proti němu dokázali efektivně bránit. Pokud se budete snažit vyrobit útok FPD na váš vlastní web (na cizí weby přeci neútočíme, víme?), tak vězte, že fantazii se meze nekladou, základem je vyvolat nějakou chybovou hlášku PHP, která standardně obsahuje i plnou cestu k souboru, kde k chybě došlo. Zde je pár základních triků, které vám pomůžou v experimentování.

Přetypujte parametry v URL nebo ve formuláři

Pokud za název parametru přidáte prázdné hranaté závorky, PHP ze vstupního parameteru udělá pole. S tímto trikem skript ovšem nepočítá a tuto proměnnou v klidu předá funkcím, které pracují pouze s řetězci (například strtolower(), nebo preg_match()) a když se jim předá pole, tak zahlásí chybu. Příkladem budiž třeba search.php?q[]=…, výsledná chyba je pak zachycena na úvodním obrázku. Ve formulářích odesílaných metodou POST pak stačí pomocí nástrojů vašeho prohlížeče změnit název INPUT prvku přidáním hranatých závorek například na name="search[]".

Prázdný session identifikátor

Identifikátor session má jistá omezení, co se týká povolených znaků a jejich počtu. Nepovoleným znakem je třeba mezera a nepovolený počet znaků je třeba nula, tedy prázdný řetězec. Pokud identifikátor s nepovoleným znakem nebo nepovolenou délkou použijeme, session_start() zahlásí chybu. Identifikátor session se dá změnit například přímo ve vašem prohlížeči (hledejte cookie s názvem PHPSESSID nebo něco podobného), případně jej můžete změnit pomocí JavaScriptu vložením následujícího kódu do řádku s adresou v prohlížeči ve chvíli, kdy máte načten váš web a následným reloadem stránky.

javascript:void(document.cookie="PHPSESSID=");

Pokud web přenáší identifikátor session v URL (což by dnes už žádný moderní web neměl dělat), tak není nic jednoduššího než změnit daný parametr v URL.

Přímé volání souborů vyžadujících nahrané knihovny

Zkuste si v prohlížeči přímo zavolat soubory, které pouze vkládáte (includujete) do jiných souborů. Je možné, že uvidíte hlášku podobnou této:

Fatal error: Call to undefined function login() in /stor1/vemex/html/form.php on line 7

Ano,  i v tomto případě váš web právě neodolal FPD útoku.

Vynechání povinných parametrů

Pokud skript vyžaduje nějaké parametry, zkuste si ho zavolat bez těchto parametrů. Možná budete překvapeni.

Chybějící soubory

Zkontrolujte, že všechny vaše veřejně dostupné skripty mohou číst soubory, které jsou potřeba k jejich úspěšnému vykonání.

Nevalidní vstup

Pokud nějaký váš skript zpracovává HTML nebo XML, zkuste mu poslat well-deformed data, nebo něco, co jako HTML nebo XML moc nevypadá, třeba se mu to nebude líbit.Full Path Disclosure na okd.cz

Prozrazení před přesměrováním

Pokud je na webu nějaký skript, který přesměrovává, zkuste se nějakým speciálním nástrojem (Firebug, Fiddler, curl) podívat na jeho výstup, když budete zkoušet výše popsané triky. Je totiž možné, že skript prozradí plnou cestu a další informace, ale poté přesměruje kamsi do pryč, takže v konvenčním prohlížeči očekávanou chybovou hlášku neuvidíte.

Různé pomocné a debugovací skripty

Spousta skriptů typu system check toho taky vyzradí spoustu a to nemusí ani hlásit žádnou chybu. Do této skupiny patří i všechny ty /info.php a /phpinfo.php skripty, tedy výstup z funkce phpinfo().

Jak zabránit FPIADHYB

Můžete se snažit sebevíc, ošetřovat různé stavy a typy proměnných a vůbec všechno dělat a psát defenzivně, ale milé PHP vás stejně vždy něčím překvapí. Jediná možnost je tedy vypnout zobrazování chybových hlášek pomocí direktivy display_errors. Nejlépe už přímo v konfiguraci serveru, když to budete dělat až někde ve skriptu, tak už může být pozdě. V souboru .htaccess to uděláte takto:

php_flag display_errors off

Chyby tedy nebudou zobrazovány, ale rozhodně je dobré, abychom o nich věděli. Měli bychom je tedy někam ukládat, nejlépe do nějakého logu. To se zařídí direktivou log_errors, v souboru .htaccess takto:

php_flag log_errors on

Do jakého logu se budou chybové hlášky ukládat určuje direktiva error_log. Zkuste se podívat třeba do serverového error logu.

Rozhodně nikdy nevypínejte zobrazování chybových hlášek tím, že zakážete jejich generování například pomocí error_reporting(0); tím zakážete i jejich ukládání do logu. Kde není chybová hláška, tam totiž není co ukládat.

Nutno poznamenat, že FPIADHYB není jen záležitost PHP, ale týká se v podstatě všech technologií, které nějakým způsobem vypisují chybové hlášky obsahující užitečné informace.

TL;DR: nastavte display_errors = off

Přijďte si o FPIADHYB, SQLIA, LFI a BBQ popovídat na školení bezpečnosti PHP aplikací nebo, pokud jste dobří v odbíhání od původního tématu, klidně i na jakékoliv jiné školení.


Disclaimer: mám za to, že odkazovaní o problémech vědí, jen je nezajímají. Jinak si totiž nedovedu vysvětlit to, že i po několika měsících od nahlášení nejsou vyřešeny. Pokud odkazovaní o problémech nevědí, tak jim, milí čtenáři, dejte vědět a pošlete jim odkaz na tento článek. Děkuji.

10 komentářů u “FPD aneb Full Path Disclosure
  1. Omlouvam se – zrejme me budes mit za chlapce prostsiho televizni rosnicky, ale proste mi to neda. Ohledna chybovejch hlasek pred presmerovanim: jakym pusobem to ma fungovat? Pokud vysolim nejakej output, bude mi header s location na nic – cili nepresmeruju. Leda ze by se presmerovavalo js a tam zas nevidim moc moznost dostat sever side hlasku. Jak mi v tomhle ma pomoc curl/wget oproti normalnimu browseru? Dik za vysvetleni.

  2. 2 starenka: No, to je jednoduche. To, ze posles hlavicku s pozadavkem na presmerovani a browser to udela, jeste neznamena, ze neposles nejaka data, resp. obsah stranky. Takze pak staci neco, co nereaguje na presmerovani (wget, curl, nebo cokoli co tu stranku jen stahne) a uvidis tam tu chybovou hlasku.

    Navic v PHP muzes vypsat neco na vystup a az pak presmerovat, tusim, ze se na to vyuziva nejakej buffer.

  3. [to Protozoon]: Ano, používá se na to output buffer
    ob_start();
    //něco vypisuju nebo přesměrovávám
    ob_end_flush(); //vypisuju buffer

  4. Protozoon a starenka: Nehledej v tom žádnou složitost. Hlavička Location neukončuje výstup a stránka normálně může pokračovat obsahem – i když jej uživatel nikdy neuvidí. Je to pouze hlavička stejně jako Content-Type a další. Pokud tedy pošleš Location a následně neukončíš skript pomocí die(), tak pomocí echo() lze poslat regulérní obsah stránky včetně chybových hlášek.

    Zkus si poslat hlavičku Location a následně vygenerovat a poslat na výstup několik MB textu. I když se ti nezobrazí, prohlížeč jej poslušně stahne.

  5. Protozoon, starenka: Většina lidí asi dává za přesměrování exit. Aby se zbytečně nezpracovával zbytek skriptu (tahat data z databáze, když se pak zahodí atd.), kde by se mohly vyskytnout chyby, proti kterým se přesměrováním na nějakou error stránku bráním. Pokud dojde k chybě před přesměrováním, dostanu klasickou Headers already sent by… Často se dělá přesměrovací pojistka, kdy se do stránky udělá přesměrování pomocí meta i pomocí javascriptu a pro jistotu ještě s textem “Pokud nedošlo k automatickému přesměrování, klikněte na následující odkaz…”. A o tom je zřejmě zmínka v článku.

  6. základ je skrytí pomocí htacess. Kdo píše třídy, tak problém snižuje, prostě se jen definuje třídu.

    Jinak ohledně NEtte:, holt nemá zaplou laděnku.

  7. Jen tak narychlo
    class Foo extends Bar {}
    Pokud si takovýhle soubor někdo otevře a já nebudu mít na serveru vypnuté zobrazování errorů, tak mám úplně stejný problém jako kdybych třídy nepoužíval.

  8. Zobrazování skriptů přímo se dá zabránit také tak, že pokud v loaderu (či podobném scriptu) definujete konstantu, a obsah si pak taháte dynamicky, stačí na začátek všech svých souborů (scriptů) přidat podobný řádek tomuto:

    defined('START') or die('No direct access allowed.');

    dežo možná narážel na to, že pokud píšeme objektově, tak spousta souborů jsou pouze knihovny, a pokud takový soubor uživatel chce zobrazit přímo, tak efekt je žádný, pokud se nemýlím?!

  9. Pomocí ukončování die() se dá poznat, jestli daný soubor vůbec existuje, nebo ne. To může mnoho napovědět. Lepší je prostě zobrazit standardní Not Found/404 stránku přesně takovou, jakou zobrazujeme, když návštěvník nenajde to, co hledal.

    Pokud píšeme objektově, tak nás to nespasí. Stačí mít třídu, které chybí rodičovská třída a chybová hláška při přímém přístupu je na světě, přesně tak, jak ukázal Jakub Tesárek o příspěvek výše.

Napsat komentář

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

*

Mete pouvat Markdown: **Tun**, *kurzva*, `kd` atd.

Můžete používat následující HTML značky a atributy: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>