💡 Esempio completo funzionante disponibile su GitHub:
carica-documenti-non-attendibili-sicuri-python

Il vecchio modo era doloroso

Hai scritto tre righe per generare una miniatura di un documento caricato. Avevano questo aspetto e andavano bene:

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

Quello che quelle righe facevano, prima di GroupDocs.Signature 26.9, era recuperare ogni indirizzo a cui il documento puntava. Un file Word può contenere un’immagine che non è presente nel file – il file memorizza un URL e chi lo apre scarica quell’URL. Su un desktop è una funzionalità. Su un server che accetta upload, significa che la persona che ti ha inviato il file decide quali indirizzi la tua infrastruttura richiede.

L’attacco ha un nome, server‑side request forgery (SSRF), e tre forme da nominare. Un indirizzo interno non raggiungibile da Internet è raggiungibile dal tuo server, quindi un documento manipolato può far richiedere al tuo servizio http://169.254.169.254/ o un endpoint amministrativo su localhost. Un percorso UNC può indurre un host Windows ad autenticarsi verso l’esterno, consegnando credenziali a un server controllato dall’attaccante. E un collegamento a un host che semplicemente non risponde trattiene il thread di caricamento fino al timeout, un modo economico per esaurire il pool di worker con documenti che sembrano innocui.

Niente di tutto ciò è un bug nella libreria di documenti. Seguire un collegamento è ciò che il formato richiede. La parte scomoda era che accontentarsi era il comportamento predefinito, in codice che nessuno segnalava in revisione.

C’è un modo migliore

Il caricamento sicuro dei documenti è il comportamento di GroupDocs.Signature per Python che rifiuta di effettuare tali richieste. Dalla versione 26.9, LoadOptions.skip_external_resources è impostato di default a True, quindi le stesse tre righe ora non recuperano nulla e mostrano un segnaposto al posto dell’immagine collegata.

Il cambiamento è un valore predefinito, non una nuova funzionalità – la proprietà esisteva già. Ciò che 26.9 ha modificato è la direzione in cui punta quando il tuo codice non specifica nulla, che è l’unica impostazione che la maggior parte dei servizi utilizza.

Il nuovo modo: tre modalità di caricamento

Passo 1 – Mantieni il valore predefinito per tutto ciò che è non attendibile

Nessun LoadOptions:

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

Non viene richiesto nulla. L’anteprima è più piccola di quella che sarebbe altrimenti, e quella differenza di dimensione è la prova più comoda disponibile che nessuna richiesta ha lasciato la macchina.

Passo 2 – Inserisci nella whitelist un host che possiedi realmente

Molti documenti collegano a qualcosa di legittimo: un CDN aziendale, un server di immagini interno, un archivio di modelli. Consenti quello e nient’altro:

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)

La regola di corrispondenza merita attenzione. È un test di sottostringa case‑insensitive sull’indirizzo della risorsa, il che rende pericoloso un frammento breve: github corrisponde a github.attacker.example/payload.png così come all’host che intendevi. Usa schema, host e percorso – questo esempio inserisce nella whitelist raw.githubusercontent.com/groupdocs-signature/.

Passo 3 – Consenti tutto, deliberatamente

Il comportamento pre‑26.9, ancora disponibile:

load_options = LoadOptions()
load_options.skip_external_resources = False

Ragionevole per documenti prodotti dalla tua stessa applicazione. Una trappola: la proprietà obsoleta load_external_resources ha polarità opposta, quindi skip_external_resources = False sostituisce load_external_resources = True. Copiando un valore dalla vecchia proprietà, inverti la tua postura di sicurezza senza alcun errore che te lo segnali.

Confronto affiancato: prima vs. dopo

Stesso documento, stesso percorso di codice, tre politiche di caricamento. Queste sono le dimensioni dei file presenti nella cartella Result/ del campione, così possono essere verificate anziché accettate per fede:

Modalità di caricamento Dimensione anteprima Richieste in uscita
predefinita (26.9 e successive) 16 435 byte nessuna
host in whitelist 51 738 byte una, verso l’indirizzo consentito
tutte le risorse (predefinita pre‑26.9) 51 738 byte una per ogni risorsa collegata

L’immagine collegata è 35 303 byte di quella differenza. Non mi fidavo dell’impostazione finché quei due numeri non sono stati messi fianco a fianco, e suggerirei lo stesso: leggere la proprietà restituisce ciò che hai configurato, non ciò che il processo ha effettivamente fatto.

Cosa conta come risorsa esterna?

È più limitato di quanto la gente si aspetti, ed è per questo che l’aggiornamento è di solito indolore. Immagini collegate anziché incorporate, campi INCLUDEPICTURE, immagini collegate in presentazioni e fogli di calcolo, e le immagini e i fogli di stile a cui fa riferimento un SVG. Il contenuto incorporato rimane intatto, perché è già dentro il file e non è necessaria alcuna richiesta per renderlo.

Questa distinzione è l’intera frontiera di sicurezza. Un documento può far contattare il tuo server solo se memorizza un indirizzo invece dei byte, quindi la domanda per qualsiasi corpus è semplicemente quante dei suoi file collegano anziché incorporare. Se nessuno lo fa, il nuovo valore predefinito non ti costa nulla e puoi aggiornare senza ulteriori letture.

Esempio reale: un upload che viene firmato

Il caso per cui esiste il cambiamento predefinito. Un documento arriva da fuori e devi apporvi una firma:

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)

Nessuna risorsa esterna viene richiesta mentre il documento è caricato, firmato o salvato. L’output firmato mantiene il suo collegamento, così un utente che lo apre in Word in seguito vede ancora l’immagine risolta sulla propria macchina. Saltare la richiesta è una politica lato server, non una modifica al documento – ed è proprio questo che lo rende sicuro da applicare a file gestiti per conto di terzi.

Cos’altro cambia quando effettui l’upgrade?

Per la maggior parte dei servizi, nulla di visibile, il che vale la pena affermare chiaramente perché un valore predefinito di sicurezza che modifica il comportamento ovunque non supererebbe una revisione di aggiornamento. Firma, verifica e ricerca rimangono intatti. L’eccezione è un’anteprima che mostrava un’immagine collegata e ora mostra un segnaposto – il cambiamento sta facendo il suo lavoro. Inserisci nella whitelist l’host se è tuo, accettalo se non lo è.

Da segnalare separatamente: SVG. Un SVG può fare riferimento a immagini e fogli di stile tramite URL; questi riferimenti sono risorse esterne secondo la stessa regola, e SVG è sia un formato di upload comune sia un vettore SSRF frequente. Un servizio che accetta avatar SVG e li rende lato server è esattamente il tipo di sistema che questo cambiamento protegge.

Un dettaglio di Python: come viene scritta l’anteprima

PreviewOptions accetta due factory di stream anziché un percorso, e le semplici callable Python sono tutto ciò di cui ha bisogno:

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)

Una crea uno stream per pagina, l’altra lo rilascia. Il documento di esempio ha una sola pagina, quindi viene scritto un file; per input multi‑pagina, includi il numero di pagina nel nome o ogni pagina sovrascrive la precedente.

Conclusione

Il valore predefinito è stato invertito in modo che il comportamento rischioso richieda una decisione esplicita e quello sicuro non richieda nulla. Mantieni il valore predefinito per input non attendibili, inserisci nella whitelist in modo ristretto gli host propri, e ricorda che la firma non ha mai avuto bisogno della rete.

Se vuoi un controllo più rigoroso della dimensione del file, punta un documento di test verso un host che controlli e osserva il suo registro di accessi mentre l’anteprima viene generata. La dimensione ti dice se i byte sono arrivati; il registro di accessi ti dice se è stata fatta una richiesta, e questi due differiscono esattamente nel caso che conta – un host in whitelist ma irraggiungibile appare identico a uno bloccato solo dal risultato.

Eseguire il campione su uno dei tuoi documenti richiede un minuto e ti mostra, in tre dimensioni di file, esattamente cosa il tuo servizio ha recuperato per conto di chi ti ha inviato il file.

Risorse aggiuntive