Pular para o conteúdo
getnextpdf.com

Sanitizando PDFs não confiáveis: content disarm and reconstruction

Spec: ISO 32000-2, §12.6.4

Um PDF que chega do mundo externo é tanto código quanto documento. Ele pode carregar JavaScript, uma Launch action, um executável embutido, e uma estrutura forjada para derrubar um parser. Content Disarm & Reconstruction (CDR) trata esse arquivo como não confiável, mantém apenas o que é seguro, e reconstrói um PDF limpo a partir dos sobreviventes.

Esta página explica como o CdrEngine do NextPDF Enterprise faz isso — e a única coisa honesta que todo fornecedor de CDR deveria dizer em alto e bom som: o arquivo reconstruído é uma projeção de segurança do original, não uma cópia fiel dele.

As partes perigosas de um PDF não são exóticas. Elas são recursos padrão. A especificação define um catálogo inteiro de actions — o que acontece quando um documento abre, quando uma página é exibida, quando um campo muda — e esse catálogo inclui JavaScript e Launch actions (Spec: ISO 32000-2, §12.6.4). Um visualizador que honra o formato vai alegremente executá-las. Esse é o ponto de apoio do atacante: um arquivo que é perfeitamente válido e perfeitamente malicioso ao mesmo tempo.

Filtrar por extensão de arquivo não faz nada aqui. A ameaça está dentro de um PDF bem formado, então a única defesa real é abri-lo, entendê-lo, e retirar a maquinaria ativa antes que ela sequer alcance um renderizador. Esta é a postura de validação de entrada que a orientação de upload de arquivos e de validação de entrada da OWASP descreve: nunca confie nos bytes, e prefira reconstruir um artefato tido como bom a escanear um hostil em busca de assinaturas tidas como ruins.

  • O CDR assume que a entrada é hostil e produz um arquivo novo em vez de remendar o antigo.
  • CdrEngine::sanitize() executa seis fases: parse, controle de admissão, detecção de ameaças, filtro, reference scrub, reconstrução.
  • Ele retorna um CdrResult que informa o que foi removido, se o documento foi sequer admitido, e — se não foi — por que foi rejeitado.
  • A saída é uma projeção de segurança. Ela explicitamente não é uma cópia probatória, uma correspondência de hash, ou um artefato de arquivamento. Isso é um contrato, não uma ressalva.
  • Isto é somente Enterprise. O core do NextPDF não realiza CDR.

A própria documentação do mecanismo declara a linha vermelha de design em três palavras: Security Projection Layer. sanitize() é uma transformação destrutiva e não reversível. Ele tem permissão para jogar bytes fora. O que ele não tem permissão para fazer é fingir que o resultado é o mesmo documento.

O pipeline é deliberadamente ordenado. Cada fase estreita a confiança antes da fase seguinte agir sobre ela.

  1. ParseO PdfReader constrói o grafo de objetos a partir dos bytes brutos. Uma falha de parse é uma rejeição, não uma adivinhação de melhor esforço.
  2. Controle de admissãoOs limites de recursos são checados primeiro — contagem de objetos, contagem de páginas, tamanho de stream decodificado, razão de inflação por stream. Entrada acima do orçamento é rejeitada, nunca sanitizada.
  3. DetectarO ThreatDetector percorre o grafo e registra cada recurso perigoso como um DetectedThreat com seu número de objeto e tipo.
  4. FiltrarOs objetos são particionados entre seguros e removidos. Chaves no nível do catalog são retiradas no lugar, de modo que o catalog do documento em si sobrevive.
  5. Reference scrubPonteiros para objetos removidos são limpos, de modo que o arquivo reconstruído não tem referências penduradas (dangling).
  6. ReconstruirO CdrRebuilder serializa apenas os objetos seguros em um novo PDF. A saída é estrutura nova, não um original editado.
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.

Admissão antes do disarm. Antes que uma única ameaça seja removida, o mecanismo pergunta se o documento vale sequer ser processado. O CdrPolicy carrega os limites — maxObjects, maxPageCount, maxDecodedStreamBytes, e um maxInflationRatio que defende contra bombas de descompressão. Um documento que estoura esses limites é rejeitado, e o CdrResult diz isso com admitted: false e um rejectionReason. Esta distinção é estrutural: “nós o limpamos” e “nós o recusamos” são desfechos diferentes, e o tipo de resultado os mantém separados, de modo que o seu relatório de erros também pode mantê-los.

As ameaças são nomeadas, não adivinhadas. ThreatDetector::detect() mapeia cada construção perigosa para um ThreatTypeJavaScript, LaunchAction, OpenAction, AdditionalActions, RemoteGoTo, SubmitForm, ImportData, EmbeddedFiles, RichMedia, NamedJavaScript, e mais — cada um atrelado a um recurso específico de PDF. Um objeto que não pode ser parseado de forma alguma se torna seu próprio tipo de ameaça, UnparseableObject, porque um objeto que o sanitizador não consegue ler é um objeto pelo qual ele não consegue responder. Cada achado é um DetectedThreat carregando o número do objeto infrator, de modo que a remoção é precisa.

A remoção protege o documento, não apenas a carga. Algumas chaves perigosas vivem no catalog do documento (o Root) — um /OpenAction que dispara na abertura, por exemplo. Remover o objeto Root inteiro para matar uma chave destruiria o catalog e silenciosamente quebraria o arquivo. O mecanismo trata essas como retiradas de chave no lugar no catalog em vez disso, e como uma guarda de falhar fechado ele se recusa a emitir um documento reconstruído que tenha perdido seu /Root de todo. Um sanitizador que produz um arquivo estruturalmente quebrado enquanto reporta sucesso é exatamente o modo de falha que esta guarda existe para prevenir.

A forma abaixo é o ponto de entrada real. Você entrega ao mecanismo bytes brutos e uma política; você recebe de volta um CdrResult que é honesto sobre o que aconteceu.

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

Não há caminho em que este código entregue silenciosamente de volta um arquivo ainda perigoso. Ou o documento é admitido e desarmado, ou é rejeitado com uma razão declarada. A lista removedThreats significa que o disarm é auditável, não mágico.

“CDR é só redação com passos extras.”

Não é, e confundir os dois é perigoso. A redação remove informação — nomes, números de conta, o conteúdo que um humano não pode ver. O CDR remove capacidade — o JavaScript, a Launch action, a carga embutida que uma máquina não pode executar. Eles têm critérios de sucesso opostos. Uma redação está correta quando o conteúdo sensível sumiu e o resto é preservado palavra por palavra. Um disarm está correto quando a ameaça sumiu, e ele está perfeitamente disposto a alterar estrutura benigna para chegar lá. Use a ferramenta que combina com a sua intenção; não recorra a uma esperando as garantias da outra.

Um segundo equívoco é que uma reconstrução limpa prova que o original era limpo. Ela não prova nada sobre o original. Ela prova apenas que a saída não contém nenhuma ameaça detectada. A entrada pode ter sido uma arma; o trabalho do CDR é garantir que o que você encaminha adiante não seja.

Esta é a parte que os folhetos pulam, então vamos dizê-la de forma clara.

  • A saída é uma projeção de segurança, não uma cópia probatória. O código-fonte do CdrEngine carrega isso como uma linha vermelha arquitetural. O PDF sanitizado não deve ser usado para preservação de evidência legal, para comparação de hash contra o original, ou como cópia de arquivamento. A transformação é destrutiva e não reversível por design.
  • A detecção tem escopo. O CDR remove as ameaças que sabe nomear. Ele é uma camada forte e auditável em uma pilha de defesa em profundidade — não uma garantia de que um arquivo esteja livre de toda técnica futura possível. Mantenha-o por trás da mesma validação de upload, das checagens de content-type e do tratamento de menor privilégio que a orientação de upload de arquivos da OWASP descreve.
  • Algumas políticas são intencionalmente com perda (lossy). Os casos Strip* opt-in removem arquivos embutidos, assinaturas, campos de formulário, camadas e mídia 3D. Eles são poderosos e vão excluir conteúdo legítimo — uma carga de fatura ZUGFeRD/Factur-X, digamos. Eles ficam desligados por padrão por exatamente essa razão. Ligue-os com conhecimento de causa.
  • Assinaturas não sobrevivem a uma reconstrução. Reconstruir o arquivo muda seus bytes, então qualquer assinatura digital original deixa de corresponder à sua faixa de bytes. Um documento desarmado fica sem assinatura em relação à fonte. Se você precisa de um artefato assinado, assine a saída limpa como um novo ato.
Content Disarm & Reconstruction (CDR) — edition availability
EditionAvailability
Core

Não disponível. O core do NextPDF não realiza CDR. Ele faz parsing, renderiza e escreve PDFs; ele não detecta ameaças nem reconstrói entrada não confiável.

Pro

Não disponível na edição Pro.

Enterprise

Disponível via CdrEngine. A saída desarmada é uma projeção de segurança da fonte — ela não deve ser tratada como cópia probatória, comparável por hash, ou de arquivamento do original.

  • CDR (Content Disarm & Reconstruction) — uma estratégia de sanitização que faz parsing de um arquivo não confiável, remove componentes ativos ou perigosos, e reconstrói um arquivo limpo a partir do restante seguro, em vez de escanear em busca de assinaturas tidas como ruins.
  • Projeção de segurança — uma saída sanitizada que preserva o suficiente da fonte para ser útil enquanto garante a remoção das ameaças. Ela é deliberadamente não fiel byte a byte e não adequada para evidência, hashing ou arquivamento.
  • Controle de admissão — a barreira de pré-sanitização que rejeita documentos que excedem limites de recursos (contagem de objetos, contagem de páginas, tamanho de stream decodificado, razão de inflação) antes que qualquer trabalho de disarm comece.
  • Action — uma construção de PDF que faz algo acontecer em um gatilho, como abertura de documento ou mudança de campo; o catálogo de tipos de action (Spec: ISO 32000-2, §12.6.4) inclui JavaScript e Launch actions, a superfície de ameaça canônica do CDR.
  • Referência pendurada (dangling) — um ponteiro para um objeto que não existe mais após a filtragem. A fase de reference scrub remove esses, de modo que o arquivo reconstruído permaneça estruturalmente consistente.