Zum Inhalt springen

Sicherheit und Betrieb

Diese Brücke sendet Ihr HTML über eine Netzwerkgrenze hinweg an eine Browser-Engine. Diese Seite dokumentiert anhand des Quellcodes jede Schutzmaßnahme, die diese Grenze absichert. Wenn eine Schutzmaßnahme einen Standard zitiert, entspricht das Zitat den Angaben im DocBlock des Codes. Diese Seite gibt die Aussage des Codes wieder und rekonstruiert keinen normativen Wortlaut.

Die DocBlocks des Pakets selbst benennen die Bedrohungen, gegen die es schützt:

  • XSS-zu-PDF — feindliches Markup, das während des Renderings ausgeführt wird.
  • SSRF — Markup oder eine Ziel-URL, die eine Anfrage an eine interne Adresse auslöst.
  • Ressourcenerschöpfung — übergroße Eingabe oder eine Dekomprimierungsbombe.
  • DNS-Rebinding — ein Hostname, der die Validierung besteht und sich dann zum Verbindungszeitpunkt zu einer privaten Adresse auflöst.
  • On-Path-TLS-Interception — ein ausgetauschtes Zertifikat auf dem Pfad zum Worker.

Jede dieser Bedrohungen wird weiter unten durch eine spezifische, testbare Schutzmaßnahme adressiert.

Eingabekontrollen (bevor die Anfrage PHP verlässt)

Abschnitt betitelt „Eingabekontrollen (bevor die Anfrage PHP verlässt)“

CloudflareSecurityPolicy::validate() wird ausgeführt, bevor eine Anfrage konstruiert wird:

SchutzmaßnahmeVerhaltenQuelle des Limits
GrößenobergrenzeWeist HTML zurück, das größer ist als maxHtmlSizeCloudflareRendererConfig, Standard 5000000 Bytes
Base64-Dekomprimierungsbomben-SchutzSchätzt die dekodierte Größe jeder data:…;base64,…-URI; weist sie bei Erreichen oder Überschreiten der Obergrenze zurückMAX_DATA_URI_BYTES = 13631488
Meta-Refresh-VerbotWeist jedes <meta http-equiv="refresh"> zurück, ohne Berücksichtigung der Groß-/KleinschreibungRegex in CloudflareSecurityPolicy

Ein Verstoß löst eine RuntimeException mit einer Meldung aus, die den beanstandeten Wert und das Limit nennt. Das Meta-Refresh-Verbot besteht, weil eine Refresh-Direktive innerhalb der vom Worker gerenderten Seite eine Navigation auslösen kann — ein SSRF-Vektor, der im Inhalt steckt, nicht in der URL.

Die HTML-Sicherheitsrichtlinie aus nextpdf/core (HtmlSecurityPolicyInterface, Standard DefaultHtmlSecurityPolicy) arbeitet auf der Parsing-Ebene und ergänzt die oben genannten Prüfungen auf Transportebene. Rufen Sie sie mit getHtmlSecurityPolicy() ab. Übergeben Sie eine benutzerdefinierte Richtlinie über den Konstruktor.

CloudflareSecurityPolicy::validateWorkerUrl():

  1. Weist eine URL zurück, die sich nicht parsen lässt oder bei der scheme/host fehlt (Invalid Worker URL).
  2. Weist jedes Nicht-HTTPS-Schema zurück (Worker URL must use HTTPS).
  3. Bei einem IP-Literal-Host weist sie private oder reservierte Bereiche mithilfe von PHPs FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE. In der Praxis weist dies den privaten Bereich nach RFC 1918, Loopback- und Link-Local-Adressen nach RFC 3927 zurück — die Tests prüfen die Zurückweisungen von 192.168.x, 127.0.0.1 und 169.254.x ausdrücklich. Die Zugehörigkeit zu einem Bereich wird von der Filter-Erweiterung von PHP entschieden, nicht anhand einer Klausel, auf die sich dieses Paket festlegt; RFC 1918 und RFC 3927 werden hier beschreibend als die bekannten Definitionen dieser Bereiche genannt.
  4. Bei einem Hostnamen löst sie alle A- und AAAA-Records über dns_get_record() auf (nicht gethostbyname(), das nur die erste Antwort zurückgibt) und weist zurück, wenn auch nur eine aufgelöste Adresse privat oder reserviert ist.

Dass sämtliche Records aufgelöst werden, ist beabsichtigt und im DocBlock der Klasse als Abwehr gegen einen Host dokumentiert, der mehrere Records zurückgibt: Eine Einzel-Record-Abfrage könnte einen öffentlichen Record auswählen, während die spätere Verbindung einen privaten wählt. Dies entspricht dem OWASP SSRF Prevention Cheat Sheet, das einer Anwendung vorgibt, alle IP-Adressen hinter dem Domainnamen abzurufen (A- und AAAA-Records) und die Prüfung auf nicht öffentliche Adressen auf jede einzelne anzuwenden.

validateWorkerUrl() gibt die geprüfte IP-Menge zurück. Der Renderer ruft dann unmittelbar vor dem Senden der Anfrage assertPinsStillValid() auf. Dieser Aufruf löst den Host erneut auf und weist zurück, wenn seit der Validierung eine neue IP aufgetaucht ist (Worker URL DNS answer changed since validation — possible DNS rebinding attack). Damit wird das Time-of-Check-/Time-of-Use-Fenster zwischen Validierung und Verbindung geschlossen.

Wenn eine geprüfte IP-Menge oder eine SPKI-Pin-Menge vorhanden ist und eine PSR-17-ResponseFactory bereitgestellt wurde, verwendet der Renderer Transport\PinnedCurlTransport anstelle des injizierten PSR-18-Clients. Der Transport erzwingt auf Ebene des cURL-Handles:

  • Gepinntes DNSCURLOPT_RESOLVE bindet host:port an die geprüfte IP-Menge, sodass libcurl zum Verbindungszeitpunkt keine eigene Auflösung durchführt. Dadurch wird sichergestellt, dass die Userland-DNS-Prüfung die Verbindung tatsächlich bindet; ohne diese Bindung könnte libcurl eine andere Adresse auflösen.
  • TLS-Public-Key-PinningCURLOPT_PINNEDPUBLICKEY wird aus der kombinierten Pin-Menge gesetzt. Dies folgt RFC 7469 §2.6: Eine gepinnte Verbindung wird akzeptiert, wenn sich die Menge der vom Server vorgelegten SPKI-Fingerabdrücke mit der konfigurierten Pin-Menge überschneidet, und ein Fehlschlag der Pin-Validierung wird als nicht behebbar behandelt. Pin-Strings werden von sha256/<base64> in die sha256//<base64>-Form von cURL normalisiert; ein fehlerhafter Pin löst eine InvalidSpkiPinException aus.
  • TLS-Verifizierung aktiviertCURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2.
  • Keine automatischen RedirectsCURLOPT_FOLLOWLOCATION => false, CURLOPT_MAXREDIRS => 0. Ein 3xx wird an die Richtlinienebene weitergereicht, statt dass libcurl ihm zu einem ungeprüften Host folgt. Der DocBlock der Klasse hält fest, dass dies eine bewusste Entscheidung ist, damit Redirects erneut validiert und nicht stillschweigend befolgt werden.
  • Hartes TimeoutCURLOPT_TIMEOUT wird aus renderTimeout gesetzt (Standard 30 Sekunden).

Ein cURL-Fehler oder ein Body, der kein String ist, löst eine CloudflareRenderException mit cURL-Fehlernummer und -meldung aus.

Die Konfiguration enthält pinnedPublicKeys und ein separates backupPublicKeys. RFC 7469 §2.5 beschreibt einen Backup-Pin — einen Fingerabdruck für ein sekundäres, noch nicht bereitgestelltes Schlüsselpaar, das offline gehalten wird — als primären Weg, sich von einem versehentlichen Fehlschlag der Pin-Validierung zu erholen. Es entspricht dieser Empfehlung, mindestens einen Backup-Pin vorzuhalten, damit eine Zertifikatsrotation den Endpunkt nicht unbrauchbar macht. Das separate Feld erlaubt es, eine Rotation unabhängig zu validieren. Im Betrieb:

  • Pinnen Sie das SPKI des Leaf-Zertifikats oder eines Zwischenzertifikats, dessen Rotation Sie kontrollieren.
  • Konfigurieren Sie vor der Rotation stets einen Backup-Pin für das nächste Zertifikat.
  • Eine leere Pin-Menge deaktiviert das Pinning; verwenden Sie das nur bei einer stabilen, bekannten Zertifikatskette. Pinning ist per Konfiguration aktivierbar (Opt-in).
  • Die Worker-Anfrage enthält Authorization: Bearer <apiToken>. apiToken ist #[SensitiveParameter], sodass er aus Stack-Traces entfernt wird. Die Erreichbarkeitsprüfung sendet denselben Bearer-Header über ein HTTP-HEAD.
  • R2-Zugriffsschlüssel (accessKeyId, secretAccessKey) sind #[SensitiveParameter] und werden ausschließlich verwendet, um den Signierschlüssel für AWS Signature V4 abzuleiten.
  • ApiKeyValidator vergleicht Schlüssel mit hash_equals() (timing-sicher) und unterstützt die Speicherung gehashter Schlüssel mit SHA-256 über validateHashed().
  • Konfigurationsobjekte sind final readonly — ein einmal gesetztes Geheimnis kann nicht verändert werden.
  • Beziehen Sie Geheimnisse aus Umgebungsvariablen oder einem Secrets-Manager. Committen Sie sie niemals. Das Paket folgt der umfassenderen Sicherheitsbasis von NextPDF: PHPStan Level 10, declare(strict_types=1) in jeder Datei, kein eval()/exec(), GitHub Actions sind auf SHA festgelegt.
  • Es nennt kein Cloudflare-Plattformlimit (Worker-CPU-Zeit, Speicher, Obergrenze des Anfrage-Bodys oder Anzahl der Subrequests). Die einzigen Größen- und Zeitlimits, die diese Dokumentation nennt, sind die, die das Paket selbst erzwingt; sie sind oben und unter /integrations/cloudflare/configuration/ aufgeführt. Konsultieren Sie für Plattformlimits die offizielle Dokumentation von Cloudflare und die Implementierung Ihres eigenen Workers.
  • Es signiert keine PDFs und erhebt keinen Anspruch auf Signatur-Konformität. Wenn Signaturen erforderlich sind, rendern Sie hier und signieren Sie anschließend mit der Engine. NextPDF Pro bietet ausschließlich PAdES B-B-Signierung; Profile für die Langzeitvalidierung sind eine Enterprise-Funktion und liegen außerhalb des Geltungsbereichs dieser Brücke.
  • Es zertifiziert oder garantiert nichts und macht die Pipeline auch nicht „manipulationssicher“. Es implementiert die spezifischen, anhand des Quellcodes überprüfbaren Schutzmaßnahmen, die auf dieser Seite beschrieben sind, und nichts darüber hinaus.
SymptomErste Prüfung
Worker URL must use HTTPSDas Schema der konfigurierten workerUrl.
private or reserved IPDie DNS-Records des Worker-Hostnamens; ein Record, der sich in einen Bereich nach RFC 1918 / Loopback / RFC 3927 auflöst.
DNS answer changed since validationDNS-Instabilität oder ein Rebinding-Versuch; lösen Sie erneut auf und prüfen Sie die Record-Menge.
cURL transport errorNetzwerkpfad, TLS-Kette und — falls Pins gesetzt sind — ob das SPKI des ausgelieferten Zertifikats noch in der Pin-Menge enthalten ist.
Renderings schlagen direkt nach einer Zertifikatsrotation fehlEine Pin-Menge ohne passenden Backup-Pin. Fügen Sie das neue SPKI vor dem Rotieren als Backup hinzu.
is not installed / no LocalRendererFactoryInterfaceFallback aktiviert, aber keine Factory verdrahtet, oder nextpdf/artisan nicht vorhanden.
Ratenbegrenzungs-Ablehnungen über Knoten hinweg inkonsistentDer In-Memory-Limiter arbeitet pro Prozess; schalten Sie einen gemeinsam genutzten Store vor.

Melden Sie Schwachstellen über GitHub Security Advisories oder den Sicherheitskontakt in der SECURITY.md des Repositorys. Reichen Sie Sicherheitsprobleme nicht als öffentliche GitHub-Issues ein.

  • /integrations/cloudflare/overview/ — warum das Paket um diese Grenze herum konzipiert ist.
  • /integrations/cloudflare/configuration/ — Felder für Pin-Mengen und Limits.
  • /integrations/cloudflare/troubleshooting/ — vollständige Zuordnung von Fehlern zu Exceptions.