Salta ai contenuti
getnextpdf.com

Sanitizzare PDF non fidati: content disarm and reconstruction

Spec: ISO 32000-2, §12.6.4

Un PDF che arriva dal mondo esterno è codice tanto quanto è un documento. Può portare JavaScript, una Launch action, un eseguibile incorporato, e una struttura costruita per far inciampare un parser. Il Content Disarm & Reconstruction (CDR) tratta quel file come non fidato, mantiene solo ciò che è sicuro, e ricostruisce un PDF pulito dai sopravvissuti.

Questa pagina spiega come il CdrEngine di NextPDF Enterprise lo fa — e l’unica cosa onesta che ogni fornitore di CDR dovrebbe dire ad alta voce: il file ricostruito è una proiezione di sicurezza dell’originale, non una sua copia fedele.

Le parti pericolose di un PDF non sono esotiche. Sono funzionalità standard. La specifica definisce un intero catalogo di action — cosa succede quando un documento si apre, quando una pagina viene mostrata, quando un campo cambia — e quel catalogo include JavaScript e Launch action (Spec: ISO 32000-2, §12.6.4). Un lettore che onora il formato le eseguirà volentieri. Questo è l’appiglio dell’attaccante: un file che è perfettamente valido e perfettamente malevolo allo stesso tempo.

Filtrare per estensione del file non serve a nulla qui. La minaccia è dentro un PDF ben formato, quindi l’unica vera difesa è aprirlo, comprenderlo, e spogliarlo della macchineria attiva prima che raggiunga mai un renderer. Questa è la postura di validazione degli input che descrive la guida OWASP su file-upload e validazione degli input: non fidarsi mai dei byte, e preferire ricostruire un artefatto noto-buono piuttosto che scandire uno ostile alla ricerca di firme note-cattive.

  • Il CDR presuppone che l’input sia ostile e produce un file nuovo anziché rattoppare quello vecchio.
  • CdrEngine::sanitize() esegue sei fasi: parse, controllo di ammissione, rilevamento delle minacce, filter, scrub dei riferimenti, rebuild.
  • Restituisce un CdrResult che dice cosa è stato rimosso, se il documento sia stato persino ammesso, e — se no — perché sia stato respinto.
  • L’output è una proiezione di sicurezza. Non è esplicitamente non una copia probatoria, una corrispondenza di hash, o un artefatto di archiviazione. Questo è un contratto, non un’avvertenza.
  • Questo è solo Enterprise. Il core di NextPDF non esegue CDR.

La documentazione stessa del motore dichiara la linea rossa di progettazione in tre parole: Security Projection Layer. sanitize() è una trasformazione distruttiva e non reversibile. Le è permesso buttare via byte. Ciò che non le è permesso fare è fingere che il risultato sia lo stesso documento.

La pipeline è deliberatamente ordinata. Ogni fase restringe la fiducia prima che la fase successiva agisca su di essa.

  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.
The six phases of CdrEngine::sanitize(), in order. The document is parsed, then admitted or rejected against resource limits, then scanned for threats; dangerous objects are filtered out, dangling references are scrubbed, and only the surviving safe objects are serialised into a brand-new PDF. The original bytes never carry through.

Ammissione prima del disarm. Prima che una singola minaccia sia rimossa, il motore chiede se il documento valga persino la pena di essere elaborato. CdrPolicy porta i limiti — maxObjects, maxPageCount, maxDecodedStreamBytes, e un maxInflationRatio che difende contro le bombe di decompressione. Un documento che li sfonda è respinto, e il CdrResult lo dice con admitted: false e un rejectionReason. Questa distinzione è portante: «l’abbiamo pulito» e «lo abbiamo rifiutato» sono esiti diversi, e il tipo del risultato li tiene separati così che anche la tua reportistica degli errori possa farlo.

Le minacce sono nominate, non indovinate. ThreatDetector::detect() mappa ogni costrutto pericoloso a un ThreatTypeJavaScript, LaunchAction, OpenAction, AdditionalActions, RemoteGoTo, SubmitForm, ImportData, EmbeddedFiles, RichMedia, NamedJavaScript, e altri — ciascuno legato a una specifica funzionalità PDF. Un oggetto che non può essere analizzato affatto diventa il proprio tipo di minaccia, UnparseableObject, perché un oggetto che il sanitizzatore non può leggere è un oggetto per cui non può garantire. Ogni ritrovamento è un DetectedThreat che porta il numero dell’oggetto incriminato, così che la rimozione sia precisa.

La rimozione protegge il documento, non solo il payload. Alcune chiavi pericolose vivono sul document catalog (il Root) — una /OpenAction che scatta all’apertura, per esempio. Rimuovere l’intero oggetto Root per eliminare una chiave distruggerebbe il catalog e romperebbe silenziosamente il file. Il motore gestisce invece queste come strip di chiave sul posto sul catalog, e come una guardia fail-closed si rifiuta di emettere un documento ricostruito che ha perso del tutto il suo /Root. Un sanitizzatore che produce un file strutturalmente rotto mentre riporta successo è esattamente la modalità di fallimento che questa guardia esiste per prevenire.

La forma qui sotto è il vero punto d’ingresso. Si consegnano al motore byte grezzi e una policy; si riceve indietro un CdrResult che è onesto su ciò che è successo.

<?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.

Non c’è alcun percorso in cui questo codice restituisce in silenzio un file ancora pericoloso. O il documento è ammesso e disarmato, oppure è respinto con un motivo dichiarato. La lista removedThreats significa che il disarm è verificabile, non magico.

«Il CDR è solo redazione con passaggi in più.»

Non lo è, e confondere i due è pericoloso. La redazione rimuove l’informazione — nomi, numeri di conto, il contenuto che un essere umano non deve vedere. Il CDR rimuove la capacità — il JavaScript, la Launch action, il payload incorporato che una macchina non deve eseguire. Hanno criteri di successo opposti. Una redazione è corretta quando il contenuto sensibile è sparito e il resto è preservato alla lettera. Un disarm è corretto quando la minaccia è sparita, ed è perfettamente disposto ad alterare struttura benigna per arrivarci. Si usi lo strumento che corrisponde alla tua intenzione; non si ricorra a uno aspettandosi le garanzie dell’altro.

Un secondo equivoco è che una ricostruzione pulita provi che l’originale fosse pulito. Non prova nulla sull’originale. Prova solo che l’output non contiene alcuna minaccia rilevata. L’input può essere stato un’arma; il compito del CDR è assicurarsi che ciò che si inoltra a valle non lo sia.

Questa è la parte che le brochure saltano, quindi la diremo chiaramente.

  • L’output è una proiezione di sicurezza, non una copia probatoria. Il sorgente del CdrEngine porta questo come una linea rossa architetturale. Il PDF sanitizzato non deve essere usato per la conservazione di prove legali, per il confronto di hash con l’originale, o come copia di archiviazione. La trasformazione è distruttiva e non reversibile per progetto.
  • Il rilevamento ha un ambito. Il CDR rimuove le minacce che sa nominare. È uno strato forte e verificabile in uno stack di difesa in profondità — non una garanzia che un file sia libero da ogni possibile tecnica futura. Lo si tenga dietro la stessa validazione degli upload, i controlli di content-type, e la gestione a privilegio minimo che descrive la guida OWASP su file-upload.
  • Alcune policy sono intenzionalmente con perdita. I casi Strip* opt-in rimuovono file incorporati, firme, campi di modulo, layer, e media 3D. Sono potenti ed elimineranno contenuto legittimo — un payload di fattura ZUGFeRD/Factur-X, per esempio. Sono disattivati per impostazione predefinita esattamente per questo motivo. Li si attivi consapevolmente.
  • Le firme non sopravvivono a una ricostruzione. Ricostruire il file ne cambia i byte, quindi qualsiasi firma digitale originale non corrisponde più al suo byte range. Un documento disarmato è non firmato rispetto alla sorgente. Se serve un artefatto firmato, si firmi l’output pulito come un nuovo atto.
Content Disarm & Reconstruction (CDR) — edition availability
EditionAvailability
Core

Non disponibile. Il core di NextPDF non esegue CDR. Analizza, renderizza e scrive PDF; non rileva minacce né ricostruisce input non fidati.

Pro

Non disponibile nell’edizione Pro.

Enterprise

Disponibile tramite CdrEngine. L’output disarmato è una proiezione di sicurezza della sorgente — non deve essere trattato come una copia probatoria, confrontabile per hash, o di archiviazione dell’originale.

  • CDR (Content Disarm & Reconstruction) — una strategia di sanitizzazione che analizza un file non fidato, rimuove i componenti attivi o pericolosi, e ricostruisce un file pulito dal resto sicuro, anziché scandire alla ricerca di firme note-cattive.
  • Proiezione di sicurezza — un output sanitizzato che preserva abbastanza della sorgente da essere utile pur garantendo la rimozione delle minacce. È deliberatamente non byte-fedele e non adatto a prove, hashing o archiviazione.
  • Controllo di ammissione — il gate pre-sanitizzazione che respinge i documenti che superano i limiti di risorse (numero di oggetti, numero di pagine, dimensione dello stream decodificato, rapporto di inflazione) prima che inizi qualsiasi lavoro di disarm.
  • Action — un costrutto PDF che fa accadere qualcosa su un trigger come l’apertura del documento o il cambiamento di un campo; il catalogo dei tipi di action (Spec: ISO 32000-2, §12.6.4) include JavaScript e Launch action, la superficie di minaccia canonica del CDR.
  • Riferimento pendente — un puntatore a un oggetto che non esiste più dopo il filtraggio. La fase di scrub dei riferimenti li rimuove così che il file ricostruito resti strutturalmente coerente.