💡 Volledig werkend voorbeeld beschikbaar op GitHub:
load-untrusted-documents-safely-python

De oude manier was pijnlijk

Je schreef drie regels om een miniatuur van een geüpload document te renderen. Ze zagen er zo uit, en ze leken in orde:

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

Wat die regels deden, vóór GroupDocs.Signature 26.9, was elke adres ophalen waar het document naar verwees. Een Word‑bestand kan een afbeelding bevatten die het niet zelf heeft – het bestand slaat een URL op, en wat het ook opent, downloadt die URL. Op een desktop is dat een functie. Op een server die uploads accepteert, betekent het dat de persoon die je het bestand heeft gestuurd beslist welke adressen jouw infrastructuur opvraagt.

De aanval heeft een naam, server‑side request forgery, en drie vormen die het waard zijn om te benoemen. Een intern adres dat van het internet onbereikbaar is, is wel bereikbaar vanaf je server, dus een gemanipuleerd document kan je service http://169.254.169.254/ of een admin‑endpoint op localhost laten ophalen. Een UNC‑pad kan een Windows‑host ertoe brengen uitgaand te authenticeren, waardoor inloggegevens aan een aanvaller‑gecontroleerde server worden overhandigd. En een link naar een host die simpelweg nooit antwoordt houdt de laadthread vast tot een time‑out, wat een goedkope manier is om een worker‑pool uit te putten met documenten die onschadelijk lijken.

Niets daarvan is een bug in de documentbibliotheek. Een link volgen is wat het formaat vraagt. Het ongemakkelijke was dat meewerken de standaard was, in code die niemand zou markeren bij een review.

Er is een betere manier

Veilig document laden is het gedrag van GroupDocs.Signature voor Python dat weigert die verzoeken te doen. Vanaf versie 26.9 heeft LoadOptions.skip_external_resources standaard de waarde True, zodat dezelfde drie regels nu niets ophalen en een tijdelijke aanduiding weergeven waar de gekoppelde afbeelding zou staan.

De wijziging is een standaardinstelling in plaats van een nieuwe functie – de eigenschap bestond al. Wat 26.9 heeft aangepast, is welke richting het opgaat wanneer je code niets aangeeft, en dat is de enige instelling die de meeste services ooit gebruiken.

De nieuwe manier: drie laadmodi

Stap 1 – Houd de standaard voor alles wat onbetrouwbaar is

Geen LoadOptions whatsoever:

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

Er wordt niets opgevraagd. De preview is kleiner dan anders, en dat grootteverschil is het meest handige bewijs dat er geen verzoek de machine heeft verlaten.

Stap 2 – Whitelist een host die je daadwerkelijk bezit

Veel documenten linken ergens legitiems: een bedrijfs‑CDN, een interne afbeeldingsserver, een sjabloon‑winkel. Sta dat toe en niets anders:

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)

De overeenkomende regel verdient aandacht. Het is een case‑insensitive substring‑test tegen het resource‑adres, waardoor een kort fragment gevaarlijk is: github komt overeen met github.attacker.example/payload.png net zo gemakkelijk als de host die je bedoelde. Gebruik een schema, een host en een pad – dit voorbeeld whitelist raw.githubusercontent.com/groupdocs-signature/.

Stap 3 – Sta alles toe, bewust

Het gedrag vóór 26.9, nog steeds beschikbaar:

load_options = LoadOptions()
load_options.skip_external_resources = False

Redelijk voor documenten die je eigen applicatie heeft geproduceerd. Eén valkuil: de verouderde eigenschap load_external_resources heeft de tegenovergestelde polariteit, dus skip_external_resources = False vervangt load_external_resources = True. Kopieer je een waarde van de oude eigenschap, dan keer je je beveiligingshouding om zonder foutmelding.

Zij‑aan‑zij: vóór vs. daarna

Zelfde document, zelfde codepad, drie laad‑beleidsregels. Dit zijn de groottes van de bestanden die in de Result/‑map van het voorbeeld zijn vastgelegd, zodat ze gecontroleerd kunnen worden in plaats van op vertrouwen:

Laadmodus Preview‑grootte Uitgaande verzoeken
standaard (26.9 en later) 16 435 bytes geen
whitelisted host 51 738 bytes één, naar het toegestane adres
alle resources (pre‑26.9 standaard) 51 738 bytes één per gekoppelde resource

De gekoppelde afbeelding is 35 303 bytes van dat verschil. Ik vertrouwde de instelling niet totdat die twee cijfers naast elkaar stonden, en ik zou hetzelfde aanraden: de eigenschap teruglezen vertelt je wat je geconfigureerd hebt, niet wat het proces deed.

Wat telt als een externe resource?

Kleiner dan mensen verwachten, waardoor de upgrade meestal onopvallend is. Gekoppelde afbeeldingen in plaats van ingesloten, INCLUDEPICTURE‑velden, gekoppelde afbeeldingen in presentaties en spreadsheets, en de afbeeldingen en stijlsheets die een SVG refereert. Ingesloten inhoud blijft onaangeroerd, omdat die al in het bestand zit en er geen verzoek voor nodig is om het te renderen.

Dat onderscheid is de volledige beveiligingsgrens. Een document kan je server alleen laten uitreiken als het een adres opslaat in plaats van de bytes, dus de vraag voor elke corpus is simpelweg hoeveel van zijn bestanden linken in plaats van embedden. Als geen enkel bestand dat doet, kost de nieuwe standaard je niets en kun je upgraden zonder verder te lezen.

Praktijkvoorbeeld: een upload die wordt ondertekend

Het geval waarvoor de standaardwijziging bestaat. Een document komt van buitenaf, en je moet er een handtekening op zetten:

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)

Er wordt geen externe resource opgevraagd terwijl het document wordt geladen, ondertekend of opgeslagen. De ondertekende output behoudt zijn link, zodat een gebruiker die later in Word opent de afbeelding nog steeds op zijn eigen machine ziet. Overslaan is een server‑side beleid, geen bewerking van het document – precies wat het veilig maakt om toe te passen op bestanden die je namens iemand anders verwerkt.

Wat verandert er verder bij een upgrade?

Voor de meeste services niets zichtbaar, wat het waard is om duidelijk te stellen omdat een beveiligingsstandaard die overal gedrag wijzigt, niet zou overleven bij een upgrade‑review. Ondertekenen, verificatie en zoeken blijven onaangeroerd. De uitzondering is een preview die vroeger een gekoppelde afbeelding liet zien en nu een placeholder toont – de wijziging doet zijn werk. Whitelist de host als die van jou is, accepteer het anders.

Specifiek vermelden: SVG. Een SVG kan afbeeldingen en stijlsheets via URL refereren; die referenties zijn externe resources onder dezelfde regel, en SVG is zowel een veelvoorkomend upload‑formaat als een veelvoorkomende SSRF‑vector. Een service die SVG‑avatars accepteert en server‑side rendert, is precies het type systeem dat deze wijziging beschermt.

Eén Python‑detail: hoe de preview wordt weggeschreven

PreviewOptions neemt twee stream‑factories in plaats van een pad, en gewone Python‑callables zijn alles wat het nodig heeft:

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)

De ene maakt een stream per pagina, de andere sluit die weer. Het voorbeeld‑document heeft één pagina, dus één bestand wordt weggeschreven; bij een meer‑pagina‑invoer, voeg je het paginanummer toe aan de naam of overschrijft elke pagina de vorige.

Conclusie

De standaard is omgedraaid zodat het riskante gedrag een expliciete beslissing vereist en het veilige gedrag niets nodig heeft. Houd de standaard voor onbetrouwde invoer, whitelist nauwkeurig waar je eigen hosts bij betrokken zijn, en onthoud dat ondertekenen nooit netwerktoegang nodig had.

Als je een strengere controle wilt dan bestandsgrootte, richt dan een testdocument op een host die jij beheert en bekijk het toegangslog terwijl de preview draait. Grootte vertelt je of er bytes zijn aangekomen; het toegangslog vertelt je of er überhaupt een verzoek is gedaan, en die verschillen precies in het geval dat telt – een whitelisted host die onbereikbaar is, ziet er identiek uit als een geblokkeerde host vanuit alleen de output.

Het uitvoeren van het voorbeeld tegen één van je eigen documenten duurt een minuut en vertelt je, in drie bestandsgroottes, precies wat je service heeft opgehaald namens degene die je het bestand heeft gestuurd.

Aanvullende bronnen