Stručně: SSRF v PHP zabráníte tak, že vaše aplikace stahuje jen adresy z pevného seznamu povolených cílů, výhradně přes HTTPS a bez následování přesměrování. Přeložená adresa IP se zkontroluje proti privátním a rezervovaným rozsahům a poté se pro spojení zafixuje. Firewall pro odchozí provoz zajistí, že zůstanou interní systémy nedostupné i při chybě v kódu.
Když vlastní server posílá požadavky za cizí osoby
Náhledy odkazů v chatu, import obrázku produktu přes URL, webhooky do partnerských systémů, čtečka kanálů v CMS: mnoho aplikací PHP stahuje adresy, které zadal uživatel. Je to užitečná funkce a zároveň dveře dovnitř.
U Server-Side Request Forgery, zkráceně SSRF, přiměje útočník aplikaci odeslat požadavek na cíl dle své volby. Háček: požadavek přichází ze samotného serveru. Dosáhne tak systémů, které jsou zvenčí uzavřené, například databází, administračních rozhraní ve vnitřní síti, služeb, které naslouchají jen na localhost, nebo služeb metadat poskytovatelů cloudu.
V OWASP Top 10 z roku 2021 bylo SSRF samostatnou kategorií. V projektech PHP se zranitelnost často objevuje tam, kde někdo rychle postavil funkci „přidat obrázek přes URL“.
SSRF v PHP: které funkce stahují adresy
Adresy nestahuje jen cURL. Na tato místa byste si měli dávat pozor:
| Funkce nebo oblast | Proč je relevantní |
|---|---|
cURL (curl_exec()) |
nejčastější cesta, možné mnoho protokolů |
file_get_contents(), fopen(), copy() s URL |
používají obálky URL, pokud je aktivní allow_url_fopen |
getimagesize() a knihovny pro obrázky |
mohou načítat vzdálené soubory |
| zpracování XML, klienti SOAP | za určitých okolností načítají externí zdroje |
| knihovny HTTP jako Guzzle | stavějí na cURL nebo streamech, stejná pravidla |
Pokud vaše aplikace nepotřebuje otevírat vzdálené soubory přes souborové funkce, vypněte allow_url_fopen na straně serveru. Příbuzné téma, vkládání souborů přes cesty z požadavku, řeší příručka Prevence Local a Remote File Inclusion.
Obrana v pěti krocích
OWASP doporučuje kombinaci opatření, která by šla jednotlivě obejít, ale společně drží:
- Seznam povolených hodnot místo volné URL: Nejlépe uživatel vybírá jen ze známých cílů, nebo zadá pouze identifikátor a adresu sestaví aplikace sama.
- Povolit jen HTTPS: Jiné protokoly jako
file,gopherneboftpv cURL vyloučit, i pro přesměrování. - Nenásledovat přesměrování: Povolená adresa může jinak přesměrovat na interní cíl.
- Zkontrolovat a zafixovat přeloženou adresu: Přeložit název hostitele, zkontrolovat IP proti privátním rozsahům, loopbacku a rozsahům link-local a použít přesně tuto IP pro spojení. Jinak může druhé překládání DNS vrátit jiný cíl.
- Omezit síť: Webserver smí navenek dosáhnout jen cílů, které potřebuje.
Bezpečný vzor s cURL
Následující příklad zavádí kroky 1 až 4 pro IPv4. Názvy hostitelů a postup jsou příklady; pro IPv6 platí stejné kontroly.
<?php
declare(strict_types=1);
function ladeVonPartner(string $url): string
{
$teile = parse_url($url);
if (!is_array($teile) || ($teile['scheme'] ?? '') !== 'https'
|| isset($teile['user']) || isset($teile['pass']) || isset($teile['port'])) {
throw new InvalidArgumentException('Adresse nicht erlaubt.');
}
$host = strtolower($teile['host'] ?? '');
$erlaubt = ['api.partner.example', 'bilder.partner.example'];
if (!in_array($host, $erlaubt, true)) {
throw new InvalidArgumentException('Ziel nicht erlaubt.');
}
$ip = gethostbyname($host);
$oeffentlich = filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);
if ($oeffentlich === false) {
throw new RuntimeException('Ziel löst auf eine nicht erlaubte Adresse auf.');
}
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_RESOLVE => [$host . ':443:' . $ip],
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 10,
]);
$antwort = curl_exec($ch);
if ($antwort === false) {
throw new RuntimeException('Abruf fehlgeschlagen.');
}
return $antwort;
}
Některé podrobnosti jsou důležité. gethostbyname() při chybě vrací název hostitele; následná kontrola IP to zachytí. Příznaky FILTER_FLAG_NO_PRIV_RANGE a FILTER_FLAG_NO_RES_RANGE vylučují podle příručky PHP mimo jiné rozsahy 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 a 169.254.0.0/16. Od PHP 8.2 existuje navíc FILTER_FLAG_GLOBAL_RANGE, který připouští jen globálně platné adresy. CURLOPT_RESOLVE zajistí, že cURL použije přesně zkontrolovanou adresu, zatímco název hostitele a ověření certifikátu zůstanou zachovány.
Dva další body omezují, co se útočník ze zranitelnosti SSRF dozví. Odpovědi a chybová hlášení stahovaného cíle nevracejte uživateli nefiltrovaně: už rozlišení mezi „spojení odmítnuto“ a „vypršení času“ prozradí, jaké interní služby existují. A omezte dobu běhu a velikost odpovědi, aby funkce nešla použít k přetížení vlastního serveru. Webhooky na adresy, které si zákazníci zadávají sami, nejlépe odesílejte z fronty, ne během probíhajícího požadavku.
Pokud vaše aplikace opravdu musí stahovat libovolné adresy, například pro náhledy odkazů, přesuňte tuto funkci do samostatné služby bez přístupu do vnitřní sítě. Seznam povolených hodnot pak není možný, tím důležitější je oddělení sítě.
Síťová úroveň jako záchranná síť
Kontroly v kódu mohou mít mezery, například když dvě knihovny rozloží URL různě. OWASP radí takové nesrovnalosti řešit odmítnutím a navíc omezovat na úrovni sítě. Webserver potřebuje navenek většinou jen málo cílů: zdroje balíčků, platební a dopravní služby, poštovní relay, určitá rozhraní.
Firewall pro odchozí provoz pomáhá dvojnásob. Zastaví požadavky SSRF do vnitřní sítě a po průniku, například přes nezáplatovanou verzi PHP, ztěžuje stahování dalších nástrojů. Jak sestavit pravidla, popisuje článek Firewall pro webserver: otevřít jen nutné porty. Jaká spojení server skutečně vytváří, ukazuje Kontrola odchozích spojení serveru.
V cloudových prostředích patří k tomu zpevněná varianta služby metadat. OWASP uvádí pro AWS přechod na IMDSv2 a vypnutí IMDSv1.
Jak SSRF vyhledat ve stávajícím kódu
Rychlým začátkem je vyhledání míst, kde funkce pro stahování dostávají hodnoty z požadavku:
grep -rnE "(curl_init|file_get_contents|fopen|getimagesize|copy)\s*\(" src/ --include="*.php" | grep -E '\$_(GET|POST|REQUEST)|\$url'
grep -rn "CURLOPT_FOLLOWLOCATION" src/ --include="*.php"
Nálezy jsou úkoly ke kontrole. Obzvlášť důkladně se podívejte na funkce, které používají administrátoři: importy, nastavení webhooků, kontroly aktualizací pluginů.
Snadno se přehlédnou generátory PDF, které převádějí HTML s vloženými obrázky nebo stylopisy na dokumenty. Pokud část tohoto HTML pochází z uživatelských vstupů, například u faktur s volitelným logem, generátor za určitých okolností stahuje adresy, které určí uživatel. Zkontrolujte tam nastavení pro načítání vzdálených zdrojů a vypněte ho, pokud není potřeba.
Jak konkrétně pomáhá PHP SOS?
- Najdeme všechna místa, kde vaše aplikace stahuje adresy, i v pluginech a knihovnách.
- Zavedeme centrální funkci pro stahování se seznamem povolených hodnot, omezením protokolů a kontrolou IP.
- Stanovíme pravidla pro odchozí provoz a ochráníme služby metadat v cloudových prostředích.
- Provedení ověříme zvenčí a zdokumentujeme povolené cíle.
Nezávislou kontrolu vaší aplikace popisuje bezpečnostní audit a penetrační test.
Časté dotazy
Které funkce PHP lze zneužít pro SSRF?
Všechny, které stahují adresu z uživatelských vstupů: cURL, file_get_contents a fopen s obálkami URL, funkce pro obrázky a XML, které načítají vzdálené soubory, i knihovny pro webhooky, kanály nebo náhledy odkazů.
Stačí zablokovat interní adresy IP?
Samotné ne. OWASP označuje černé listiny za snadno obejitelné a doporučuje seznamy povolených hodnot. Aplikace navíc musí použít přesně tu adresu, kterou zkontrolovala, a nesmí následovat přesměrování.
Proč je SSRF v cloudu obzvlášť kritické?
Poskytovatelé cloudu zpřístupňují instancím interní služby metadat, přes které lze částečně získat přístupové údaje. Pokud může aplikace posílat požadavky tam, může se útočník přes ni k těmto datům dostat. Poskytovatelé nabízejí zpevněné varianty těchto služeb, které by měly být aktivní.
Co přináší firewall pro odchozí provoz?
Omezuje, jaké cíle může server vůbec dosáhnout. I když kontrola v kódu selže, zůstanou interní systémy nedostupné. Navíc po průniku ztěžuje stahování škodlivého kódu a odliv dat.