Um PDF não é apenas papel. Ele é um contêiner que pode carregar scripts que são executados quando o arquivo é aberto, links que iniciam programas externos, links que se conectam a servidores web, arquivos aninhados dentro de outros e uma assinatura que atesta que o documento não foi alterado desde que alguém o validou. Quando um arquivo chega de uma fonte não controlada por você, a primeira atitude mais segura não é renderizá-lo. É ler o que o arquivo diz sobre si mesmo e construir um inventário de tudo o que ele pode tentar fazer, para que um ser humano possa decidir se ele deve fazer parte do seu fluxo de trabalho.
Este artigo apresenta um passo a passo de uma auditoria estática e somente leitura sobre essa superfície de risco usando o componente PDFium para Delphi e Lazarus. A auditoria nunca renderiza uma página. Ela analisa a estrutura do documento, enumera as partes do arquivo que possuem comportamento e gera um relatório simples. É a diferença entre pedir para um estranho esvaziar os bolsos na porta ou confiar nele apenas porque ele sorriu.
O que é e o que não é uma auditoria
Deixe claro o limite. Uma visualização em sandbox renderiza um arquivo sob restrições rígidas para que o usuário possa olhá-lo sem que o arquivo toque no resto da máquina. A auditoria vem antes disso. Trata-se de uma inspeção sem renderização cuja única saída é uma descrição da superfície de ameaça: quais scripts existem, quais ações estão vinculadas a links, se o arquivo está assinado e com qual nível de segurança, e o que está anexado. Você a executa quando um documento cruza uma fronteira de confiança, no recebimento por e-mail, em um formulário de envio ou em um canal de parceiros, antes de qualquer etapa posterior abri-lo de fato.
O componente carrega um documento da mesma forma para uma auditoria ou para qualquer outra finalidade. Você define o nome do arquivo e o ativa, o que analisa os dados de referência cruzada e o catálogo do documento sem renderizar uma única página. Tudo o que segue abaixo lê a partir desse estado carregado e não renderizado.
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'Incoming_Invoice.pdf';
Pdf.Active := True; // parses structure, renders nothing
// audit the loaded document here
finally
Pdf.Free;
end;
end;
JavaScript de documento na árvore de nomes
A primeira coisa a enumerar é o código. Um PDF pode carregar JavaScript no nível do documento: scripts que não estão anexados a nenhuma página ou campo, mas ao próprio documento, armazenados na árvore /Names sob uma entrada /JavaScript. Um visualizador compatível os executa na abertura. Esse é o mecanismo por trás de uma longa linhagem de malware em PDF, pois permite que um arquivo execute lógicas no instante em que o usuário clica duas vezes nele, antes mesmo de ler uma palavra.
Um auditor deseja dois fatos sobre cada script: que ele existe e o que ele contém. O componente expõe a contagem e permite ler cada ação como um registro que contém o nome do script e seu corpo completo. Ler o corpo é importante. Um script chamado Doc.0 não diz nada, mas seu texto pode chamar app.launchURL ou montar uma string e passá-la para onde não deveria. Extrair o código-fonte para que um revisor possa lê-lo é o objetivo de sinalizar um arquivo que executa código na abertura.
var
I: Integer;
Action: TPdfJavaScriptAction;
begin
if Pdf.JavaScriptActionCount > 0 then
WriteLn('WARNING: document runs ', Pdf.JavaScriptActionCount,
' script(s) on open');
for I := 0 to Pdf.JavaScriptActionCount - 1 do
begin
Action := Pdf.JavaScriptAction[I];
WriteLn(' script "', Action.Name, '":');
WriteLn(Action.Script); // full body, for a human to read
end;
end;
Um arquivo sem scripts de documento não é automaticamente seguro, pois scripts de página e de campo também existem, mas um arquivo com scripts de documento sempre merece atenção extra. Apenas a contagem de presença é um filtro útil, e o corpo do script é o que transforma o filtro em um julgamento.
Ações Launch e URI
O próximo comportamento a inventariar vive em links e anotações. Dois tipos de ação são mais importantes para um auditor. Uma ação Launch inicia um programa externo ou abre um arquivo local quando o link é acionado. Uma ação URI abre um destino web. Um revisor que analisa um documento suspeito deve ser capaz de ver, sem clicar em nada, se um botão na página três está configurado para iniciar cmd.exe ou para abrir uma URL que não corresponde à marca na página.
O componente classifica os links encontrados e expõe o tipo de ação e o caminho de destino de cada um, para que uma auditoria possa listar cada ação Launch e URI com seu destino. Trata-se de um relatório, não de execução. O auditor lê a ação a partir da estrutura e a registra. Ele nunca a executa.
O controle do visualizador que renderiza documentos é o local onde a execução de uma ação aconteceria, e sua postura padrão é deliberadamente cautelosa. O controle TPdfView possui um conjunto LinkOptions que decide quais tipos de links são acionados automaticamente com um clique. Seu padrão é [loAutoGoto, loAutoOpenURI], o que significa que saltos internos no documento e URLs web podem ser abertos, mas loAutoLaunch está ausente, portanto, ações de inicialização nunca são executadas automaticamente. Para um fluxo de trabalho de auditoria, você pode ir além e limpar o conjunto inteiramente, para que nada seja acionado automaticamente enquanto você ainda decide se confia no arquivo.
// Audit posture for the viewer: nothing auto-runs, nothing auto-opens.
View.LinkOptions := [];
// The shipped default already withholds launch:
// default = [loAutoGoto, loAutoOpenURI]
// loAutoLaunch is NOT in the default set, so external programs
// are never started on a stray click out of the box.
O motivo para reter a inicialização por padrão é simples. Um salto dentro do documento é inofensivo e uma URL é visível e cancelável, mas iniciar um programa externo arbitrário a partir de um clique é a ação mais perigosa que um link em PDF pode solicitar, então ela fica desativada a menos que você a ative explicitamente. Um auditor desativa até mesmo os comportamentos seguros, porque o trabalho é analisar, não agir.
O nível de permissão MDP da assinatura digital
As assinaturas mudam a questão. Uma assinatura simples atesta os bytes no momento da assinatura. Uma assinatura de certificação, do tipo criada com uma regra de detecção e prevenção de modificações de documentos, vai além: ela declara o que pode mudar legitimamente após a certificação do documento, e um visualizador compatível avisa se algo fora do permitido foi tocado. Ler esse nível de permissão informa a um auditor se o arquivo está certificado e, em caso afirmativo, qual o nível de bloqueio esperado.
A permissão MDP é um inteiro com três valores definidos. O nível 1 significa que nenhuma alteração é permitida; qualquer modificação quebra a certificação. O nível 2 permite preenchimento de formulários e assinatura, o caso comum para um contrato que deve ser preenchido e assinado, mas não alterado de outra forma. O nível 3 também permite anotações além de preenchimento e assinatura. Conhecer o nível permite que a lógica de recebimento avalie a intenção: um documento certificado no nível 1 que, no entanto, carrega campos de formulário ou scripts está se contradizendo, e essa contradição deve ser sinalizada.
O componente lê a contagem de assinaturas e expõe cada uma como um registro cujo campo Permission carrega o valor MDP, preenchido diretamente a partir da chamada FPDFSignatureObj_GetDocMDPPermission interna. Uma permissão zero significa que a assinatura não é uma assinatura de certificação (DocMDP), portanto não há bloqueio no nível do documento a relatar.
var
I: Integer;
Sig: TPdfSignature;
begin
if Pdf.SignatureCount = 0 then
WriteLn('document is not signed')
else
for I := 0 to Pdf.SignatureCount - 1 do
begin
Sig := Pdf.Signature[I];
case Sig.Permission of
1: WriteLn('certified: no changes allowed');
2: WriteLn('certified: form fill and signing allowed');
3: WriteLn('certified: form fill, signing and annotations allowed');
else
WriteLn('signed, but not a DocMDP certification');
end;
end;
end;
A auditoria não toma a decisão de confiança por você. Ela garante que a decisão seja informada, e não cega.
O resto da superfície: arquivos anexados e XFA
Mais dois itens completam um inventário completo. Arquivos anexados são documentos inteiros carregados dentro do PDF como anexos e são um meio clássico de transmissão, pois um relatório com aparência inofensiva pode trazer um executável ou um segundo PDF malicioso em sua árvore de anexos. O componente expõe a contagem de anexos e o nome de cada um, para que a auditoria possa listar o que está sendo transportado sem extrair ou abrir nada disso.
A presença do XFA é outro sinalizador. Um formulário XFA substitui o AcroForm estático por uma arquitetura de formulário baseada em XML que traz seu próprio modelo de renderização e script, uma superfície maior e mais complexa do que um formulário simples. Você não precisa processar o XFA para notar que ele está lá; sua mera presença é um sinal de que o arquivo carrega uma camada interativa mais rica que merece atenção extra. O componente a relata como um booleano simples.
var
I: Integer;
begin
if Pdf.XFA then
WriteLn('NOTE: document contains an XFA form layer');
if Pdf.AttachmentCount > 0 then
begin
WriteLn('embedded files: ', Pdf.AttachmentCount);
for I := 0 to Pdf.AttachmentCount - 1 do
WriteLn(' - ', Pdf.AttachmentName[I]);
end;
end;
Uma rotina somente leitura que gera um relatório
Reunindo as peças, a auditoria é um único procedimento que carrega um documento, enumera seus scripts e respectivos corpos, lista os alvos Launch e URI, relata o nível MDP da assinatura, observa anexos e XFA, e grava as descobertas em um log. Ele não renderiza nada, por isso é rápido e não pode ser enganado para exibir conteúdo de página hostil. A saída é um registro simples e legível por humanos que um revisor ou uma regra posterior pode processar.
O modelo que funciona bem na prática é coletar cada descoberta como uma linha, prefixar aquelas que representam riscos reais para que fiquem no topo de uma fila de revisão e salvar todo o conteúdo ao lado do arquivo. Um documento sem scripts, sem ações de inicialização, sem anexos, sem XFA e com assinatura válida ou sem assinatura passa sem alertas. Um documento que aciona vários sinalizadores ao mesmo tempo é o que um revisor deve ver antes de qualquer etapa posterior abri-lo. A auditoria não toma a decisão de confiança por você. Ela garante que a decisão seja informada, e não cega.
Assim que o arquivo passar pela auditoria e você precisar visualizá-lo, faça isso sob restrições em vez de usar um visualizador padrão. A abordagem em nosso passo a passo sobre como construir uma visualização segura de PDF no Delphi mostra como evitar que a manipulação automática de links e conteúdos ativos entrem em ação durante uma visualização controlada. Para integrar essa enumeração em um pipeline de recebimento completo com ferramentas de revisão, consulte o artigo sobre o painel de revisão e recebimento de PDF. Ambos são construídos sobre a mesma base somente leitura e sem renderização e são enviados como parte do PDFium Component para Delphi e C++Builder, junto com as APIs de renderização, texto, formulário e assinatura abordadas em outras partes deste blog.