Röviden: Az SSRF-et PHP-ban úgy előzi meg, hogy az alkalmazása csak olyan címeket hív le, amelyek egy rögzített engedélyezési listán szerepelnek, kizárólag HTTPS-en, átirányítások követése nélkül. A feloldott IP-címet a privát és fenntartott tartományokkal szemben ellenőrzi, majd a kapcsolathoz rögzíti. A kimenő forgalom tűzfala gondoskodik arról, hogy a belső rendszerek a kód hibája esetén is elérhetetlenek maradjanak.
Amikor a saját szerver kérdez idegenek helyett
Linkelőnézetek egy chatben, egy termékkép importálása URL-lel, webhookok partnerrendszerekhez, egy hírolvasó a CMS-ben: sok PHP-alkalmazás hív le olyan címeket, amelyeket egy felhasználó adott meg. Ez hasznos funkció, és egyben egy ajtó befelé.
A Server-Side Request Forgery, röviden SSRF esetén a támadó rávesz egy alkalmazást, hogy a választása szerinti célra küldjön kérést. A csattanó: a kérés magától a szervertől érkezik. Így olyan rendszereket ér el, amelyek kívülről le vannak zárva, például adatbázisokat, a belső hálózat adminisztrációs felületeit, csak a localhoston figyelő szolgáltatásokat vagy a felhőszolgáltatók metaadat-szolgáltatásait.
Az OWASP Top 10 2021-es kiadásában az SSRF külön kategória volt. A PHP-projektekben a rés gyakran ott bukkan fel, ahol valaki gyorsan összeütött egy „kép hozzáadása URL-lel” funkciót.
SSRF PHP-ban: mely függvények hívnak le címeket
Nem csak a cURL hív le címeket. A következő helyekre figyeljen:
| Függvény vagy terület | Miért releváns |
|---|---|
cURL (curl_exec()) |
a leggyakoribb út, sok protokoll lehetséges |
file_get_contents(), fopen(), copy() URL-lel |
URL-wrappereket használnak, ha az allow_url_fopen aktív |
getimagesize() és képkönyvtárak |
távoli fájlokat tölthetnek be |
| XML-feldolgozás, SOAP-kliensek | adott esetben külső erőforrásokat töltenek be |
| HTTP-könyvtárak, például a Guzzle | cURL-ra vagy stream-ekre épülnek, ugyanazok a szabályok |
Ha az alkalmazásának nem kell távoli fájlokat fájlfüggvényekkel megnyitnia, kapcsolja ki az allow_url_fopen beállítást a szerver oldalán. Egy rokon témát, a kérésből származó útvonalakkal történő fájlbeágyazást, a Local és Remote File Inclusion megelőzése útmutató tárgyalja.
A védelem öt lépésben
Az OWASP olyan intézkedések kombinációját ajánlja, amelyek egyenként megkerülhetők lennének, együtt azonban elbírnak:
- Engedélyezési lista szabad URL helyett: a legjobb, ha a felhasználó csak ismert célok közül választ, vagy csak egy azonosítót ad meg, és az alkalmazás maga építi fel a címet.
- Csak HTTPS engedélyezése: a cURL-ban kizárni az olyan protokollokat, mint a
file, agophervagy azftp, az átirányításoknál is. - Átirányításokat nem követni: egy engedélyezett cím különben belső célra irányíthat át.
- A feloldott cím ellenőrzése és rögzítése: feloldani a hosztnevet, az IP-t ellenőrizni a privát, loopback és link-local tartományokkal szemben, és pontosan ezt az IP-t használni a kapcsolathoz. Különben egy második DNS-feloldás más célt adhat.
- A hálózat korlátozása: a webszerver kifelé csak azokat a célokat érheti el, amelyekre szüksége van.
Biztonságos minta cURL-lel
A következő példa az 1–4. lépést valósítja meg IPv4-re. A hosztnevek és a folyamat példák; IPv6-ra ugyanazok az ellenőrzések érvényesek.
<?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éhány részlet fontos. A gethostbyname() hiba esetén a hosztnevet adja vissza; az ezt követő IP-ellenőrzés ezt kivédi. A FILTER_FLAG_NO_PRIV_RANGE és a FILTER_FLAG_NO_RES_RANGE flag a PHP kézikönyve szerint többek között a 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 és 169.254.0.0/16 tartományokat zárja ki. A PHP 8.2 óta létezik emellett a FILTER_FLAG_GLOBAL_RANGE, amely csak globálisan érvényes címeket enged. A CURLOPT_RESOLVE gondoskodik arról, hogy a cURL pontosan az ellenőrzött címet használja, miközben a hosztnév és a tanúsítvány-ellenőrzés megmarad.
Két további pont korlátozza, mit tanul a támadó egy SSRF-résből. A lehívott cél válaszait és hibaüzeneteit ne adja vissza szűretlenül a felhasználónak: már a „kapcsolat elutasítva” és az „időtúllépés” közötti különbségtétel is elárulja, mely belső szolgáltatások léteznek. És korlátozza a válasz futási idejét és méretét, hogy a funkció ne legyen alkalmas a saját szerver túlterhelésére. Azokat a webhookokat, amelyek az ügyfelek által maguk megadott címekre mennek, a legjobb egy sorból küldeni, nem a folyamatban lévő kérés közben.
Ha az alkalmazásának valóban tetszőleges címeket kell lehívnia, például linkelőnézetekhez, szervezze ki ezt a funkciót egy különálló szolgáltatásba, amelynek nincs hozzáférése a belső hálózathoz. Engedélyezési lista ekkor nem lehetséges, a hálózati szétválasztás annál fontosabb.
A hálózati szint mint biztonsági háló
A kódellenőrzéseknek lehetnek hézagai, például ha két könyvtár másképp bont fel egy URL-t. Az OWASP azt tanácsolja, hogy az ilyen következetlenségeket elutasításként kezeljék, és ezenkívül hálózati szinten is korlátozzanak. Egy webszervernek kifelé többnyire csak kevés célra van szüksége: csomagforrásokra, fizetési és szállítmányozási szolgáltatókra, levelezési relére, bizonyos interfészekre.
A kimenő forgalom tűzfala kétszeresen segít. Megállítja az SSRF-kéréseket a belső hálózatba, és egy betörés után, például egy nem patchelt PHP-verzión keresztül, megnehezíti további eszközök utántöltését. Hogyan építse fel a szabályokat, azt a Tűzfal webszerverekhez: csak a szükséges portokat nyitni írja le. Hogy a szerver ténylegesen milyen kapcsolatokat épít fel, azt a Egy szerver kimenő kapcsolatainak ellenőrzése mutatja.
Felhős környezetekben a metaadat-szolgáltatás keményített változata is hozzátartozik. Az OWASP az AWS-nél az IMDSv2-re való áttérést és az IMDSv1 kikapcsolását említi.
Az SSRF felderítése a meglévő kódban
Gyors belépő a keresés azokra a helyekre, ahol a lehívó függvények a kérésből kapnak értékeket:
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"
A találatok ellenőrzési feladatok. Különösen alaposan nézze meg azokat a funkciókat, amelyeket az adminok használnak: importok, webhook-beállítások, bővítmények frissítés-ellenőrzései.
Könnyen elkerülik a figyelmet a PDF-generátorok, amelyek beágyazott képekkel vagy stíluslapokkal rendelkező HTML-t alakítanak dokumentumokká. Ha ennek a HTML-nek egy része felhasználói bemenetből származik, például szabadon választható logójú számláknál, a generátor adott esetben olyan címeket hív le, amelyeket a felhasználó határoz meg. Ott ellenőrizze a távoli erőforrások betöltésének beállításait, és kapcsolja ki, ha nincs rá szükség.
Hogyan segít konkrétan a PHP SOS?
- Megkeressük az összes helyet, ahol az alkalmazása címeket hív le, a bővítményekben és a könyvtárakban is.
- Központi lehívó függvényt építünk engedélyezési listával, protokollkorlátozással és IP-ellenőrzéssel.
- Szabályokat állapítunk meg a kimenő forgalomra, és védjük a metaadat-szolgáltatásokat felhős környezetekben.
- Kívülről ellenőrizzük a megvalósítást, és dokumentáljuk az engedélyezett célokat.
Az alkalmazás független ellenőrzését a biztonsági audit és penetrációs teszt írja le.
Gyakori kérdések
Mely PHP-függvényekkel lehet visszaélni SSRF-hez?
Mindegyikkel, amely felhasználói bemenetből származó címet hív le: cURL, file_get_contents és fopen URL-wrapperekkel, távoli fájlokat betöltő kép- és XML-függvények, valamint a webhookokhoz, feedekhez vagy linkelőnézetekhez használt könyvtárak.
Elég a belső IP-címek letiltása?
Önmagában nem. Az OWASP a tiltólistákat megkerülhetőnek nevezi, és engedélyezési listákat ajánl. Ezenkívül az alkalmazásnak pontosan azt a címet kell használnia, amelyet ellenőrzött, és nem követhet átirányításokat.
Miért különösen kritikus az SSRF a felhőben?
A felhőszolgáltatók a példányok számára belső metaadat-szolgáltatásokat biztosítanak, amelyeken keresztül részben hozzáférési adatok is lehívhatók. Ha egy alkalmazás kéréseket küldhet oda, a támadó rajta keresztül elérheti ezeket az adatokat. A szolgáltatók ezeknek a szolgáltatásoknak keményített változatait kínálják, amelyeket aktiválni kell.
Mit ad egy tűzfal a kimenő forgalomhoz?
Korlátozza, hogy a szerver egyáltalán mely célokat érheti el. Még ha egy ellenőrzés a kódban cserbenhagy is, a belső rendszerek elérhetetlenek maradnak. Ezenkívül egy betörés után megnehezíti a kártékony kód utántöltését és az adatok kiáramlását.