Ir al contenido
getnextpdf.com

Sanear PDF no confiables: desarme y reconstrucción de contenido

Spec: ISO 32000-2, §12.6.4

Un PDF que llega del mundo exterior es código tanto como es un documento. Puede transportar JavaScript, una acción Launch, un ejecutable incrustado y una estructura diseñada para hacer tropezar a un analizador. El Desarme y Reconstrucción de Contenido (CDR) trata ese archivo como no confiable, conserva solo lo que es seguro y reconstruye un PDF limpio a partir de los supervivientes.

Esta página explica cómo lo hace el CdrEngine de NextPDF Enterprise, y lo único honesto que todo proveedor de CDR debería decir en voz alta: el archivo reconstruido es una proyección de seguridad del original, no una copia fiel de él.

Las partes peligrosas de un PDF no son exóticas. Son funciones estándar. La especificación define todo un catálogo de acciones —qué ocurre cuando se abre un documento, cuando se muestra una página, cuando cambia un campo— y ese catálogo incluye acciones de JavaScript y de Launch (Spec: ISO 32000-2, §12.6.4). Un lector que respeta el formato las ejecutará gustosamente. Ese es el punto de apoyo del atacante: un archivo que es perfectamente válido y perfectamente malicioso al mismo tiempo.

Filtrar por extensión de archivo no hace nada aquí. La amenaza está dentro de un PDF bien formado, así que la única defensa real es abrirlo, entenderlo y despojar la maquinaria activa antes de que llegue siquiera a un renderizador. Esta es la postura de validación de entradas que describe la guía de carga de archivos y validación de entradas de OWASP: nunca confíe en los bytes, y prefiera reconstruir un artefacto bueno conocido a escanear uno hostil en busca de firmas malas conocidas.

  • CDR asume que la entrada es hostil y produce un archivo nuevo en lugar de parchear el antiguo.
  • CdrEngine::sanitize() ejecuta seis fases: analizar, control de admisión, detección de amenazas, filtrar, depurar referencias, reconstruir.
  • Devuelve un CdrResult que le indica qué se eliminó, si el documento fue siquiera admitido y —si no— por qué fue rechazado.
  • La salida es una proyección de seguridad. Explícitamente no es una copia probatoria, una coincidencia de hash ni un artefacto de archivado. Eso es un contrato, no una advertencia.
  • Esto es solo de Enterprise. El núcleo de NextPDF no realiza CDR.

La propia documentación del motor enuncia la línea roja del diseño en tres palabras: Security Projection Layer. sanitize() es una transformación destructiva y no reversible. Tiene permiso para tirar bytes. Lo que no tiene permiso para hacer es fingir que el resultado es el mismo documento.

La canalización está deliberadamente ordenada. Cada fase estrecha la confianza antes de que la siguiente actúe sobre ella.

  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.

Admisión antes del desarme. Antes de eliminar una sola amenaza, el motor se pregunta si el documento merece siquiera ser procesado. CdrPolicy lleva los límites —maxObjects, maxPageCount, maxDecodedStreamBytes y un maxInflationRatio que defiende contra las bombas de descompresión—. Un documento que los rebasa es rechazado, y el CdrResult lo dice con admitted: false y un rejectionReason. Esta distinción es de carga: «lo limpiamos» y «lo rechazamos» son resultados distintos, y el tipo de resultado los mantiene separados para que su informe de errores también pueda hacerlo.

Las amenazas se nombran, no se adivinan. ThreatDetector::detect() asigna cada construcción peligrosa a un ThreatTypeJavaScript, LaunchAction, OpenAction, AdditionalActions, RemoteGoTo, SubmitForm, ImportData, EmbeddedFiles, RichMedia, NamedJavaScript, y más— cada uno ligado a una función PDF específica. Un objeto que no puede analizarse en absoluto se convierte en su propio tipo de amenaza, UnparseableObject, porque un objeto que el saneador no puede leer es un objeto por el que no puede responder. Cada hallazgo es un DetectedThreat que lleva el número del objeto infractor, de modo que la eliminación es precisa.

La eliminación protege el documento, no solo la carga útil. Algunas claves peligrosas viven en el catálogo del documento (el Root) —una /OpenAction que se dispara al abrir, por ejemplo—. Eliminar todo el objeto Root para matar una clave destruiría el catálogo y rompería el archivo en silencio. El motor las gestiona en su lugar como despojos de clave en su sitio sobre el catálogo, y como guarda que falla de forma cerrada se niega a emitir un documento reconstruido que haya perdido por completo su /Root. Un saneador que produce un archivo estructuralmente roto mientras informa de éxito es exactamente el modo de fallo que esta guarda existe para impedir.

La forma de abajo es el punto de entrada real. Usted le entrega al motor bytes en bruto y una política; recibe de vuelta un CdrResult que es honesto sobre lo ocurrido.

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

No hay ninguna ruta por la que este código devuelva en silencio un archivo todavía peligroso. O bien el documento es admitido y desarmado, o es rechazado con una razón declarada. La lista removedThreats significa que el desarme es auditable, no mágico.

«CDR no es más que redacción con pasos extra.»

No lo es, y confundir ambos es peligroso. La redacción elimina información —nombres, números de cuenta, el contenido que una persona no debe ver—. CDR elimina capacidad —el JavaScript, la acción Launch, la carga útil incrustada que una máquina no debe ejecutar—. Tienen criterios de éxito opuestos. Una redacción es correcta cuando el contenido sensible ha desaparecido y el resto se conserva al pie de la letra. Un desarme es correcto cuando la amenaza ha desaparecido, y está perfectamente dispuesto a alterar estructura benigna para llegar ahí. Use la herramienta que coincide con su intención; no recurra a una esperando las garantías de la otra.

Un segundo concepto erróneo es que una reconstrucción limpia demuestra que el original estaba limpio. No demuestra nada sobre el original. Solo demuestra que la salida no contiene ninguna amenaza detectada. La entrada pudo haber sido un arma; el trabajo de CDR es asegurar que lo que usted reenvía aguas abajo no lo sea.

Esta es la parte que los folletos se saltan, así que lo diremos sin rodeos.

  • La salida es una proyección de seguridad, no una copia probatoria. El código fuente de CdrEngine lo lleva como una línea roja arquitectónica. El PDF saneado no debe usarse para la conservación de pruebas legales, para la comparación de hashes con el original ni como copia de archivado. La transformación es destructiva y no reversible por diseño.
  • La detección tiene alcance. CDR elimina las amenazas que sabe nombrar. Es una capa fuerte y auditable en una pila de defensa en profundidad, no una garantía de que un archivo esté libre de toda técnica futura posible. Manténgala detrás de la misma validación de cargas, comprobaciones de tipo de contenido y manejo con mínimos privilegios que describe la guía de carga de archivos de OWASP.
  • Algunas políticas son intencionadamente con pérdida. Los casos opcionales Strip* eliminan archivos incrustados, firmas, campos de formulario, capas y medios 3D. Son potentes y borrarán contenido legítimo —por ejemplo, una carga útil de factura ZUGFeRD/Factur-X—. Están desactivados por defecto exactamente por esa razón. Actívelos a conciencia.
  • Las firmas no sobreviven a una reconstrucción. Reconstruir el archivo cambia sus bytes, así que cualquier firma digital original ya no coincide con su rango de bytes. Un documento desarmado queda sin firmar respecto al origen. Si necesita un artefacto firmado, firme la salida limpia como un acto nuevo.
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 (Desarme y Reconstrucción de Contenido): una estrategia de saneamiento que analiza un archivo no confiable, elimina los componentes activos o peligrosos y reconstruye un archivo limpio a partir del remanente seguro, en lugar de escanear en busca de firmas malas conocidas.
  • Proyección de seguridad: una salida saneada que conserva lo suficiente del origen para ser útil, garantizando a la vez la eliminación de las amenazas. Es deliberadamente no fiel byte a byte y no apta para pruebas, hashing o archivado.
  • Control de admisión: la puerta previa al saneamiento que rechaza los documentos que exceden los límites de recursos (recuento de objetos, recuento de páginas, tamaño de flujo decodificado, ratio de inflación) antes de que comience cualquier trabajo de desarme.
  • Acción: una construcción PDF que hace que algo ocurra ante un disparador como la apertura del documento o el cambio de un campo; el catálogo de tipos de acción (Spec: ISO 32000-2, §12.6.4) incluye acciones de JavaScript y de Launch, la superficie de amenaza canónica de CDR.
  • Referencia colgante: un puntero a un objeto que ya no existe tras el filtrado. La fase de depuración de referencias las elimina para que el archivo reconstruido se mantenga estructuralmente coherente.