Sanitizando PDFs não confiáveis: content disarm and reconstruction
Spec: ISO 32000-2, §12.6.4ISO 32000-2 §12.6.4
Visão geral
Seção intitulada “Visão geral”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.
Por que isso importa
Seção intitulada “Por que isso importa”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.4ISO 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.
A versão resumida
Seção intitulada “A versão resumida”- 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
CdrResultque 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.
Como o NextPDF aborda isso
Seção intitulada “Como o NextPDF aborda isso”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.
- 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.
- 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.
- DetectarO ThreatDetector percorre o grafo e registra cada recurso perigoso como um DetectedThreat com seu número de objeto e tipo.
- 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.
- Reference scrubPonteiros para objetos removidos são limpos, de modo que o arquivo reconstruído não tem referências penduradas (dangling).
- ReconstruirO CdrRebuilder serializa apenas os objetos seguros em um novo PDF. A saída é estrutura nova, não um original editado.
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 ThreatType — JavaScript, 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.
Exemplo prático
Seção intitulada “Exemplo prático”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.
Equívoco comum
Seção intitulada “Equívoco comum”“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.
Limites e fronteiras
Seção intitulada “Limites e fronteiras”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
CdrEnginecarrega 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.
| Edition | Availability |
|---|---|
| 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 |
Documentos relacionados
Seção intitulada “Documentos relacionados”- Como a criptografia de PDF realmente funciona — a outra metade de lidar com PDFs sensíveis: proteger conteúdo versus remover capacidade.
- Erros como recurso — a filosofia de
falhar fechado que as rejeições de controle de admissão do CDR e a guarda de
/Rootincorporam. - Uma API que se recusa a adivinhar — por que um tipo de resultado que distingue limpo de rejeitado supera um melhor esforço silencioso.
Glossário
Seção intitulada “Glossário”- 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.4ISO 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.