Nette Http 3.4.0 přináší ochranu proti SSRF útokům
Avatar uživatele. Obrázek vytvořený MCP serverem. OG náhled v komentáři. Webhook callback. OAuth redirect. RSS feed import. Kdykoli vaše aplikace stahuje URL, kterou poslal uživatel (nebo AI asistent), je terčem Server-Side Request Forgery (SSRF), jedné z OWASP Top 10 zranitelností.
Nette/Http 3.4.0 přináší dvě nové třídy, UrlValidator a IPAddress, které tenhle problém řeší jedním řádkem.
Jak fungují útoky typu SSRF v cloudových prostředích
Klasický SSRF útok vypadá nenápadně:
https://169.254.169.254/latest/meta-data/iam/security-credentials/
Adresa 169.254.169.254 je cloud metadata endpoint, dostupný jen
zevnitř AWS, Azure nebo GCP instance. Útočník se na ni z venku nedostane,
ale váš server ano. Pokud aplikace tu URL slepě stáhne (třeba jako
„obrázek profilu uživatele“), pošle si útočník zpátky aktivní
IAM klíče.
Další oblíbené cíle: https://192.168.1.1/admin/ (interní
router), http://localhost:6379/ (Redis bez auth),
https://10.0.5.7/internal-api/ (cokoli ve vaší LAN).
Naivní kontrola „je to https://?“ nestačí. Musíte
vědět, na jaké IP adresy URL skutečně míří, ne jak vypadá. To
znamená přeložit název na IP adresy přes DNS, zkontrolovat je proti seznamu
zakázaných rozsahů a ošetřit záludnosti jako IPv4-mapped IPv6 nebo DNS
rebinding… rychle se z toho stane padesát řádků kódu, který každý
projekt řeší trochu jinak, obvykle špatně.
S nástupem MCP serverů a AI integrací útočná plocha výrazně narostla. AI dostane URL od uživatele a předá ji nástroji ke stažení. Server URL stáhne. AI nemá intuici pro „tohle je interní endpoint“, takže bez téhle ochrany je z AI agenta ideální cíl útoku.
Jak ověřit platnost URL adres v PHP
use Nette\Http\UrlValidator;
if (!(new UrlValidator)->allows($userUrl)) {
return; // unsafe URL
}
// teď je bezpečné fetchovat
Default je přísný: jen https, jen port 443, hostname musí
resolvnout výhradně na veřejné IP adresy. Blokuje loopback,
privátní rozsahy, link-local včetně cloud metadata, multicast a
IANA-reserved.
Když potřebujete jiná pravidla, nastavíte si je v konstruktoru:
// Interní monitoring potřebuje LAN (http a libovolný port)
new UrlValidator(schemes: ['http', 'https'], ports: null, allowPrivateIps: true);
// OAuth jen na známé partnery
new UrlValidator(
hostAllowlist: ['*.partner1.com', '*.partner2.com'],
);
Wildcard *.example.com matchuje subdomény libovolné hloubky.
Apex doménu přidejte zvlášť.
Prevence útoků typu DNS rebinding v PHP
Mezi allows() a samotným fetchem je krátké okno, kdy
útočník kontrolující DNS hosta přepne A záznam z veřejné na interní
IP. Validace projde, ale request půjde jinam (DNS rebinding útok).
Pro plnou obranu existuje getResolvedIPs(). Funguje stejně jako
allows(): pro nebezpečné URL vrátí [], pro
bezpečné pole zvalidovaných adres. Ty pak předáte cURL přes
CURLOPT_RESOLVE:
$ips = (new UrlValidator)->getResolvedIPs($url);
if (!$ips) {
return; // unsafe
}
$ch = curl_init($url);
$host = parse_url($url, PHP_URL_HOST);
curl_setopt($ch, CURLOPT_RESOLVE, ["$host:443:" . implode(',', $ips)]);
cURL už nedělá nový DNS dotaz, spojení jde přesně na ty IPs, které prošly validací. Časové okno pro DNS rebinding se tím zavírá.
Ověřování adres IPv4 a IPv6 v PHP
Současně přichází IPAddress, neměnný objekt pro práci
s IPv4 a IPv6 adresami:
use Nette\Http\IPAddress;
$ip = new IPAddress('169.254.169.254');
$ip->isLinkLocal(); // true (cloud metadata)
$ip->isInRange('192.168.0.0/16'); // false
Plná podpora IPv4-mapped IPv6: ::ffff:127.0.0.1 se vyhodnotí
jako loopback. Naivní kontrola tenhle zápis jako loopback nerozpozná, takže
jím útočník její ochranu snadno obejde.
Použitelné nezávisle na UrlValidator všude, kde pracujete
s IP: rate limiting, audit logy, trusted proxy validace. Statická
IPAddress::isValid() jako rychlý checker,
IPAddress::tryFrom() jako factory vracející ?self
bez výjimky.
Co tahle dvojice neřeší: HTTP redirecty (váš klient by je měl mít vypnuté, nebo revalidovat každý hop) a útoky mířící na samotný obsah stažené odpovědi. Validace adresy není validace obsahu.
Co dalšího přináší 3.4
SSRF ochrana je vlajková loď, ale verze 3.4 toho přináší víc:
- Konec CSRF tokenů: nová metoda
Request::isFrom()pozná původ requestu z hlavičekSec-Fetch-*, a je to tak velké téma, že jsme mu věnovali samostatný článek. - Modernější cookies:
setCookie()nově posílá atributMax-Age, podporuje partitioned cookies (CHIPS) parametrempartitioned: truea proSameSite=Nonenebo partitioned cookie automaticky vynutíSecure. - Enum
SameSite: místo stringových konstantIResponse::SameSiteLaxa spol. nově píšeteSameSite::Lax. Přijímá hosetCookie()iSession::setCookieParameters(), staré konstanty jsou deprecated. - Expirace všude stejně:
setCookie(),Session::setExpiration()i expirace session sekcí vykládají hodnotu shodně: číslo je relativní počet sekund, text je interval ('20 minutes') nebo datum;setCookie()navíc přijímáDateTimeInterface. Předávání absolutního UNIX timestampu je deprecated. Session cookie představuje hodnotanull, která nahradí dřívější0. detectLanguage()rozumí wildcardu: hlavičkaAccept-Languagesmí obsahovat*(posílají ho hlavně API klienti a boti). Dříve ho metoda ignorovala a vracelanull, nově pro něj vrátí jeden z jazyků, které aplikace nabízí.- Knihovna nově vyžaduje PHP 8.3+, deprecated metoda
Request::getRemoteHost()vracínulla dávno deprecated třídaUserStoragebyla odstraněna.
Vše výše popsané přichází s verzí nette/http 3.4.0.
Další čtení
- Čtvrt století s CSRF. Teď ho konečně řeší prohlížeč
- Nette Http 3.2: změna přístupu ke credentials
- Nette Tester: HTTP testování ještě nikdy nebylo tak jednoduché
- Latte 3.1: Když šablonovací systém skutečně rozumí HTML
- Nette PHP Generator přináší sílu PHP 8.4
- Nette Utils 4.0: UTF-8, Finder a pojmenované argumenty
Chcete-li odeslat komentář, přihlaste se