Aller au contenu
getnextpdf.com

Assainir des PDF non fiables : désarmement et reconstruction de contenu

Spec: ISO 32000-2, §12.6.4

Un PDF qui arrive du monde extérieur est autant du code qu’un document. Il peut transporter du JavaScript, une action Launch, un exécutable incorporé, et une structure conçue pour faire trébucher un analyseur. Le Content Disarm & Reconstruction (CDR) traite ce fichier comme non fiable, ne garde que ce qui est sûr, et reconstruit un PDF propre à partir des survivants.

Cette page explique comment le CdrEngine de NextPDF Enterprise fait cela — et la seule chose honnête que chaque fournisseur de CDR devrait dire à voix haute : le fichier reconstruit est une projection de sécurité de l’original, pas une copie fidèle de celui-ci.

Les parties dangereuses d’un PDF ne sont pas exotiques. Ce sont des fonctionnalités standard. La spécification définit tout un catalogue d’actions — ce qui se passe à l’ouverture d’un document, à l’affichage d’une page, au changement d’un champ — et ce catalogue inclut les actions JavaScript et Launch (Spec: ISO 32000-2, §12.6.4). Un lecteur qui honore le format les exécutera volontiers. C’est le point d’appui de l’attaquant : un fichier qui est parfaitement valide et parfaitement malveillant en même temps.

Filtrer par extension de fichier ne sert à rien ici. La menace est à l’intérieur d’un PDF bien formé, de sorte que la seule vraie défense est de l’ouvrir, de le comprendre, et de retirer la machinerie active avant qu’elle n’atteigne jamais un moteur de rendu. C’est la posture de validation des entrées que décrivent les recommandations d’OWASP sur le téléversement de fichiers et la validation des entrées : ne jamais faire confiance aux octets, et préférer reconstruire un artefact connu comme bon plutôt que de scruter un fichier hostile à la recherche de signatures connues comme mauvaises.

  • Le CDR suppose que l’entrée est hostile et produit un nouveau fichier plutôt que de rapiécer l’ancien.
  • CdrEngine::sanitize() exécute six phases : analyse, contrôle d’admission, détection de menaces, filtrage, nettoyage des références, reconstruction.
  • Il retourne un CdrResult qui t’indique ce qui a été retiré, si le document a même été admis, et — sinon — pourquoi il a été rejeté.
  • La sortie est une projection de sécurité. Ce n’est explicitement pas une copie à valeur de preuve, une correspondance de hachage, ni un artefact d’archivage. C’est un contrat, pas une réserve.
  • Ceci est réservé à Enterprise. Le core de NextPDF n’effectue pas de CDR.

La documentation propre du moteur énonce la ligne rouge de conception en trois mots : Security Projection Layer. sanitize() est une transformation destructive et non réversible. Elle a le droit de jeter des octets. Ce qu’elle n’a pas le droit de faire, c’est de prétendre que le résultat est le même document.

La chaîne est délibérément ordonnée. Chaque phase resserre la confiance avant que la phase suivante n’agisse dessus.

  1. ParsePdfReader builds the object graph from the raw bytes. A parse failure is a rejection, not a best-effort guess.
  2. Admission controlResource limits are checked first — object count, page count, decoded-stream size, per-stream inflation ratio. Over-budget input is rejected, never sanitised.
  3. DetectThreatDetector walks the graph and records each dangerous feature as a DetectedThreat with its object number and type.
  4. FilterObjects are partitioned into safe and removed. Catalog-level keys are stripped in place so the document catalog itself survives.
  5. Reference scrubPointers to removed objects are cleaned so the rebuilt file has no dangling references.
  6. RebuildCdrRebuilder serialises only the safe objects into a new PDF. The output is fresh structure, not an edited original.
Les six phases de CdrEngine::sanitize(), dans l'ordre. Le document est analysé, puis admis ou rejeté au regard des limites de ressources, puis scruté à la recherche de menaces ; les objets dangereux sont filtrés, les références pendantes sont nettoyées, et seuls les objets sûrs survivants sont sérialisés dans un PDF flambant neuf. Les octets d'origine ne sont jamais reportés.

L’admission avant le désarmement. Avant qu’une seule menace ne soit retirée, le moteur demande si le document vaut même la peine d’être traité. CdrPolicy porte les limites — maxObjects, maxPageCount, maxDecodedStreamBytes, et un maxInflationRatio qui défend contre les bombes de décompression. Un document qui les dépasse est rejeté, et le CdrResult le dit avec admitted: false et un rejectionReason. Cette distinction est porteuse : « nous l’avons nettoyé » et « nous l’avons refusé » sont des résultats différents, et le type de résultat les sépare pour que ton signalement d’erreurs puisse en faire autant.

Les menaces sont nommées, pas devinées. ThreatDetector::detect() associe chaque construction dangereuse à un ThreatTypeJavaScript, LaunchAction, OpenAction, AdditionalActions, RemoteGoTo, SubmitForm, ImportData, EmbeddedFiles, RichMedia, NamedJavaScript, et d’autres — chacun lié à une fonctionnalité PDF précise. Un objet qui ne peut pas être analysé du tout devient son propre type de menace, UnparseableObject, car un objet que l’assainisseur ne peut pas lire est un objet dont il ne peut pas se porter garant. Chaque trouvaille est un DetectedThreat portant le numéro de l’objet fautif, de sorte que la suppression est précise.

La suppression protège le document, pas seulement la charge utile. Certaines clés dangereuses vivent sur le catalogue du document (la Root) — une /OpenAction qui se déclenche à l’ouverture, par exemple. Supprimer tout l’objet Root pour tuer une seule clé détruirait le catalogue et casserait silencieusement le fichier. Le moteur les gère plutôt comme des retraits de clé sur place sur le catalogue, et comme garde-fou à échec fermé il refuse d’émettre un document reconstruit qui aurait perdu sa /Root tout court. Un assainisseur qui produit un fichier structurellement cassé tout en rapportant un succès est exactement le mode de défaillance que ce garde-fou existe pour empêcher.

La forme ci-dessous est le vrai point d’entrée. Tu remets au moteur des octets bruts et une politique ; tu récupères un CdrResult qui est honnête sur ce qui s’est passé.

<?php
declare(strict_types=1);
use NextPDF\Enterprise\Security\Cdr\CdrEngine;
use NextPDF\Enterprise\Security\Cdr\CdrPolicy;
$engine = new CdrEngine();
// Standard policy removes the known active threats — JavaScript, Launch
// actions, remote go-to, form submit/import, and the rest — while leaving
// the lossy opt-in "Strip*" cases off by default.
$result = $engine->sanitize($untrustedPdfBytes, CdrPolicy::standard());
if (!$result->admitted) {
// Rejected by admission control (e.g. object/page limit, zip bomb).
// This is NOT a sanitised document. Do not serve it; report the reason.
throw new \RuntimeException($result->rejectionReason);
}
if ($result->hadThreats()) {
// The disarmed bytes are safe to render. Each removed threat carries its
// type and object number for your audit log — never silently.
foreach ($result->removedThreats as $threat) {
error_log(\sprintf(
'CDR removed %s in object %d',
$threat->type->value,
$threat->objectNumber,
));
}
}
$cleanBytes = $result->sanitizedPdf; // The security projection. Not the original.

Il n’existe aucun chemin où ce code rend discrètement un fichier encore dangereux. Soit le document est admis et désarmé, soit il est rejeté avec une raison énoncée. La liste removedThreats signifie que le désarmement est auditable, pas magique.

« Le CDR n’est que du caviardage avec des étapes en plus. »

Ce n’est pas le cas, et confondre les deux est dangereux. Le caviardage supprime de l’information — des noms, des numéros de compte, le contenu qu’un humain ne doit pas voir. Le CDR supprime de la capacité — le JavaScript, l’action Launch, la charge utile incorporée qu’une machine ne doit pas exécuter. Ils ont des critères de succès opposés. Un caviardage est correct lorsque le contenu sensible a disparu et que le reste est préservé verbatim. Un désarmement est correct lorsque la menace a disparu, et il est parfaitement disposé à altérer une structure bénigne pour y parvenir. Utilise l’outil qui correspond à ton intention ; ne te tourne pas vers l’un en attendant les garanties de l’autre.

Une seconde idée fausse est qu’une reconstruction propre prouve que l’original était propre. Elle ne prouve rien sur l’original. Elle prouve seulement que la sortie ne contient aucune menace détectée. L’entrée a pu être une arme ; le travail du CDR est de s’assurer que ce que tu transmets en aval n’en est pas une.

C’est la partie que les brochures sautent, alors nous allons la dire clairement.

  • La sortie est une projection de sécurité, pas une copie à valeur de preuve. Le code source de CdrEngine porte ceci comme une ligne rouge architecturale. Le PDF assaini ne doit pas être utilisé pour la conservation de preuves juridiques, pour une comparaison de hachage avec l’original, ni comme copie d’archivage. La transformation est destructive et non réversible par conception.
  • La détection a une portée. Le CDR retire les menaces qu’il sait nommer. C’est une couche forte et auditable dans une pile de défense en profondeur — pas une garantie qu’un fichier est exempt de toute technique future possible. Garde-le derrière les mêmes validations de téléversement, vérifications de type de contenu et manipulation au moindre privilège que décrivent les recommandations d’OWASP sur le téléversement de fichiers.
  • Certaines politiques sont intentionnellement avec perte. Les cas Strip* à activer retirent les fichiers incorporés, les signatures, les champs de formulaire, les calques et les médias 3D. Ils sont puissants et ils supprimeront du contenu légitime — une charge utile de facture ZUGFeRD/Factur-X, par exemple. Ils sont désactivés par défaut exactement pour cette raison. Active-les en connaissance de cause.
  • Les signatures ne survivent pas à une reconstruction. Reconstruire le fichier change ses octets, de sorte que toute signature numérique d’origine ne correspond plus à sa plage d’octets. Un document désarmé n’est pas signé par rapport à la source. Si tu as besoin d’un artefact signé, signe la sortie propre comme un nouvel acte.
Content Disarm & Reconstruction (CDR) — edition availability
EditionAvailability
Core

Not available. NextPDF core does not perform CDR. It parses, renders, and writes PDFs; it does not threat-detect or rebuild untrusted input.

Pro

Not available in the Pro edition.

Enterprise

Available via CdrEngine. The disarmed output is a security projection of the source — it must not be treated as an evidentiary, hash-comparable, or archival copy of the original.

  • CDR (Content Disarm & Reconstruction) — une stratégie d’assainissement qui analyse un fichier non fiable, retire les composants actifs ou dangereux, et reconstruit un fichier propre à partir du reste sûr, plutôt que de scruter à la recherche de signatures connues comme mauvaises.
  • Projection de sécurité — une sortie assainie qui préserve assez de la source pour être utile tout en garantissant la suppression des menaces. Elle n’est délibérément pas fidèle aux octets et n’est pas adaptée à la preuve, au hachage ou à l’archivage.
  • Contrôle d’admission — la porte de pré-assainissement qui rejette les documents dépassant les limites de ressources (nombre d’objets, nombre de pages, taille de flux décodé, ratio d’inflation) avant que tout travail de désarmement ne commence.
  • Action — une construction PDF qui fait se produire quelque chose sur un déclencheur tel que l’ouverture de document ou le changement d’un champ ; le catalogue des types d’action (Spec: ISO 32000-2, §12.6.4) inclut les actions JavaScript et Launch, la surface de menace canonique du CDR.
  • Référence pendante — un pointeur vers un objet qui n’existe plus après filtrage. La phase de nettoyage des références les supprime pour que le fichier reconstruit reste structurellement cohérent.