JB

Cross-site scripting(XSS): The vulnerability created by your code when injecting text into the DOM

javascriptfrontendcross-site-scriptingsecurity
Sep 29, 2020

Translation In progress!

In this text, I intend to address a 5W2H format about this flaw, which, if forgotten, can have a great cost by allowing a security breach in a page, allowing malicious scripts to present, steal or modify user data. So, in addition to the text, dare to know, grab your coffee mug and proceed so that problems like this cease to exist.

Pixel art que fiz para representar o XSS (inspirada em space invaders)

XSS — Injeção de scripts maliciosos no DOM.

XSS é um tipo de vulnerabilidade, comumente, encontrada em aplicações web que possibilitam esse ataque malicioso ao injetarem um client-side script dentro da página que é vista pelos usuários.

Em outras palavras, você desenvolve algum input sem preocupar-se com tratar o texto que será colocado nele, dado isso, possibilita a inserção de um código JavaScript que será processado, podendo dar abertura em algumas formas de comprometer a segurança da página.

Embora não exista uma larga padronização dos tipos de XSS, existem 3 principais formas conhecidas, sendo elas:

  1. Persistente — Esse é o tipo mais devastador, pois acontece quando há possibilidade de os dados (scripts) do atacante serem salvos pelo servidor e após isso exibidos em páginas aparentemente “normais” para o usuário em sua navegação.
  2. Não persistente — Essa é a forma mais tradicional/comum, que, ocorre quando você utiliza algum dado que o usuário colocou no input, normalmente um campo de pesquisa, que sem tratamento algum passa pelo servidor e na página seguinte devolve o que foi digitado pelo usuário, esse tipo e o anterior permitem infinitas formas de colocar algo na página que instigue o usuário a clicar (como por exemplo enviá-lo a um site externo ou simplesmente solicitar dados).
  3. DOM cross-site — Em contraste com os anteriores, nesse ataque os scripts maliciosos sequer alcançam o servidor, isso dada a arquitetura cada vez mais amplamente disseminada de responsabilidades distintas de back-end e front-end. Mas, ainda sim, o scripts maliciosos conseguem agir e dessa vez no lado do Client, e permanecem sendo refletidos pelo código JavaScript tal como os XSS tradicionais. Nesse, um exemplo seria uma aplicação de To-Do List que não haja precauções, e o XSS é executado, inclusive seguirei com esse exemplo mais tarde no post, pois defendendo-se desse tipo, você previne-se de todos os outros. Hoje, alguns frameworks e bibliotecas JavaScript tem precauções contra XSS, mas as coisas ocorrem “por baixo dos panos”, então você fica sem saber o que realmente acontece.

Mensurando o mal com casos reais

Ao notar tudo isso eu ingenuamente imaginei:

uau, poderão dar console.log de “Hello World” pelo input de texto.

Por pensar que se eu consegui notar a facilidade da criação de templates para a inserção no input, provavelmente, outros desenvolvedores excepcionalmente experientes também já notaram, mas a realidade objetiva demonstra que esquecer do cuidado com XSS não define se você é bom ou ruim, apenas define que você esqueceu do cuidado com XSS. Tal como já ocorreu com o Orkut, Twitter e Ebay:

Após checar algumas matérias e pontos de vista sobre esses dois casos, a primeira coisa que pensei foi “Okay, mas e daí? isso ainda acontece em 2020 ou a comunidade de desenvolvimento web superou isso?”, e um participante do fórum que citei no início desse post me recordou do ilustre caso nacional e recente de

em que um hacker brasileiro utilizou de XSS para rodar Minecraft na página do STF (Simplesmente insano), e o mesmo atacante rodou o jogo

Com isso, conclui que conforme desenvolvedores front-end se aventuram em bibliotecas e frameworks, atingindo níveis mais elevados de abstrações, por vezes, costumam literalmente subtrair conceitos; e isso é compreensível, já que o foco se torna a produtividade, desempenho e um bom e/ou satisfatório resultado final dentro do prazo previsto. Mas na implementação de algum projeto pequeno que utilize Vanilla.Js ou simplesmente opte por algum framework ou biblioteca exótica na qual a implementação de uma solução para XSS precise ser “manual”, caso não ocorra a implementação, abrirá brecha para um perigo maior como já ocorreu nos casos citados.

Trazendo a sua interface para a luz

Aplicação de To-Do para testar XSS

Toda essa reflexão surgiu durante a criação de uma aplicação Web de To-Do List, que, transformei em um misto de testador XSS, recomendo que acesse e acompanhe revesando entre a leitura e testar scripts que possibilitam um funcionamento parecido com os XSS.

Inicialmente, recomendo que ative o modo XSS! Deve imaginar que para rodar um código JavaScript numa página HTML seja necessário utilizar da tag script, mas dado que o HTML 5 por padrão não executa uma tag script inserida via propriedade innerHTML, tudo resolvido? Ainda não. Pois, a utilização da tag script não é a única forma de se rodar JavaScript numa página HTML, podemos ter por exemplo:

  • <img src='x' onerror='alert("Site bastante divertido esse hein")'>
  • <img src='x' onerror='console.log("Hello World! Security are cool and vulnerability are bad :D")'>

Ao inserir esses dois textos acima na aplicação de To-Do, notará que essas duas instruções fazem rodar código JavaScript a partir de uma página HTML, para testar mais combinações acesse essa cheat-sheet de xss ou treine nesse jogo de xss, ambos encontrados em um google de distância.

Finalmente, vamos ao código!

Código Javascript que insere elementos no DOM via appendChild() com um dos aninhamentos utilizando innerHTML.

O código inicial que eu possuía para injetar um elemento no DOM, era algo parecido com isso, que pega o input do usuário, extraí o que ele digitou numa variável chamada value e depois aninha elementos para a inserção deles no DOM; a vulnerabilidade está presente na linha 12, pois sem haver tratamento da variável “value”, a propriedade innerHTML irá injetar no DOM quaisquer tags inseridas no campo de texto, podendo executar os exemplos simplórios de exibição de alerta que testamos e outros verdadeiramente graves. Após isso, eu tentei uma solução com a utilização do método insertAdjacentHTML(), que para um uso geral é muito mais sofisticado do que a propriedade já citada para o mesmo propósito (inserir HTML no DOM), mas que para esse caso, ainda sim injetará o script malicioso na página.

Código Javascript que insere elementos no DOM via appendChild() com o texto sendo inserido via textContent.

Algum tempo depois, tive a análise de que a solução era na verdade bem simples, bastava utilizar uma propriedade que tratava o texto inserido pelo usuário somente como texto. Disso, optei pela utilização da propriedade textContent para inserir o valor digitado pelo usuário em um elemento HTML e os aninhei com o método appendChild(), então pronto? resolvido? Não completamente. Para esse caso em específico, o problema foi resolvido, mas no fórum comentado anteriormente me demostraram outra forma de solucionar. Ela me fez notar que há um tradeoff na solução já demonstrada, que seria abdicar da utilização de templates (aninhamentos simples dos elementos) na construção de algo mais complexo e também, adicionaria linhas e baixa facilidade de leitura do código. Então, por motivos de escalabilidade irei apresentar uma solução melhor que a última.

Sanitização do texto

O mesmo usuário do fórum que havia me indicado casos recentes de XSS, também me recomendou a utilização de Regex para filtrar o que for enviado pelo input. Ele exemplificou de forma didática com o primeiro código abaixo, e depois encurtou a solução com algo parecido com o segundo código abaixo:

Código Javascript de Regex filtrando um texto para outra armazena-lo em outra variável.

Código Javascript de Regex filtrando um texto para outra armazena-lo em outra variável.

Essa regex encontra as tags presentes no input do usuário e as guarda na variável chamada “result”, e depois, extraí para uma nova variável chamada “valor” o conteúdo de cada match trocando o valor da tag por uma string vazia, então caso seja inserido:

<img src='x' onerror='alert("Site bastante divertido esse hein")'>Testeeeee</img>

Resultará em extrair:

[
  "<img src='x' onerror='alert("Site bastante divertido esse hein")'>",
  "</img>"
]

E o que será guardado na variável valor, será somente:

Testeeeee

Nessa solução é possível que continue utilizando templates com innerHTML e insertAdjacentHTML ao tratar o texto vindo de um input da página. E além de servir para scripts simples de alert, essa solução alcança situações mais complexas, ainda sim é uma execução simples e pode ser melhorada cada vez mais caso você tente entender melhor acerca disso.

Considerações Finais

Dado os casos apresentados e dos problemas que XSS podem causar numa aplicação e todo o circuito percorrido até algumas possíveis soluções simples deve imaginar que isso é apenas o começo e é possível ir muito mais além! Vá além produzindo um código de cada vez mais qualidade, boa sorte em sua jornada!

A minha jornada têm sido muito feliz, pois conheço uma pessoa chamada Viviane Queiroz, que me apoiou a voltar ao código e também me incentivou a escrever esse post, tenho grande admiração por ela e muito obrigado!

illustration searching a bug

Ainda sobre jornada, saiba que um ponto positivo na jornada de devs é estar interagindo numa comunidade de desenvolvedores e ter acesso a opiniões e soluções diferentes da sua de outros devs, dependendo de como você reage, isso pode lhe trazer um enorme crescimento pessoal, não estou dizendo que você precise ser amigo de todos os desenvolvedores, mas apenas não guarde algo que você queira compartilhar, pois notará que valerá a pena compartilhar, como dessa vez que um usuário num fórum me recordou do caso de XSS com Minecraft no STF e indicou uma solução com Regex.

A refatoração é uma constante.

Obrigado pela atenção!

Sapere Aude.