Przejdź do głównej zawartości

Bezpieczeństwo i operacje

Ten most przesyła kod Hypertext Markup Language (HTML) przez granicę sieciową do silnika przeglądarki. Na tej stronie udokumentowano mechanizmy kontroli chroniące tę granicę, traktując kod źródłowy jako źródło prawdy. Gdy mechanizm kontroli powołuje się na standard, cytowany jest standard zadeklarowany w docblocku kodu. Strona powtarza twierdzenie zawarte w kodzie; nie odtwarza tekstu normatywnego.

Docblocki pakietu wymieniają zagrożenia, przed którymi pakiet się broni:

  • XSS-do-PDF — cross-site scripting (XSS) za pośrednictwem złośliwego znacznika, który wykonuje się podczas renderowania formatu Portable Document Format (PDF).
  • SSRF — server-side request forgery (SSRF) spowodowane znacznikiem lub docelowym adresem Uniform Resource Locator (URL), prowadzące do wysłania żądania na adres wewnętrzny.
  • Wyczerpanie zasobów — nadmiernie duże dane wejściowe lub bomba dekompresyjna.
  • DNS rebinding — atak DNS rebinding (Domain Name System), w którym nazwa hosta przechodzi walidację, a następnie w chwili połączenia zostaje rozwiązana na adres prywatny.
  • Przechwytywanie TLS na ścieżce — przechwytywanie Transport Layer Security (TLS) na ścieżce do Workera z użyciem podstawionego certyfikatu.

Dla każdego zagrożenia poniżej wskazano konkretny, testowalny mechanizm kontroli.

Kontrola danych wejściowych (zanim żądanie opuści PHP)

Dział zatytułowany „Kontrola danych wejściowych (zanim żądanie opuści PHP)”

CloudflareSecurityPolicy::validate() jest wykonywane przed zbudowaniem jakiegokolwiek żądania:

Mechanizm kontroliZachowanieŹródło limitu
Limit rozmiaruOdrzuca kod HTML większy niż maxHtmlSizeCloudflareRendererConfig, domyślnie 5000000 bajtów
Zabezpieczenie przed bombą dekompresyjną Base64Szacuje rozmiar po zdekodowaniu każdego identyfikatora URI data:…;base64,…; odrzuca wartości równe pułapowi lub większeMAX_DATA_URI_BYTES = 13631488
Zakaz meta-refreshOdrzuca każdy <meta http-equiv="refresh">, bez rozróżniania wielkości literwyrażenie regularne w CloudflareSecurityPolicy

Naruszenie powoduje zgłoszenie RuntimeException z komunikatem zawierającym naruszającą wartość oraz limit. Zakaz meta-refresh wprowadzono, ponieważ dyrektywa odświeżania może rozpocząć nawigację wewnątrz strony renderowanej przez Workera — to wektor SSRF osadzony w treści, a nie w adresie URL.

Polityka bezpieczeństwa HTML z nextpdf/core (HtmlSecurityPolicyInterface, domyślnie DefaultHtmlSecurityPolicy) działa w warstwie parsowania i uzupełnia powyższe kontrole w warstwie transportu. Uzyskaj ją za pomocą getHtmlSecurityPolicy(). Wstrzyknij własną za pośrednictwem konstruktora.

CloudflareSecurityPolicy::validateWorkerUrl():

  1. Odrzuca adres URL, którego nie da się sparsować lub który nie zawiera części scheme/host (Invalid Worker URL).
  2. Odrzuca każdy schemat inny niż HTTPS (Worker URL must use HTTPS).
  3. W przypadku hosta będącego literałem IP odrzuca zakresy prywatne lub zarezerwowane za pomocą flag PHP FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE. W praktyce oznacza to odrzucanie przestrzeni prywatnej RFC 1918, adresów pętli zwrotnej oraz adresów link-local RFC 3927. Testy wprost obejmują odrzucanie adresów 192.168.x, 127.0.0.1 oraz 169.254.x. O przynależności do zakresu decyduje rozszerzenie filter w PHP; pakiet nie wiąże tej decyzji z konkretną klauzulą. RFC 1918 oraz RFC 3927 wymieniono tu opisowo jako powszechnie znane definicje tych zakresów.
  4. W przypadku nazwy hosta rozwiązuje wszystkie rekordy A i AAAA za pomocą dns_get_record() (a nie gethostbyname(), które zwraca tylko pierwszą odpowiedź) i odrzuca hosta, jeśli którykolwiek z rozwiązanych adresów jest prywatny lub zarezerwowany.

Rozwiązywanie wszystkich rekordów jest celowe. Docblock klasy opisuje je jako ochronę przed hostem, który zwraca kilka rekordów: wyszukiwanie pojedynczego rekordu mogłoby wybrać adres publiczny, podczas gdy późniejsze połączenie wybrałoby prywatny. Jest to zgodne z OWASP SSRF Prevention Cheat Sheet: dla domeny należy rozwiązać zarówno rekordy A, jak i AAAA oraz zastosować kontrolę adresów niepublicznych do całego zbioru wyników.

validateWorkerUrl() zwraca zweryfikowany zbiór adresów IP. Bezpośrednio przed wysłaniem żądania renderer wywołuje assertPinsStillValid(). To wywołanie ponownie rozwiązuje hosta i odrzuca nowo zaobserwowany adres IP (Worker URL DNS answer changed since validation — possible DNS rebinding attack). Zamyka to okno między czasem sprawdzenia a czasem użycia, czyli między walidacją a połączeniem.

Jeśli dostępny jest zweryfikowany zbiór adresów IP lub zbiór przypięć Subject Public Key Info (SPKI) oraz dostarczono fabrykę ResponseFactory zgodną z PHP Standards Recommendation 17 (PSR-17), renderer używa Transport\PinnedCurlTransport zamiast wstrzykniętego klienta PHP Standards Recommendation 18 (PSR-18). Transport egzekwuje te mechanizmy kontroli na poziomie uchwytu cURL:

  • Przypięcie DNSCURLOPT_RESOLVE wiąże parę host:port ze zweryfikowanym zbiorem adresów IP, więc libcurl nie wykonuje własnego wyszukiwania w momencie nawiązywania połączenia. To powiązanie sprawia, że sprawdzenie DNS po stronie aplikacji dotyczy faktycznego połączenia; bez niego libcurl mógłby rozwiązać inny adres.
  • Przypinanie klucza publicznego TLSCURLOPT_PINNEDPUBLICKEY jest ustawiane ze scalonego zbioru przypięć. Jest to zgodne z RFC 7469 §2.6: przypięte połączenie jest akceptowane, gdy zbiór odcisków SPKI przedstawionych przez serwer ma część wspólną ze skonfigurowanym zbiorem przypięć, a niepowodzenie walidacji przypięcia jest nieodwracalne. Ciągi przypięć są normalizowane z postaci sha256/<base64> do postaci wymaganej przez cURL sha256//<base64>; nieprawidłowe przypięcie powoduje zgłoszenie InvalidSpkiPinException.
  • Weryfikacja TLS włączonaCURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2.
  • Brak automatycznych przekierowańCURLOPT_FOLLOWLOCATION => false, CURLOPT_MAXREDIRS => 0. Odpowiedź 3xx jest przekazywana do warstwy polityki, zamiast być śledzona przez libcurl do niezweryfikowanego hosta. Docblock klasy wskazuje, że jest to celowe, więc przekierowania są ponownie walidowane, a nie śledzone po cichu.
  • Twardy limit czasuCURLOPT_TIMEOUT jest ustawiany z renderTimeout (domyślnie 30 sekund).

Błąd cURL albo treść, która nie jest łańcuchem znaków, powoduje zgłoszenie CloudflareRenderException z numerem i komunikatem błędu cURL.

Konfiguracja zawiera pinnedPublicKeys oraz osobne backupPublicKeys. RFC 7469 §2.5 opisuje przypięcie zapasowe jako odcisk dla zapasowej, jeszcze niewdrożonej pary kluczy przechowywanej offline i traktuje je jako podstawową ścieżkę odzyskiwania przy niezamierzonym niepowodzeniu walidacji przypięcia. Zachowaj co najmniej jedno przypięcie zapasowe, aby rotacja certyfikatu nie unieruchomiła punktu końcowego. Osobne pole pozwala niezależnie zweryfikować rotację. Operacyjnie:

  • Przypnij SPKI certyfikatu liścia lub certyfikatu pośredniego, którego rotację kontrolujesz.
  • Przed rotacją zawsze skonfiguruj przypięcie zapasowe dla następnego certyfikatu.
  • Pusty zbiór przypięć wyłącza przypinanie; stosuj to tylko ze stabilnym, znanym łańcuchem certyfikatów. Przypinanie jest opcjonalne i włączane konfiguracją.
  • Żądanie do Workera zawiera Authorization: Bearer <apiToken>. apiToken jest oznaczony jako #[SensitiveParameter], więc ślady stosu go nie ujawniają. Sonda osiągalności wysyła ten sam nagłówek Bearer w żądaniu Hypertext Transfer Protocol (HTTP) HEAD.
  • Klucze dostępu Cloudflare R2 (accessKeyId, secretAccessKey) są oznaczone jako #[SensitiveParameter] i służą wyłącznie do wyprowadzenia klucza podpisującego Amazon Web Services (AWS) Signature V4.
  • ApiKeyValidator porównuje klucze za pomocą hash_equals() w sposób bezpieczny pod względem czasowym i obsługuje przechowywanie kluczy w postaci skrótu Secure Hash Algorithm 256 (SHA-256) za pomocą validateHashed().
  • Obiekty konfiguracji są final readonly — sekret ustawiony raz nie może zostać zmieniony.
  • Pobieraj sekrety ze zmiennych środowiskowych lub z menedżera sekretów. Nigdy nie zatwierdzaj ich w repozytorium. Pakiet stosuje szerszy standard bezpieczeństwa NextPDF: PHPStan Level 10, declare(strict_types=1) w każdym pliku, brak eval()/exec(), GitHub Actions przypięte do SHA.
  • Nie podaje żadnych limitów platformy Cloudflare (czasu procesora Workera, pamięci, limitu treści żądania ani liczby podżądań). Jedyne limity rozmiaru i czasu, jakie wskazuje ta dokumentacja, to te, które pakiet egzekwuje samodzielnie, wymienione powyżej oraz w /integrations/cloudflare/configuration/. W kwestii limitów platformy zapoznaj się z oficjalną dokumentacją Cloudflare oraz z własną implementacją Workera.
  • Nie podpisuje plików PDF i nie formułuje żadnego twierdzenia o zgodności podpisu. Gdy podpisy są wymagane, renderuj tutaj, a następnie podpisz za pomocą silnika. NextPDF Pro zapewnia wyłącznie podpisywanie PDF Advanced Electronic Signatures (PAdES) B-B; profile długoterminowej walidacji są funkcją Enterprise i pozostają poza zakresem tego mostu.
  • Nie certyfikuje ani nie gwarantuje potoku i nie czyni go „odpornym na manipulacje”. Implementuje wyłącznie konkretne mechanizmy kontroli, opisane na tej stronie i możliwe do zweryfikowania w kodzie.
ObjawNajpierw sprawdź
Worker URL must use HTTPSSprawdź schemat skonfigurowanego workerUrl.
private or reserved IPRekordy DNS nazwy hosta Workera; poszukaj rekordu, który rozwiązuje się do przestrzeni RFC 1918 / pętli zwrotnej / RFC 3927.
DNS answer changed since validationNiestabilność DNS lub próba rebindingu; ponownie rozwiąż nazwę i przejrzyj pełny zbiór rekordów.
cURL transport errorŚcieżkę sieciową, łańcuch TLS oraz — jeśli ustawiono przypięcia — to, czy SPKI prezentowanego certyfikatu nadal znajduje się w zbiorze przypięć.
Renderowanie zawodzi tuż po rotacji certyfikatuZbiór przypięć bez pasującego przypięcia zapasowego. Dodaj nowe SPKI jako zapasowe przed rotacją.
is not installed / no LocalRendererFactoryInterfaceFallback jest włączony, ale nie podłączono żadnej fabryki albo brakuje nextpdf/artisan.
Niespójne odrzucenia przez limiter szybkości między węzłamiLimiter w pamięci działa na poziomie procesu; umieść przed nim współdzielony magazyn.

Zgłaszaj podatności przez GitHub Security Advisories albo kanał kontaktowy ds. bezpieczeństwa podany w pliku SECURITY.md repozytorium. Nie zgłaszaj problemów bezpieczeństwa jako publicznych zgłoszeń (issues) w serwisie GitHub.

  • /integrations/cloudflare/overview/ — dlaczego pakiet jest zaprojektowany wokół granicy.
  • /integrations/cloudflare/configuration/ — pola dotyczące zbioru przypięć i limitów.
  • /integrations/cloudflare/troubleshooting/ — pełne mapowanie niepowodzeń na wyjątki.