Krótko: SSRF w PHP zapobiegasz, sprawiając, że Państwa aplikacja pobiera tylko adresy znajdujące się na stałej liście dozwolonych, wyłącznie przez HTTPS, bez podążania za przekierowaniami. Rozwiązany adres IP jest sprawdzany względem zakresów prywatnych i zarezerwowanych, a następnie utrwalany dla połączenia. Firewall dla ruchu wychodzącego sprawia, że systemy wewnętrzne pozostają nieosiągalne nawet przy błędzie w kodzie.
Gdy własny serwer wysyła żądania za obcych
Podglądy linków w czacie, import zdjęcia produktu przez adres URL, webhooki do systemów partnerów, czytnik kanałów w CMS: wiele aplikacji PHP pobiera adresy podane przez użytkownika. To użyteczna funkcja i jednocześnie drzwi do wnętrza.
W przypadku Server-Side Request Forgery, w skrócie SSRF, atakujący sprawia, że aplikacja wysyła żądanie do wybranego przez niego celu. Sedno w tym, że żądanie pochodzi od samego serwera. Dociera więc do systemów zablokowanych z zewnątrz, na przykład baz danych, interfejsów administracyjnych w sieci wewnętrznej, usług nasłuchujących wyłącznie na localhost albo usług metadanych dostawców chmury.
W OWASP Top 10 z 2021 roku SSRF była osobną kategorią. W projektach PHP luka ta często pojawia się tam, gdzie ktoś szybko zbudował funkcję „dodaj obraz przez adres URL”.
SSRF w PHP: które funkcje pobierają adresy
Adresy pobiera nie tylko cURL. Należy mieć na oku następujące miejsca:
| Funkcja lub obszar | Dlaczego istotne |
|---|---|
cURL (curl_exec()) |
najczęstsza droga, możliwych wiele protokołów |
file_get_contents(), fopen(), copy() z adresem URL |
używają wrapperów URL, gdy allow_url_fopen jest aktywne |
getimagesize() i biblioteki obrazów |
mogą ładować zdalne pliki |
| Przetwarzanie XML, klienci SOAP | w pewnych okolicznościach ładują zewnętrzne zasoby |
| Biblioteki HTTP, takie jak Guzzle | opierają się na cURL lub strumieniach, te same zasady |
Jeśli Państwa aplikacja nie musi otwierać zdalnych plików za pomocą funkcji plikowych, wyłącz allow_url_fopen po stronie serwera. Pokrewny temat, czyli dołączanie plików przez ścieżki z żądania, omawia poradnik Zapobieganie Local i Remote File Inclusion.
Obrona w pięciu krokach
OWASP zaleca połączenie środków, z których każdy z osobna dałoby się obejść, ale razem są skuteczne:
- Lista dozwolonych zamiast dowolnego URL: Najlepiej, aby użytkownik wybierał tylko spośród znanych celów albo podawał jedynie identyfikator, a aplikacja sama buduje adres.
- Dopuszczać tylko HTTPS: Wykluczyć w cURL inne protokoły, takie jak
file,gopherlubftp, także dla przekierowań. - Nie podążać za przekierowaniami: Dozwolony adres może inaczej przekierować na cel wewnętrzny.
- Sprawdzić i utrwalić rozwiązany adres: Rozwiązać nazwę hosta, sprawdzić IP względem zakresów prywatnych, loopback i link-local oraz użyć dokładnie tego IP dla połączenia. W przeciwnym razie drugie rozwiązanie DNS może zwrócić inny cel.
- Ograniczyć sieć: Serwer WWW może na zewnątrz osiągać tylko te cele, których potrzebuje.
Bezpieczny wzorzec z cURL
Poniższy przykład realizuje kroki od 1 do 4 dla IPv4. Nazwy hostów i przebieg to przykłady; dla IPv6 obowiązują te same kontrole.
<?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;
}
Kilka szczegółów jest ważnych. gethostbyname() zwraca w razie błędu nazwę hosta; późniejsza kontrola IP to wychwytuje. Flagi FILTER_FLAG_NO_PRIV_RANGE i FILTER_FLAG_NO_RES_RANGE wykluczają według podręcznika PHP między innymi zakresy 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8 i 169.254.0.0/16. Od PHP 8.2 istnieje dodatkowo FILTER_FLAG_GLOBAL_RANGE, który dopuszcza tylko adresy globalnie ważne. CURLOPT_RESOLVE sprawia, że cURL używa dokładnie sprawdzonego adresu, a nazwa hosta i weryfikacja certyfikatu zostają zachowane.
Dwa dalsze punkty ograniczają to, czego atakujący dowie się z luki SSRF. Nie zwracaj użytkownikowi bez filtrowania odpowiedzi i komunikatów o błędach z pobranego celu: już samo rozróżnienie między „połączenie odrzucone” a „przekroczenie czasu” zdradza, jakie usługi wewnętrzne istnieją. Ogranicz też czas trwania i rozmiar odpowiedzi, aby funkcja nie nadawała się do przeciążenia własnego serwera. Webhooki na adresy, które klienci wpisują samodzielnie, najlepiej wysyłać z kolejki, a nie w trakcie trwającego żądania.
Jeśli Państwa aplikacja naprawdę musi pobierać dowolne adresy, na przykład dla podglądów linków, wydziel tę funkcję do oddzielnej usługi bez dostępu do sieci wewnętrznej. Lista dozwolonych nie jest wtedy możliwa, tym ważniejsze jest oddzielenie sieci.
Warstwa sieciowa jako siatka bezpieczeństwa
Kontrole w kodzie mogą mieć luki, na przykład gdy dwie biblioteki rozkładają adres URL w różny sposób. OWASP radzi traktować takie niespójności jako odrzucenie i dodatkowo ograniczać na poziomie sieci. Serwer WWW potrzebuje na zewnątrz zwykle tylko kilku celów: źródeł pakietów, dostawców płatności i wysyłki, przekaźnika poczty, określonych interfejsów.
Firewall dla ruchu wychodzącego pomaga podwójnie. Zatrzymuje żądania SSRF do sieci wewnętrznej i utrudnia po włamaniu, na przykład przez niezałataną wersję PHP, doładowywanie kolejnych narzędzi. Jak zbudować reguły, opisuje artykuł Firewall dla serwera WWW: otwierać tylko niezbędne porty. Jakie połączenia serwer faktycznie nawiązuje, pokazuje artykuł Kontrola wychodzących połączeń serwera.
W środowiskach chmurowych należy do tego utwardzony wariant usługi metadanych. OWASP wskazuje dla AWS przejście na IMDSv2 i wyłączenie IMDSv1.
Wykrywanie SSRF w istniejącym kodzie
Szybkim początkiem jest wyszukanie miejsc, w których funkcje pobierające otrzymują wartości z żądania:
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"
Trafienia to zadania kontrolne. Szczególnie dokładnie przejrzyj funkcje używane przez administratorów: importy, ustawienia webhooków, sprawdzanie aktualizacji wtyczek.
Łatwo przeoczyć generatory PDF, które zamieniają HTML z osadzonymi obrazami lub arkuszami stylów na dokumenty. Jeśli część tego HTML pochodzi z danych użytkownika, na przykład przy fakturach z dowolnie wybieranym logo, generator może pobierać adresy określone przez użytkownika. Sprawdź tam ustawienia ładowania zdalnych zasobów i wyłącz je, jeśli nie są potrzebne.
Jak konkretnie pomaga PHP SOS?
- Znajdujemy wszystkie miejsca, w których Państwa aplikacja pobiera adresy, także we wtyczkach i bibliotekach.
- Budujemy centralną funkcję pobierania z listą dozwolonych, ograniczeniem protokołów i kontrolą IP.
- Ustalamy reguły dla ruchu wychodzącego i chronimy usługi metadanych w środowiskach chmurowych.
- Sprawdzamy wdrożenie z zewnątrz i dokumentujemy dozwolone cele.
Niezależną kontrolę Państwa aplikacji opisuje usługa audyt bezpieczeństwa i test penetracyjny.
Częste pytania
Które funkcje PHP mogą zostać nadużyte do SSRF?
Wszystkie, które pobierają adres z danych wprowadzonych przez użytkownika: cURL, file_get_contents i fopen z wrapperami URL, funkcje obrazów i XML ładujące zdalne pliki oraz biblioteki do webhooków, kanałów lub podglądów linków.
Czy wystarczy zablokować wewnętrzne adresy IP?
Samo to nie. OWASP określa listy zablokowanych jako podatne na obejście i zaleca listy dozwolonych. Dodatkowo aplikacja musi używać dokładnie tego adresu, który sprawdziła, i nie może podążać za przekierowaniami.
Dlaczego SSRF jest w chmurze szczególnie krytyczne?
Dostawcy chmury udostępniają instancjom wewnętrzne usługi metadanych, za pośrednictwem których częściowo można pobrać dane dostępowe. Jeśli aplikacja może wysyłać tam żądania, atakujący może przez nią dotrzeć do tych danych. Dostawcy oferują utwardzone warianty tych usług, które powinny być włączone.
Co daje firewall dla ruchu wychodzącego?
Ogranicza, jakie cele serwer w ogóle może osiągnąć. Nawet jeśli kontrola w kodzie zawiedzie, systemy wewnętrzne pozostają wtedy nieosiągalne. Ponadto po włamaniu utrudnia doładowywanie złośliwego kodu i wyprowadzanie danych.