Stručně: Local File Inclusion v PHP vzniká, když include, require nebo souborové funkce používají cestu, kterou může návštěvník ovlivnit. Bezpečným řešením je pevný seznam povolených hodnot: parametr vybírá jen ze známých hodnot, samotná cesta nikdy nepochází z požadavku. Na straně serveru ochranu doplňují allow_url_include=Off, open_basedir a restriktivní práva k souborům.
Co je Local File Inclusion v PHP?
Mnoho starších aplikací PHP načítá stránky podle jednoduchého vzoru: parametr v adrese určuje, který soubor se vloží. Pokud je tento parametr bez kontroly předán do include, může útočník vyžádat jiné soubory na serveru. V mírnějším případě přečte konfigurační soubory s přístupovými údaji. V horším případě přiměje PHP spustit obsah, který předtím uložil jinde, například v uploadu nebo v logu.
Pro vedení je důležité: tato zranitelnost sídlí zřídka v jádru CMS, většinou ve vlastních vývojích, starých rozšířeních nebo narychlo postavených doplňkových stránkách. V běžném provozu se neprojeví, protože web funguje normálně.
U Remote File Inclusion načítá PHP kód z cizí adresy. To předpokládá, že je zapnuto allow_url_include. V aktuálních verzích PHP je toto nastavení ve výchozím stavu vypnuto, na starých nebo špatně nakonfigurovaných serverech se ale stále najde.
Typické zranitelné vzory v kódu
Ohrožená jsou místa, kde se hodnota z požadavku, z cookies nebo z databáze stane součástí cesty k souboru. Častými příklady jsou přepínače jazyka, které načítají jazykový soubor podle parametru, výběr stránek přes parametr v adrese, výběr šablony nebo motivu a skripty pro stahování dokumentů. Připojené .php na konci cesty přitom spolehlivě nechrání, protože útočníci mohou sáhnout po existujících souborech PHP.
První přehled získáte hledáním vložení, která obsahují proměnné. Najde i neškodné nálezy, ale ukáže, kam se musí podívat podrobněji:
grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
| grep -v '/vendor/'
U každého nálezu zkontrolujte, odkud proměnná pochází a zda ji může návštěvník ovlivnit.
Bezpečný vzor: seznam povolených místo cesty z požadavku
Nejspolehlivější ochranou je, že návštěvník vybírá jen klíč a aplikace sama rozhoduje, který soubor k němu patří. Cesta se nikdy neskládá ze vstupů.
<?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átký, snadno se kontroluje a nenechává žádný prostor. V moderních frameworcích tuto úlohu přebírá router; i tam by se kontrolery a šablony nikdy neměly určovat z volných parametrů.
Když musí být názvy souborů dynamické: realpath a základní adresář
Někdy není pevný seznam praktický, například u stahování z adresáře dokumentů. Pak zkontrolujte vyřešenou cestu proti pevnému základnímu adresáři a povolte jen jednoduché názvy souborů.
<?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čit místo vložit: readfile() nespouští žádný kód.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);
Rozhodující jsou dva body. Regulární výraz nepovoluje oddělovače adresářů ani speciální znaky. A soubory, které se mají jen doručit, se čtou pomocí readfile(), nikoli include, aby se obsažený kód nikdy nespustil. str_starts_with() je k dispozici od PHP 8.0.
Zabezpečení na straně serveru: php.ini, open_basedir a práva
Bezpečný kód je první linie obrany. Konfigurace serveru zajistí, že přehlédnutá chyba způsobí menší škodu.
; php.ini nebo pool FPM (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen = Off ; jen pokud aplikace nenačítá externí adresy URL
open_basedir = /var/www/kunde-a/:/tmp/kunde-a/
display_errors = Off
log_errors = On
allow_url_fopen potřebují některé aplikace pro aktualizace nebo rozhraní. Vypnutí nejdřív vyzkoušejte v testovacím prostředí. open_basedir omezuje PHP na vlastní adresáře webu, což je zvlášť důležité na serverech s více zákazníky. Podrobnosti najdete v příručkách open_basedir a izolace jednotlivých webů a Jak bezpečně nakonfigurovat php.ini.
Doplňkově by složky pro uploady a cache neměly být spustitelné a logy neměly ležet ve webrootu. Útočníkovi tak chybí místa, kam by mohl uložit vlastní obsah pro vložení.
Podle čeho poznáte pokusy o zneužití v lozích
Pokusy o zneužití file inclusion zanechávají většinou stopy v access logu. Nápadné jsou parametry, které obsahují změny adresáře, absolutní systémové cesty, obálky streamů nebo úplné cizí adresy, zejména u parametrů jako page, file, template nebo lang. Jednotlivý nález je většinou automatizované skenování. Důležité je, zda byl takový požadavek zodpovězen stavovým kódem 200 a neobvyklou velikostí odpovědi.
Jak postupovat systematicky, popisuje příručka Vyhodnocení logů webového serveru po útoku. Pokud najdete náznaky úspěšných přístupů, vycházejte z toho, že konfigurační soubory byly přečteny, a obnovte v nich obsažené přístupové údaje.
Patří sem především hesla k databázi, klíče platebních a přepravních služeb, přístupy SMTP a tajné klíče CMS. Zkontrolujte také, zda od prvního podezřelého přístupu nevznikly nové soubory. Kdo mohl soubory číst, mohl se také pokusit uložit vlastní. V takovém případě už nejde jen o chybu v kódu, ale o bezpečnostní incident, který by se měl zpracovat strukturovaně.
Trvalé zajištění oprav
Po opravě se vyplatí ochrannou funkci zajistit automatizovanými testy. Jednoduchý test předá výběru stránek neznámé hodnoty, změny adresáře a prázdné vstupy a ve všech případech očekává výchozí stránku nebo chybu 404. Takové testy zabrání tomu, aby pozdější změna zranitelnost nepozorovaně znovu otevřela.
Pro vedení a projektové vedoucí je užitečná otázka na agenturu: Existují v aplikaci místa, kde se soubory načítají podle parametrů, a jak jsou zabezpečena? Konkrétní odpověď s uvedením míst a testů je dobré znamení.
Jak konkrétně pomáhá PHP SOS?
- V kódu cíleně hledáme
include,requirea souborové funkce se vstupy z požadavku a každý nález hodnotíme. - Nastavujeme
allow_url_include,open_basedira vhodná práva pro každý web a ověřujeme, že aplikace poté dál funguje. - Vyhodnocujeme logy kvůli pokusům o zneužití a ujasňujeme, zda je nutné obnovit přístupové údaje.
- Pro nezávislou kontrolu nabízíme pro aplikace PHP bezpečnostní audit s penetračním testem.
Časté dotazy
Jaký je rozdíl mezi LFI a RFI?
U Local File Inclusion aplikace načte soubor, který už na serveru leží, ale neměl by se načítat. U Remote File Inclusion se kód stahuje z cizí adresy. RFI vyžaduje nebezpečné nastavení PHP, LFI nikoli.
Stačí z parametru odstranit ../?
Ne. Takové filtry lze často obejít kódováním nebo variantami a přehlížejí absolutní cesty. Bezpečné je jen pevné přiřazení povolených hodnot k souborům nebo kontrola vyřešené cesty proti základnímu adresáři.
Chrání open_basedir před file inclusion úplně?
Ne, omezuje jen adresáře, k nimž smí PHP přistupovat. V rámci těchto hranic zůstává zranitelnost zneužitelná. open_basedir je dodatečná ochranná vrstva a bezpečný kód nenahrazuje.
Týká se LFI i CMS jako WordPress?
Jádro běžných CMS se v tomto bodě považuje za dobře prověřené. Zranitelnosti se objevují spíše v rozšířeních, šablonách a vlastních vývojích. Aktualizace rozšíření a pohled do jejich bezpečnostních hlášení jsou proto důležité.