Um PDF não é apenas papel. É um contentor que pode transportar scripts que são executados quando o arquivo abre, hiperligações que iniciam programas externos, hiperligações que acedem a servidores web, arquivos aninhados dentro de outros arquivos, e uma assinatura que atesta que o documento não foi alterado desde que alguém o validou. Quando um arquivo provém de uma origem que não controla, o primeiro passo mais seguro não consiste em renderizá-lo. Passa sim por ler o que o arquivo diz sobre si próprio e criar um inventário de tudo o que ele poderá tentar fazer, para que um usuário possa decidir se este se enquadra no seu fluxo de trabalho
Este artigo aborda uma etapa de auditoria estática e de apenas leitura sobre essa superfície de risco utilizando o componente PDFium para Delphi e Lazarus. A auditoria nunca desenha uma página. Analisa a estrutura do documento, enumera as partes do arquivo que contêm comportamento e elabora um relatório simples. É a diferença entre pedir a um desconhecido para esvaziar os bolsos à porta e confiar nele porque sorriu
O que é e o que não é uma auditoria
Importa definir claramente as fronteiras. Uma pré-visualização em ambiente isolado (sandbox) renderiza um arquivo sob restrições rigorosas para que um usuário possa olhar para ele sem que o arquivo afete o resto da máquina. A auditoria ocorre antes disso. Trata-se de uma inspeção sem renderização cujo único resultado é uma descrição da superfície de ameaça: que scripts existem, quais as ações associadas às ligações, se o arquivo está assinado e com que rigor, e o que está anexado. Executa-a quando um documento cruza uma fronteira de confiança, como na receção por e-mail, num formulário de carregamento ou num fluxo de parceiros, antes de qualquer etapa posterior o abrir efetivamente
O componente carrega um documento da mesma forma para uma auditoria como para qualquer outra tarefa. Define o nome do arquivo e ativa-o, o que analisa os dados de referência cruzada (cross-reference) e o catálogo do documento sem renderizar uma única página. Tudo o que é descrito abaixo é lido 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 conter JavaScript ao nível do documento: scripts que não estão associados a nenhuma página ou campo específico, mas sim ao próprio documento, guardados na árvore /Names sob uma entrada /JavaScript. Um visualizador em conformidade executa-os ao abrir o arquivo. Este é o mecanismo subjacente a uma longa linhagem de malware em PDF, porque permite a um arquivo executar lógica no instante em que o usuário faz duplo clique, antes mesmo de ler uma única palavra
Um auditor procura dois fatos sobre cada um desses scripts: a sua existência e o seu conteúdo. O componente expõe a contagem e permite ler cada ação como um registro que contém o nome do script e o seu corpo completo. Ler o corpo é importante. Um script chamado Doc.0 não indica nada por si só, mas o seu texto pode chamar app.launchURL ou construir uma cadeia de texto e enviá-la para onde não deve. Extrair o código-fonte para que um revisor o possa analisar constitui o principal objetivo de sinalizar um arquivo que executa código ao ser aberto
Um arquivo com zero scripts de documento não é automaticamente seguro, dado que também existem scripts de página e de campo, mas um arquivo que contenha scripts ao nível do documento merece sempre atenção redobrada. A contagem de presença constitui por si só um filtro útil, e o corpo do script é o que transforma uma sinalização num veredito
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;Ações Launch e URI
O comportamento seguinte a inventariar reside em hiperligações e anotações. Há dois tipos de ações particularmente importantes para um auditor. Uma ação Launch inicia um programa externo ou abre um arquivo local quando a ligação é ativada. Uma ação URI abre um destino na web. Um revisor que analise um documento suspeito deve conseguir verificar, sem clicar em nada, se um botão na página três está configurado para iniciar cmd.exe ou para abrir um URL que não corresponda à marca exposta na página
O auditor lê a ação a partir da estrutura e regista-a. Nunca a executa
O controle do visualizador que renderiza os documentos é o local onde a execução de uma ação ocorreria, e a sua postura predefinida é deliberadamente cautelosa. O controle TPdfView possui um conjunto LinkOptions que define que tipos de ligação são ativados automaticamente ao clicar. A sua predefinição é [loAutoGoto, loAutoOpenURI], o que significa que saltos dentro do documento e URLs da web podem abrir-se, mas loAutoLaunch está ausente, pelo que as ações de lançamento nunca correm automaticamente. Para um fluxo de trabalho de auditoria, pode ir mais longe e limpar o conjunto por completo, para que absolutamente nada se ative de forma automática enquanto decide se deve confiar no arquivo
O raciocínio para reter a ação de lançamento por predefinição é simples. Um salto dentro do documento é inofensivo e um URL é visível e cancelável, mas iniciar um programa externo arbitrário a partir de um clique é a ação mais perigosa que uma ligação PDF pode solicitar, pelo que está desativada a menos que opte por a ativar. Um auditor rejeita mesmo os comportamentos seguros, porque o seu papel é analisar, e não interagir
// 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 nível de permissão MDP da assinatura digital
As assinaturas alteram a perspetiva. Uma assinatura simples atesta os bytes no momento em que foi efetuada. Uma assinatura de certificação, do tipo criado com uma regra de deteção e prevenção de modificação de documentos (MDP - Modification Detection and Prevention), vai mais longe: declara o que pode legitimamente sofrer alterações após a certificação do documento, e um visualizador em conformidade emite avisos se algo fora dessa tolerância tiver sido modificado. Ler esse nível de permissão indica a um auditor se o arquivo está certificado e, em caso afirmativo, qual o nível de restrição previsto para o mesmo
A permissão MDP é um inteiro com três valores definidos. O nível 1 significa que não são permitidas quaisquer alterações; qualquer modificação quebra a certificação. O nível 2 permite o preenchimento de formulários e a assinatura, que é o caso comum para um contrato que deve ser preenchido e assinado, mas não alterado noutros aspetos. O nível 3 permite adicionalmente anotações a par do preenchimento de formulários e assinatura. Conhecer o nível permite à sua lógica de admissão avaliar a intenção: um documento certificado no nível 1 que, no entanto, contenha campos de formulário ou scripts está a entrar em contradição, valendo a pena sinalizar essa incoerência
O componente lê a contagem de assinaturas e expõe cada uma como um registro cujo campo Permission transporta esse valor MDP, preenchido diretamente a partir da chamada subjacente FPDFSignatureObj_GetDocMDPPermission. Uma permissão igual a zero indica que a assinatura não é de certificação (DocMDP), pelo que não há restrições ao nível do documento a reportar
Uma auditoria não valida aqui a criptografia da assinatura; a verificação da cadeia de certificados é um aspeto independente. O que ela reporta é a intenção declarada: este arquivo indica que foi bloqueado neste nível. Trata-se precisamente do contexto de que um revisor necessita para avaliar se alterações posteriores, ou a mera presença de conteúdo ativo, são coerentes com a forma como o autor selou o documento
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;O resto da superfície: arquivos incorporados e XFA
Os arquivos incorporados são documentos completos contidos dentro do PDF como anexos, constituindo um veículo de propagação clássico, pois um relatório com aparência inofensiva pode transportar um executável ou um segundo PDF malicioso na sua árvore de anexos. O componente expõe a contagem de anexos e o nome de cada um, para que a auditoria possa listar os elementos associados sem necessidade de extrair ou abrir nenhum deles
A presença de XFA constitui a outra sinalização. Um formulário XFA substitui o AcroForm estático por uma arquitetura de formulários baseada em XML que introduz o seu próprio modelo de renderização e scripts, apresentando uma superfície maior e mais complexa do que um formulário simples. Não necessita de processar o XFA para constatar que está presente; a sua mera existência sinaliza que o arquivo possui uma camada interativa mais rica que justifica uma análise atenta. O componente reporta-o sob a forma de 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 de apenas leitura que elabora um relatório
Reunindo as peças, a auditoria resume-se a um único procedimento que carrega um documento, enumera os seus scripts e respetivos corpos, lista os destinos Launch e URI, reporta o nível MDP da assinatura, identifica anexos e XFA, e regista os resultados num log. Não renderiza nada, pelo que é pouco exigente em processamento e não pode ser enganado para apresentar conteúdos de página hostis. O resultado é um registro plano de leitura simples que um revisor ou uma regra automatizada podem processar
O modelo com bons resultados na prática consiste em recolher cada conclusão numa linha, aplicar um prefixo às que acarretam riscos reais para que surjam no topo de uma fila de revisão, e guardar toda a informação junto ao arquivo. Um documento sem scripts, sem ações de lançamento, sem anexos, sem XFA e sem assinatura (ou com uma certificação coerente) passa sem alertas. Um documento que ative diversas sinalizações simultaneamente constitui o caso que um usuário deve avaliar antes de qualquer fase posterior o abrir. A auditoria não toma a decisão de confiança por si. Garante sim que essa decisão é informada e não cega
Assim que um arquivo passa na auditoria e se torna necessário analisá-lo, faça-o sob restrições e não num visualizador predefinido. A abordagem no nosso guia prático sobre a criação de uma pré-visualização segura de PDF em Delphi mostra como impedir a ativação automática de ligações e de conteúdos ativos durante uma análise controlada. Para integrar esta enumeração num fluxo de admissão completo com ferramentas de revisão, consulte o artigo workbench de revisão e admissão de PDF. Ambos assentam no mesmo princípio de apenas leitura e sem renderização, sendo disponibilizados como parte do PDFium Component para Delphi e C++Builder, a par das APIs de renderização, texto, formulário e assinatura abordadas noutros locais deste blogue