💡 Exemple complet fonctionnel disponible sur GitHub :
load-untrusted-documents-safely-python

L’ancienne méthode était pénible

Vous avez écrit trois lignes pour rendre une vignette d’un document téléchargé. Elles ressemblaient à ceci, et cela fonctionnait :

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

Ce que ces lignes faisaient, avant GroupDocs.Signature 26.9, était de récupérer chaque adresse pointée par le document. Un fichier Word peut contenir une image qu’il ne possède pas — le fichier stocke une URL, et tout ce qui l’ouvre télécharge cette URL. Sur un poste de travail, c’est une fonctionnalité. Sur un serveur qui accepte des téléchargements, cela signifie que la personne qui vous a envoyé le fichier décide quelles adresses votre infrastructure va demander.

L’attaque porte un nom, server‑side request forgery (SSRF), et se présente sous trois formes. Une adresse interne inaccessible depuis Internet est accessible depuis votre serveur, de sorte qu’un document manipulé peut faire récupérer votre service http://169.254.169.254/ ou un point de terminaison d’administration sur localhost. Un chemin UNC peut inciter un hôte Windows à s’authentifier en sortie, remettant ainsi des identifiants à un serveur contrôlé par l’attaquant. Et un lien vers un hôte qui ne répond jamais bloque le fil de chargement jusqu’à l’expiration, ce qui est un moyen économique d’épuiser un pool de travailleurs avec des documents qui semblent inoffensifs.

Rien de cela n’est un bug dans la bibliothèque de documents. Suivre un lien est ce que le format demande. L’inconfort venait du fait que le comportement par défaut était d’obéir, sans que le code ne le signale lors d’une revue.

Il existe une meilleure façon

Le chargement sécurisé de documents est le comportement de GroupDocs.Signature pour Python qui refuse d’effectuer ces requêtes. Depuis la version 26.9, LoadOptions.skip_external_resources vaut True par défaut, de sorte que les mêmes trois lignes ne récupèrent plus rien et affichent un espace réservé à la place de l’image liée.

Le changement est un réglage par défaut plutôt qu’une nouvelle fonctionnalité — la propriété existait déjà. Ce que la version 26.9 a modifié, c’est la direction qu’elle prend lorsque votre code ne précise rien, ce qui est le seul réglage utilisé par la plupart des services.

La nouvelle méthode : trois modes de chargement

Étape 1 — Conservez le réglage par défaut pour tout ce qui est non fiable

Aucun LoadOptions :

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

Rien n’est demandé. L’aperçu est plus petit que dans le cas contraire, et cette différence de taille constitue la preuve la plus pratique que aucune requête n’a quitté la machine.

Étape 2 — Mettez en liste blanche un hôte que vous possédez réellement

De nombreux documents pointent vers des ressources légitimes : un CDN d’entreprise, un serveur d’images interne, un magasin de modèles. Autorisez cela et rien d’autre :

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 règle de correspondance mérite attention. Il s’agit d’un test de sous‑chaîne insensible à la casse sur l’adresse de la ressource, ce qui rend un fragment court dangereux : github correspond à github.attacker.example/payload.png aussi facilement que l’hôte que vous aviez prévu. Utilisez un schéma, un hôte et un chemin — cet exemple met en liste blanche raw.githubusercontent.com/groupdocs-signature/.

Étape 3 — Autorisez tout, délibérément

Le comportement pré‑26.9, toujours disponible :

load_options = LoadOptions()
load_options.skip_external_resources = False

Raisonnable pour des documents générés par votre propre application. Un piège : la propriété obsolète load_external_resources a la polarité inverse, de sorte que skip_external_resources = False remplace load_external_resources = True. Copier une valeur de l’ancienne propriété inverse votre posture de sécurité sans aucune alerte.

Comparaison côte à côte : avant vs. après

Même document, même chemin de code, trois politiques de chargement. Ce sont les tailles des fichiers présents dans le dossier Result/ de l’exemple, afin que vous puissiez les vérifier plutôt que de les accepter sur parole :

Mode de chargement Taille de l’aperçu Requêtes sortantes
par défaut (26.9 et versions ultérieures) 16 435 octets aucune
hôte en liste blanche 51 738 octets une, vers l’adresse autorisée
toutes les ressources (par défaut avant 26.9) 51 738 octets une par ressource liée

L’image liée représente 35 303 octets de cette différence. Je n’ai pas cru au réglage tant que ces deux chiffres n’étaient pas côte à côte, et je suggère la même chose : relire la propriété vous indique ce que vous avez configuré, pas ce que le processus a réellement fait.

Qu’est‑ce qui est considéré comme une ressource externe ?

Plus restreint que ce que les gens imaginent, ce qui explique pourquoi la mise à jour se passe généralement sans incident. Images liées plutôt qu’embarquées, champs INCLUDEPICTURE, images liées dans les présentations et les feuilles de calcul, ainsi que les images et feuilles de style référencées par un SVG. Le contenu embarqué reste intact, car il est déjà présent dans le fichier et aucune requête n’est nécessaire pour le rendre.

Cette distinction constitue toute la frontière de sécurité. Un document ne peut faire sortir votre serveur que s’il stocke une adresse au lieu des octets, donc la question pour n’importe quel corpus se résume à savoir combien de ses fichiers contiennent des liens plutôt que des incorporations. S’il n’y en a aucun, le nouveau réglage par défaut ne vous coûte rien et vous pouvez mettre à jour sans lire davantage.

Exemple réel : un téléchargement qui doit être signé

Le cas d’utilisation qui justifie le changement de défaut. Un document arrive de l’extérieur et vous devez y apposer une signature :

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)

Aucune ressource externe n’est demandée pendant le chargement, la signature ou l’enregistrement du document. Le fichier signé conserve son lien, de sorte qu’un utilisateur qui l’ouvre plus tard dans Word voit toujours l’image résolue sur sa propre machine. Ignorer les ressources externes est une politique côté serveur, pas une modification du document — c’est exactement ce qui le rend sûr à appliquer aux fichiers que vous traitez pour le compte de tiers.

Quels autres changements surviennent lors de la mise à jour ?

Pour la plupart des services, rien de visible, ce qui vaut la peine d’être indiqué clairement parce qu’un réglage de sécurité qui modifie le comportement partout ne passerait pas une revue de mise à jour. La signature, la vérification et la recherche restent inchangées. L’exception est un aperçu qui affichait auparavant une image liée et montre maintenant un espace réservé — le changement fait son travail. Mettez en liste blanche l’hôte si c’est le vôtre, sinon refusez-le.

À part cela, il faut souligner le cas du SVG. Un SVG peut référencer des images et des feuilles de style par URL ; ces références sont des ressources externes soumises à la même règle, et le SVG est à la fois un format d’upload courant et un vecteur SSRF répandu. Un service qui accepte des avatars SVG et les rend côté serveur est précisément le type de système que ce changement protège.

Un détail Python : comment l’aperçu est écrit

PreviewOptions accepte deux usines de flux plutôt qu’un chemin, et de simples appelables Python suffisent :

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)

L’une crée un flux par page, l’autre le libère. Le document d’exemple possède une seule page, donc un fichier est écrit ; pour une entrée multi‑pages, incluez le numéro de page dans le nom ou chaque page écrasera la précédente.

Conclusion

Le réglage par défaut a basculé de sorte que le comportement à risque nécessite une décision explicite et que le comportement sûr ne nécessite rien. Conservez le réglage par défaut pour les entrées non fiables, mettez en liste blanche de façon stricte les hôtes que vous contrôlez, et souvenez‑vous que la signature n’a jamais eu besoin du réseau.

Si vous voulez une vérification plus poussée que la taille du fichier, pointez un document de test vers un hôte que vous contrôlez et observez son journal d’accès pendant l’exécution de l’aperçu. La taille indique si des octets sont arrivés ; le journal d’accès indique si une requête a été faite, et ces deux informations diffèrent exactement dans le cas qui compte — un hôte en liste blanche mais inaccessible ressemble à un hôte bloqué lorsqu’on ne regarde que le résultat.

Exécuter l’exemple sur l’un de vos propres documents prend une minute et vous indique, en trois tailles de fichiers, exactement ce que votre service a récupéré au nom de l’expéditeur du fichier.

Ressources supplémentaires