💡 Voll funktionsfähiges Beispiel auf GitHub verfügbar:
load-untrusted-documents-safely-python

Der alte Weg war schmerzhaft

Sie haben drei Zeilen geschrieben, um ein Vorschaubild eines hochgeladenen Dokuments zu rendern. Sie sahen so aus und schienen in Ordnung zu sein:

with signature.Signature(upload_path) as sign:
    save_page_preview(sign, thumbnail_path)

Was diese Zeilen vor GroupDocs.Signature 26.9 taten, war, jede Adresse abzurufen, auf die das Dokument verwies. Eine Word‑Datei kann ein Bild enthalten, das sie nicht selbst speichert – die Datei speichert eine URL, und was immer sie öffnet, lädt diese URL herunter. Auf einem Desktop ist das ein Feature. Auf einem Server, der Uploads akzeptiert, bedeutet das, dass die Person, die Ihnen die Datei sendet, entscheidet, welche Adressen Ihre Infrastruktur anfragt.

Der Angriff hat einen Namen: Server‑Side Request Forgery, und er hat drei Ausprägungen, die es zu benennen gilt. Eine interne Adresse, die vom Internet aus nicht erreichbar ist, ist von Ihrem Server aus erreichbar, sodass ein manipuliertes Dokument Ihren Dienst http://169.254.169.254/ oder einen Admin‑Endpunkt auf localhost abrufen lassen kann. Ein UNC‑Pfad kann einen Windows‑Host dazu bringen, ausgehend zu authentifizieren und damit Anmeldeinformationen an einen Angreifer‑Server weiterzugeben. Und ein Link zu einem Host, der einfach nie antwortet, hält den Ladevorgang blockiert, bis er timeoutet – ein günstiger Weg, einen Worker‑Pool mit harmlos aussehenden Dokumenten zu erschöpfen.

Nichts davon ist ein Fehler in der Dokumentenbibliothek. Einen Link zu folgen, ist das, was das Format verlangt. Das Unbehagen lag darin, dass das Akzeptieren standardmäßig aktiviert war, im Code jedoch niemand es bei einer Code‑Review markierte.

Es gibt einen besseren Weg

Sicheres Laden von Dokumenten ist das Verhalten von GroupDocs.Signature für Python, das diese Anfragen ablehnt. Ab Version 26.9 ist LoadOptions.skip_external_resources standardmäßig auf True gesetzt, sodass dieselben drei Zeilen nun nichts mehr abrufen und stattdessen einen Platzhalter dort rendern, wo das verknüpfte Bild gewesen wäre.

Die Änderung ist ein Standardwert und keine neue Funktion – die Eigenschaft existierte bereits. Was 26.9 geändert hat, ist, in welche Richtung sie zeigt, wenn Ihr Code nichts angibt, und das ist die einzige Einstellung, die die meisten Dienste jemals verwenden.

Der neue Weg: Drei Lade‑Modi

Schritt 1 – Standard für alles Unbekannte beibehalten

Keine LoadOptions überhaupt:

with signature.Signature(source_path) as sign:
    return save_page_preview(sign, preview_path)

Es wird nichts angefordert. Die Vorschau ist kleiner, als sie sonst wäre, und dieser Größenunterschied ist der praktischste Beweis dafür, dass keine Anfrage die Maschine verlassen hat.

Schritt 2 – Einen Host, den Sie tatsächlich besitzen, auf die Whitelist setzen

Viele Dokumente verlinken irgendwo hin, das legitim ist: ein Unternehmens‑CDN, ein interner Bild‑Server, ein Vorlagen‑Store. Erlauben Sie das und nichts anderes:

load_options = LoadOptions()
load_options.whitelisted_resources = [trusted_address]

with signature.Signature(source_path, load_options) as sign:
    return save_page_preview(sign, preview_path)

Die passende Regel verdient Aufmerksamkeit. Sie ist ein case‑insensitiver Teilstring‑Test gegen die Ressourcen‑Adresse, was ein kurzes Fragment gefährlich macht: github trifft auf github.attacker.example/payload.png genauso leicht wie auf den von Ihnen beabsichtigten Host. Verwenden Sie ein Schema, einen Host und einen Pfad – dieses Beispiel setzt raw.githubusercontent.com/groupdocs-signature/ auf die Whitelist.

Schritt 3 – Alles bewusst zulassen

Das Verhalten vor 26.9, das weiterhin verfügbar ist:

load_options = LoadOptions()
load_options.skip_external_resources = False

Sinnvoll für Dokumente, die Ihre eigene Anwendung erzeugt hat. Eine Falle: Die veraltete Eigenschaft load_external_resources hat die entgegengesetzte Polarität, sodass skip_external_resources = False das ersetzt, was load_external_resources = True früher bedeutete. Kopieren Sie einen Wert von der alten Eigenschaft, und Sie kehren Ihre Sicherheitslage um, ohne dass ein Fehler Sie darauf hinweist.

Nebeneinander: Vorher vs. Nachher

Dasselbe Dokument, derselbe Code‑Pfad, drei Lade‑Richtlinien. Dies sind die Dateigrößen der im Beispiel‑Result/‑Ordner abgelegten Dateien, sodass sie geprüft statt blind vertraut werden können:

Lade‑Modus Vorschau‑Größe Ausgehende Anfragen
Standard (26.9 und später) 16 435 Bytes keine
Whitelist‑Host 51 738 Bytes eine, zur erlaubten Adresse
Alle Ressourcen (Standard vor 26.9) 51 738 Bytes eine pro verknüpfter Ressource

Das verknüpfte Bild macht 35 303 Bytes dieses Unterschieds aus. Ich habe die Einstellung erst vertraut, als mir diese beiden Zahlen nebeneinander standen, und ich würde dasselbe empfehlen: Das Auslesen der Eigenschaft sagt Ihnen, was Sie konfiguriert haben, nicht, was der Prozess tatsächlich getan hat.

Was gilt als externe Ressource?

Enger gefasst, als die meisten erwarten, weshalb das Upgrade meist unauffällig verläuft. Verknüpfte Bilder statt eingebetteter, INCLUDEPICTURE‑Felder, verknüpfte Bilder in Präsentationen und Tabellenkalkulationen sowie die Bilder und Stylesheets, auf die ein SVG verweist. Eingebettete Inhalte bleiben unberührt, weil sie bereits in der Datei liegen und kein Abruf nötig ist, um sie darzustellen.

Diese Unterscheidung bildet die gesamte Sicherheitsgrenze. Ein Dokument kann Ihren Server nur dann erreichen lassen, wenn es eine Adresse speichert anstatt der Bytes, also lautet die Frage für jedes Korpus einfach: Wie viele seiner Dateien verlinken statt einbetten? Wenn keine verlinken, kostet Sie der neue Standard nichts und Sie können upgraden, ohne weiter zu lesen.

Praxisbeispiel: Ein Upload, der signiert wird

Der Anwendungsfall, für den die Standard‑Änderung gedacht ist. Ein Dokument kommt von außen, und Sie müssen eine Signatur darauf setzen:

with signature.Signature(source_path) as sign:
    options = QrCodeSignOptions("Approved by GroupDocs.Signature")
    options.encode_type = QrCodeTypes.QR
    options.left = 400
    options.top = 50
    options.width = 120
    options.height = 120

    result = sign.sign(output_path, options)

Während das Dokument geladen, signiert oder gespeichert wird, wird keine externe Ressource angefordert. Die signierte Ausgabe behält ihren Link, sodass ein Benutzer, der sie später in Word öffnet, das Bild weiterhin auf seiner eigenen Maschine auflöst. Das Überspringen ist eine serverseitige Richtlinie, keine Änderung am Dokument – genau das macht es sicher, auf Dateien anzuwenden, die Sie im Auftrag anderer verarbeiten.

Was ändert sich sonst, wenn Sie upgraden?

Für die meisten Dienste nichts Sichtbares, was es wert ist, ausdrücklich erwähnt zu werden, weil ein Sicherheits‑Standard, der das Verhalten überall ändert, ein Upgrade‑Review nicht überstehen würde. Signieren, Verifizieren und Suchen bleiben unverändert. Die Ausnahme ist eine Vorschau, die früher ein verknüpftes Bild zeigte und jetzt einen Platzhalter anzeigt – die Änderung leistet also ihre Arbeit. Setzen Sie den Host auf die Whitelist, wenn er Ihnen gehört, akzeptieren Sie ihn, wenn nicht.

Besonders zu erwähnen: SVG. Ein SVG kann Bilder und Stylesheets per URL referenzieren; diese Referenzen gelten als externe Ressourcen nach derselben Regel, und SVG ist sowohl ein gängiges Upload‑Format als auch ein häufiger SSRF‑Vektor. Ein Dienst, der SVG‑Avatare akzeptiert und serverseitig rendert, ist genau das Szenario, das diese Änderung schützt.

Ein Python‑Detail: Wie die Vorschau geschrieben wird

PreviewOptions nimmt zwei Stream‑Factory‑Methoden statt eines Pfads, und einfache Python‑Callables reichen völlig aus:

def create_page_stream(page_data):
    return open(preview_path, "wb")

def release_page_stream(page_data, page_stream):
    page_stream.close()

preview_options = PreviewOptions(create_page_stream, release_page_stream)
preview_options.preview_format = PreviewOptions.PreviewFormats.PNG
sign.generate_preview(preview_options)

Eine erstellt pro Seite einen Stream, die andere schließt ihn wieder. Das Beispiel‑Dokument hat eine einzelne Seite, sodass eine Datei geschrieben wird; bei mehrseitigem Input fügen Sie die Seitennummer in den Namen ein oder jede Seite überschreibt die vorherige.

Fazit

Der Standard wurde umgekehrt, sodass das riskante Verhalten eine explizite Entscheidung erfordert und das sichere Verhalten nichts weiter braucht. Behalten Sie den Standard für untrusted Input bei, setzen Sie eine enge Whitelist, wenn Ihre eigenen Hosts beteiligt sind, und denken Sie daran, dass das Signieren niemals das Netzwerk benötigt.

Wenn Sie eine stärkere Prüfung als die Dateigröße wollen, richten Sie ein Test‑Dokument auf einen von Ihnen kontrollierten Host ein und beobachten Sie das Zugriffs‑Log, während die Vorschau läuft. Die Größe sagt Ihnen, ob Bytes angekommen sind; das Zugriffs‑Log sagt Ihnen, ob überhaupt eine Anfrage gestellt wurde – und diese beiden unterscheiden sich genau in dem Fall, der zählt: Ein whitelisted Host, der nicht erreichbar ist, sieht im Ergebnis identisch aus wie ein blockierter.

Das Ausführen des Beispiels mit einem Ihrer eigenen Dokumente dauert etwa eine Minute und zeigt Ihnen in drei Dateigrößen exakt, was Ihr Service im Auftrag des Absenders abgerufen hat.

Weitere Ressourcen