Pré-visualizar um PDF não confiável dentro da sua própria aplicação é uma decisão de execução, e a parte que importa não é o visual do visualizador, mas o que o painel se recusa a fazer de forma autónoma. Não grave o arquivo no disco. Não permita que as suas ligações executem comandos no sistema. Não atribua um caminho aos seus anexos. A maior parte dos danos causados por um documento hostil não provém de uma vulnerabilidade no motor, mas sim de um visualizador a realizar tarefas perfeitamente normais com dados fornecidos pelo atacante: abrir uma ligação file:// para uma partilha UNC que expõe credenciais NTLM, deixar uma cópia temporária no diretório de arquivos temporários ou copiar anexos integrados para onde quer que uma string de nome de arquivo determine. O PDFium Component é um visualizador de PDF em código-fonte para Delphi, C++Builder e Lazarus, e coloca as opções de controle relevantes ao seu alcance: um sinalizador em tempo de carregamento que desativa scripts, eventos de clique em ligações que pode vetar, acesso a anexos que corre através do seu próprio código e sinalizadores de permissões que pode ler. O percurso abaixo acompanha um documento desde o momento em que chega até ao momento em que um usuário clica em algo contido nele
O modelo de ameaças de um painel de pré-visualização
Seja realista sobre o que uma "pré-visualização segura" lhe garante. O renderizador analisa bytes não confiáveis independentemente do que faça, e a robustez do próprio motor constitui a base na qual se apoia. Tudo o que está acima dessa base é política da aplicação: se os scripts inicializam, o que um clique numa ligação faz, se os arquivos embutidos podem alcançar o disco, se a área de transferência (clipboard) e a impressora representam portas ou paredes. Uma funcionalidade a descartar desde cedo é a opção FPDF_SetSandBoxPolicy do motor. A maioria das restrições do motor é definida durante a compilação, a opção altera muito pouco na prática e basear qualquer parte do seu isolamento nela apenas produz uma falsa sensação de segurança. Quando a entrada é genuinamente hostil (por exemplo, num portal público de carregamento de arquivos), o único isolamento real consiste em renderizar num processo separado com baixos privilégios e enviar mapas de bits para a interface de usuário. Os sinalizadores em processo constituem políticas. Não constituem isolamento
Duas áreas são fáceis de esquecer precisamente porque nenhum clique lhes toca. A primeira são os arquivos temporários. Se o seu fluxo de trabalho guarda os documentos recebidos no disco antes da pré-visualização, essas cópias sobrevivem à sessão a menos que algo as elimine de forma verificável, e um arquivo que seja "recuperável a partir do diretório temporário" anula silenciosamente qualquer controle que o próprio painel imponha. Em vez disso, carregue a partir da memória através de TPdfStreamAdapter, para que os bytes hostis nunca obtenham um caminho próprio no sistema. A segunda é a área de transferência. Uma pré-visualização que permita selecionar e copiar já exportou o documento, um tela de cada vez, e nenhuma intersecção de ligações conseguirá evitar isso
Desativar JavaScript em tempo de carregamento e não na interface
O JavaScript do documento no PDFium Component inicializa apenas em conjunto com o ambiente de preenchimento de formulários. Carregar o documento com FormFill := False desativa assim a execução de scripts na raiz, em vez de suprimir os seus sintomas:
O compromisso é real e deve constar na sua especificação. Com o preenchimento de formulários desativado, a interação legítima com AcroForms e os scripts de validação também deixam de estar disponíveis; os campos são desenhados com a última aparência guardada, mas não podem ser editados. Para um painel de pré-visualização, essa é normalmente a decisão correta, uma vez que pré-visualizar significa ler e não preencher. Mas se a mesma janela duplicar a sua função como superfície de preenchimento para documentos internos confiáveis, a solução passa por ter dois caminhos de carregamento com uma decisão de confiança explícita entre eles, e não um caminho único com uma configuração intermédia demasiado permissiva para o caso hostil e demasiado restritiva para o caso confiável. O lado do preenchimento de formulários dessa divisão tem as suas próprias armadilhas, abordadas no artigo sobre navegação em campos de formulário e regeneração de aparência
procedure TPreviewPane.LoadUntrusted(const FilePath: string);
begin
Pdf.FileName := FilePath;
Pdf.FormFill := False; // no form environment, hence no JavaScript engine
Pdf.Active := True;
FPermissions := Pdf.Permissions; // raw flag word; all bits set = unrestricted
end;Hiperligações: o processador padrão executa comandos no sistema
Sem intervenção, os cliques em hiperligações são enviados diretamente para o sistema operativo. As opções predefinidas de ligações do visualizador (LinkOptions) incluem loAutoOpenURI, que constitui o cenário ideal para uma fuga de file:// para partilhas UNC. Dois eventos formam o ponto de controle: OnWebLinkClick para URLs detetados no texto da página, e OnAnnotationLinkClick para anotações de ligação que transportam ações de URI ou de execução. Defina Handled := True em ambos, incondicionalmente, antes de tomar qualquer decisão, e depois permita novamente apenas o que a política autorizar. Como segunda camada de segurança, remova loAutoOpenURI de LinkOptions para entradas não confiáveis e certifique-se de que a opção loAutoLaunch (desativada por padrão) nunca é ativada por engano através de uma configuração copiada:
Dois detalhes determinam a eficácia desta medida. Primeiro, a verificação do esquema tem de ser uma validação do prefixo na string bruta antes de qualquer processamento, porque endereços file://, caminhos UNC e esquemas invulgares são precisamente os valores que falham num analisador de URL simples ou passam por um que normalize as strings de forma demasiado agressiva. Segundo, registe cada bloqueio associando a identidade do documento. Um pequeno número de ligações file:// bloqueadas constitui ruído de fundo; um pico de ocorrências em muitos documentos recebidos num curto espaço de tempo representa um incidente que a sua equipa de segurança gostaria de conhecer
procedure TPreviewPane.PdfViewWebLinkClick(Sender: TObject;
const Url: WString; var Handled: Boolean);
begin
Handled := True; // never fall through to the default shell behavior
if (AnsiStartsText('https://', Url) or AnsiStartsText('http://', Url))
and HostIsAllowed(Url) then
OpenInBrowser(Url)
else
FAudit.LogBlockedLink(FDocumentId, Url);
end;Anexos: política de extensões e o nome de arquivo que não escolheu
Um PDF é um contentor, e a propriedade AttachmentCount com AttachmentName[] indica o que este transporta antes de qualquer elemento tocar o disco. Dois controles separados são importantes neste ponto, e apenas um deles é evidente. O evidente é a política de tipos: uma lista de permissões (allowlist) de extensões que podem ser exportadas. O subtil reside no fato de o nome do anexo constituir informação controlada pelo atacante. Um nome embutido como ..\..\Startup\update.exe transforma uma gravação descuidada numa travessia de caminho (path traversal) que coloca um executável numa pasta que o Windows executa ao iniciar a sessão. O componente disponibiliza o conteúdo como bytes através de Attachment[] e permite que o seu código escolha o caminho de destino, pelo que deve construir esse caminho a partir de um nome de arquivo higienizado e nunca a partir da string embutida original:
Privilegie a abordagem de lista de permissões. Uma lista de bloqueio de extensões "perigosas" é uma corrida que perderá no dia em que alguém utilizar uma extensão de que nunca ouviu falar; uma lista de permissões com .pdf, .png e .csv falha por omissão de forma segura
procedure TPreviewPane.ExportAttachment(Index: Integer; const TargetDir: string);
var
RawName, SafeName, Ext: string;
Data: TBytes;
begin
RawName := string(Pdf.AttachmentName[Index]);
SafeName := ExtractFileName(RawName); // strips any path components
Ext := LowerCase(ExtractFileExt(SafeName));
if not FAllowedExt.Contains(Ext) then // allowlist, not blocklist
raise EPreviewPolicy.CreateFmt('Attachment type %s blocked by policy', [Ext]);
Data := Pdf.Attachment[Index]; // embedded payload as raw bytes
TFile.WriteAllBytes(
IncludeTrailingPathDelimiter(TargetDir) + SafeName, Data);
end;O que as permissões de encriptação realmente garantem
O processador de segurança padrão da norma ISO 32000-1 codifica sinalizadores de permissão para impressão, cópia de conteúdo e modificação, e as propriedades Permissions e UserPermissions expõem-nas como máscaras de bits brutas após a abertura do documento. A Tabela 22 da norma ISO 32000-1 define os bits, e um arquivo não encriptado reporta todos os bits ativos. Leia-os e respeite-os na sua camada de comandos, mas compreenda a sua natureza. Para um documento encriptado com uma palavra-passe de proprietário e uma palavra-passe de usuário vazia, o conteúdo é totalmente desencriptado ao abrir, e os sinalizadores constituem um pedido aos visualizadores em conformidade e não um mecanismo de imposição rígido. Isto tem duas consequências opostas. Nunca apresente sinalizadores de permissão aos usuários como uma propriedade de segurança dos documentos que recebem, porque não o são. Ao mesmo tempo, respeite o bit de extração para acessibilidade (bit 10) mesmo onde a cópia geral (bit 5) esteja desativada; o acesso para leitores de tela é salvaguardado separadamente no modelo de permissões, e removê-lo porque "a cópia está desativada" prejudica a tecnologia de apoio sem qualquer ganho de segurança
Imponha restrições de ações negadas ao nível dos comandos e não ocultando botões da barra de ferramentas. O atalho Ctrl+C, os menus de contexto e a seleção por arrastamento contornam a barra de ferramentas; uma verificação de permissões dentro do comando de cópia não é contornável
Para documentos que exijam uma palavra-passe de usuário, atribua `Password` antes de `Active := True` e trate o valor como o segredo que é: obtenha-o do seu repositório de credenciais por sessão, mantenha-o fora de registros e relatórios de falhas, e nunca o armazene junto ao documento. Um painel de pré-visualização que guarda palavras-passe "por conveniência" transforma-se silenciosamente numa base de dados de palavras-passe sem as respetivas proteções
A impressão merece uma decisão própria em vez de herdar a regra da cópia de texto. Uma cópia física impressa é, por definição, não auditada, mas bloquear a impressão tende a levar os usuários a tirar capturas de tela, o que é pior em todos os aspetos. Um meio-termo comum consiste em permitir a impressão mas marcar cada página com a identidade do usuário e uma marca de tempo, imposta dentro do comando de impressão. Compreenda apenas a sua finalidade: uma marca de água é dissuasão e atribuição. Não é prevenção
O que a triagem já deveria ter indicado
Um painel de pré-visualização toma melhores decisões quando o arquivo chega com uma análise já associada: se está encriptado, se tem JavaScript, um inventário de anexos e o tipo de formulário. Essa passagem de inspeção pertence a um ponto anterior ao visualizador, e o modelo descrito em como construir um painel de receção de PDF produz precisamente os sinalizadores que uma política de pré-visualização pretende consumir. Os arquivos que a triagem marcou como de risco abrem automaticamente pelo caminho protegido; os documentos rotineiros mantêm as suas conveniências. Associe as duas fases a um único objeto de política partilhado em vez de dois telas de configuração, que divergiriam logo na segunda versão
A fronteira entre processamento dentro do processo (in-process) e fora do processo (out-of-process) depende de quem lhe envia os arquivos. Para a receção normal de negócios, os remetentes são conhecidos e apenas descuidados, e a pré-visualização in-process com scripts desativados e ligações intercetadas constitui um nível de segurança razoável. Para carregamentos públicos anónimos isso não se aplica, e nenhum sinalizador in-process o resolverá; renderize esses arquivos num processo separado com baixos privilégios e envie mapas de bits para a interface, para que uma falha no motor comprometa apenas um processo isolado e não a aplicação principal
O licenciamento, as APIs relacionadas com segurança e uma demonstração de visualizador protegido encontram-se na página do produto: PDFium Component