Stručne: Local File Inclusion v PHP vzniká, keď include, require alebo súborové funkcie používajú cestu, ktorú môže návštevník ovplyvniť. Bezpečným riešením je pevný zoznam povolených: parameter vyberá iba zo známych hodnôt, samotná cesta nikdy nepochádza z požiadavky. Na strane servera ochranu dopĺňajú allow_url_include=Off, open_basedir a reštriktívne práva k súborom.
Čo je Local File Inclusion v PHP?
Mnohé staršie aplikácie PHP načítavajú stránky podľa jednoduchého vzoru: parameter v adrese určuje, ktorý súbor sa vloží. Ak sa tento parameter odovzdá do include bez kontroly, útočník môže vyžiadať iné súbory na serveri. V neškodnejšom prípade prečíta konfiguračné súbory s prístupovými údajmi. V horšom prípade prinúti PHP vykonať obsah, ktorý predtým uložil inde, napríklad v uploade alebo v súbore logu.
Pre vedenie je dôležité: táto zraniteľnosť sídli zriedka v jadre CMS, ale zväčša vo vlastných vývojoch, starých rozšíreniach alebo narýchlo postavených doplnkových stránkach. V bežnej prevádzke si ju nevšimnete, pretože web funguje normálne.
Pri Remote File Inclusion PHP načíta kód z cudzej adresy. Predpokladá to, že je allow_url_include aktivované. V aktuálnych verziách PHP je toto nastavenie štandardne vypnuté, na starých alebo nesprávne nakonfigurovaných serveroch ho však stále nájdete.
Typické zraniteľné vzory v kóde
Ohrozené sú miesta, kde sa hodnota z požiadavky, z cookies alebo z databázy stáva súčasťou cesty k súboru. Častými príkladmi sú prepínače jazyka, ktoré načítavajú jazykový súbor podľa parametra, výber stránok cez parameter v adrese, výber šablón alebo tém a skripty na sťahovanie dokumentov. Pripojené .php na konci cesty pritom spoľahlivo nechráni, pretože útočníci môžu siahnuť po existujúcich súboroch PHP.
Prvý prehľad získate hľadaním vkladaní, ktoré obsahujú premenné. Poskytne aj neškodné nálezy, ale ukáže, kam sa treba pozrieť presnejšie:
grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
| grep -v '/vendor/'
Pri každom nálezu skontrolujte, odkiaľ premenná pochádza a či ju môže návštevník ovplyvniť.
Bezpečný vzor: zoznam povolených namiesto cesty z požiadavky
Najspoľahlivejšou ochranou je, že návštevník vyberie iba kľúč a aplikácia sama rozhodne, ktorý súbor k nemu patrí. Cesta sa nikdy nesklada zo vstupov.
<?php
declare(strict_types=1);
$seiten = [
'start' => __DIR__ . '/../vorlagen/start.php',
'leistungen' => __DIR__ . '/../vorlagen/leistungen.php',
'kontakt' => __DIR__ . '/../vorlagen/kontakt.php',
];
$wahl = is_string($_GET['seite'] ?? null) ? $_GET['seite'] : 'start';
if (!array_key_exists($wahl, $seiten)) {
http_response_code(404);
$wahl = 'start';
}
require $seiten[$wahl];
Tento vzor je krátky, ľahko sa kontroluje a nenecháva priestor. V moderných frameworkoch túto úlohu preberá router; aj tam by sa kontroléry a šablóny nikdy nemali určovať z voľných parametrov.
Ak musia byť názvy súborov dynamické: realpath a základný adresár
Niekedy nie je pevný zoznam praktický, napríklad pri sťahovaní z adresára dokumentov. Vtedy skontrolujte vyriešenú cestu voči pevnému základnému adresáru a povoľte iba jednoduché názvy súborov.
<?php
declare(strict_types=1);
const BASIS = '/var/www/daten/dokumente';
function sichererPfad(string $name): string
{
if (!preg_match('/^[a-z0-9_-]{1,64}\.pdf$/', $name)) {
throw new InvalidArgumentException('Ungültiger Dateiname.');
}
$basis = realpath(BASIS);
$pfad = realpath($basis . DIRECTORY_SEPARATOR . $name);
if ($basis === false || $pfad === false
|| !str_starts_with($pfad, $basis . DIRECTORY_SEPARATOR)) {
throw new RuntimeException('Datei nicht gefunden.');
}
return $pfad;
}
// Doručiť namiesto vložiť: readfile() nevykonáva žiadny kód.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);
Rozhodujúce sú dva body. Regulárny výraz nepovoľuje oddeľovače adresárov ani špeciálne znaky. A súbory, ktoré sa majú iba doručiť, sa čítajú pomocou readfile(), nie pomocou include, aby sa obsiahnutý kód nikdy nevykonal. str_starts_with() je k dispozícii od PHP 8.0.
Zabezpečenie na strane servera: php.ini, open_basedir a práva
Bezpečný kód je prvá obranná línia. Konfigurácia servera zaisťuje, že prehliadnutá chyba narobí menej škody.
; php.ini alebo pool FPM (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen = Off ; len ak aplikácia nenačítava externé URL
open_basedir = /var/www/kunde-a/:/tmp/kunde-a/
display_errors = Off
log_errors = On
allow_url_fopen potrebujú niektoré aplikácie pre aktualizácie alebo rozhrania. Vypnutie najprv otestujte v testovacom prostredí. open_basedir obmedzuje PHP na vlastné adresáre webu, čo je obzvlášť dôležité na serveroch s viacerými zákazníkmi. Podrobnosti sú v príručkách open_basedir a izolácia každého webu a Bezpečná konfigurácia php.ini.
Dopĺňajúco by adresáre s uploadmi a cache nemali byť spustiteľné a súbory logov by nemali ležať vo webroot. Útočníkovi tak chýbajú miesta, kam by mohol uložiť vlastný obsah na vloženie.
Podľa čoho v logoch rozpoznáte pokusy o zneužitie
Pokusy zneužiť File Inclusion zanechávajú zvyčajne stopy v access logu. Nápadné sú parametre, ktoré obsahujú zmeny adresárov, absolútne systémové cesty, obaly streamov alebo úplné cudzie adresy, najmä pri parametroch ako page, file, template alebo lang. Jeden nález je zväčša automatizované skenovanie. Dôležité je, či sa na takúto požiadavku odpovedalo stavovým kódom 200 a neobvyklou veľkosťou odpovede.
Ako pritom postupovať systematicky, popisuje príručka Vyhodnotenie logov webového servera po útoku. Ak nájdete indície úspešných prístupov, vychádzajte z toho, že sa čítali konfiguračné súbory, a obnovte prístupové údaje, ktoré obsahujú.
Patria sem predovšetkým heslá k databáze, kľúče pre platobné a prepravné služby, prístupy SMTP a tajné kľúče CMS. Skontrolujte navyše, či od prvého podozrivého prístupu nevznikli nové súbory. Kto mohol čítať súbory, mohol sa pokúsiť aj uložiť vlastné. V takom prípade už nejde len o zraniteľnosť v kóde, ale o bezpečnostný incident, ktorý by sa mal riešiť štruktúrovane.
Opravy trvalo zabezpečiť
Po oprave sa oplatí ochrannú funkciu zabezpečiť automatizovanými testami. Jednoduchý test odovzdá výberu stránok neznáme hodnoty, zmeny adresárov a prázdne vstupy a vo všetkých prípadoch očakáva štandardnú stránku alebo chybu 404. Takéto testy bránia tomu, aby neskoršia zmena zraniteľnosť nepozorovane opäť otvorila.
Pre vedenie a projektových zodpovedných je užitočná otázka agentúre: sú v aplikácii miesta, kde sa súbory načítavajú podľa parametrov, a ako sú zabezpečené? Konkrétna odpoveď s uvedením miest a testov je dobré znamenie.
Ako konkrétne pomáha PHP SOS?
- V kóde cielene hľadáme
include,requirea súborové funkcie so vstupmi z požiadavky a každý nález posudzujeme. - Nastavíme
allow_url_include,open_basedira vhodné práva pre každý web a overíme, že aplikácia potom naďalej funguje. - Logy vyhodnocujeme kvôli pokusom o zneužitie a vyjasňujeme, či sa musia obnoviť prístupové údaje.
- Pre nezávislú kontrolu ponúkame bezpečnostný audit s penetračným testom pre aplikácie PHP.
Časté otázky
Aký je rozdiel medzi LFI a RFI?
Pri Local File Inclusion aplikácia načíta súbor, ktorý už leží na serveri, ale nemal by sa načítať. Pri Remote File Inclusion sa kód doťahuje z cudzej adresy. RFI predpokladá nebezpečné nastavenie PHP, LFI nie.
Stačí z parametra odstrániť ../?
Nie. Takéto filtre sa často dajú obísť kódovaním alebo variantmi a prehliadnu absolútne cesty. Bezpečné je iba pevné priradenie povolených hodnôt k súborom alebo kontrola vyriešenej cesty voči základnému adresáru.
Chráni open_basedir úplne pred File Inclusion?
Nie, obmedzuje iba adresáre, ku ktorým môže PHP pristupovať. V rámci týchto hraníc zostáva zraniteľnosť zneužiteľná. open_basedir je dodatočná ochranná vrstva a nenahrádza bezpečný kód.
Sú CMS ako WordPress dotknuté LFI?
Jadro bežných CMS sa v tomto bode považuje za dobre preverené. Zraniteľnosti sa skôr objavujú v rozšíreniach, témach a vlastných vývojoch. Aktualizácie rozšírení a pohľad na ich bezpečnostné hlásenia sú preto dôležité.