Prevenindo ataques de html injection

Em qualquer aplicação web que desenvolvemos é comum receber dados dos usuários, armazenar no servidor para depois exibi-los a outros usuários. Um CRUD simples, certo? Apesar de simples, uma implementação ingênua pode apresentar uma vulnerabilidade grave e muito explorada por usuários mal intencionados na web: o HTML injection.

Vamos supor que nossa aplicação é um fórum onde os usuários criam posts, cada post tem um título e um texto com a descrição. De maneira simplificada, nosso formulário poderia ser assim:

<form action="/posts" method="post">
<input type="text" name="titulo">
<textarea name="descricao"></textarea>
</form>

No servidor, poderíamos simplesmente receber o post e salva-lo no banco:

@Post("/posts")
public void salva(String titulo, String descricao) {
  postDAO.salva(new Post(titulo, descricao);
}

E depois exibir um post para outros usuários que visitassem o site, com a seguinte jsp:

<h1>${post.titulo}</h1>
<div>${post.descricao}</div>

Uma aplicação simples como essa já está sujeito a ataques de HTML injection. Se o usuário colocasse na descrição de seu post qualquer texto contendo código html como <script>alert("olá");</script>, esse código seria executado no navegador de todos os visitantes do seu post, possibilitando ataques de XSS. Por esse motivo, você nunca deve confiar em dados inseridos pelo seu usuário.

Primeira tentativa: escapando todos os dados

A primeira solução é simples. Usando jsp e a jstl, podemos simplesmente escapar os dados inseridos pelo usuário. Nossa jsp com o seguinte código:

<h1><c:out escapeXml="true" value="${post.titulo}" /></h1>
<div><c:out escapeXml="true" value="${post.descricao}" /></div>

Resolvido, certo? Caso o usuário inserisse a tag <script> ela seria enviada ao navegador como &lt;script&gt;, e portanto não seria interpretada como uma tag html.

Essa solução é suficiente para o título da pergunta, mas suponha agora que gostaríamos de permitir que o usuário pudesse formatar a descrição de seu post. Seria interessante que ele pudesse dar destaque a certos trechos de seu post deixando trechos em negrito ou monospaced, por exemplo (uma feature bastante comum em qualquer aplicação que apresenta conteúdo do usuário).

Nesse caso, apenas escapar todo o html não basta, precisamos de uma solução mais esperta, permitindo que certas tags sejam interpretadas pelo navegador.

Sanitizando o conteúdo

Poderíamos resolver o caso específico do <script>alert("olá")</script> em java fazendo simplesmente: descricao = descricao.replaceAll("<script>", "").replaceAll("</script>", ""). Porém, veja que essa solução é bastante inocente e o problema é mais complexo do que parece. Um usuário mal intencionado pode facilmente entender como sua aplicação está sanitizando os dados testando algumas vezes o seu formulário. Suponha então que ele insira:

<<script>script</script>>alert("olá de novo")</<script>script</script>>

Ao fazer as duas chamadas do replaceAll, o resultado é o seguinte: <script>alert("olá");</script>. Caímos de novo no mesmo problema. Podemos aprimorar nosso algoritmo ingênuo para solucionar esse caso, mas existe uma infinidade de corner cases que podemos esquecer. Além disso, uma solução robusta definitivamente não faz parte das prioridades da aplicação que estamos desenvolvendo.

Considerando a complexidade do problema, diversas bibliotecas foram desenvolvidas para sanitizar conteúdo a ser exibido na web. Uma das bibliotecas mais famosas e robustas é o OWASP-Java. O Sergio Lopes já havia escrito sobre ela nesse post, mas a API mudou bastante. Para utilizar é simples, você especifica quais tags você deseja liberar na entrada e ele constrói o sanitizador:

PolicyFactory policy = new HtmlPolicyBuilder()
 .allowElements("strong", "i")
 .disallowElements("script")
 .toFactory();

Assim, no código da nossa aplicação podemos utilizar o sanitizador na entrada do usuário:

@Post("/posts")
public void salva(String titulo, String descricao) {
  postDAO.salva(new Post(titulo, policy.sanitize(descricao));
}

Agora, na jsp podemos continuar escapando o título do post, já na descrição podemos exibir a descrição da forma como foi salva pelo nosso dao:

<h1><c:out escapeXml="true" value="${post.titulo}"/></h1>
<div>${post.descricao}</div>

Repare que podem haver diversos outros casos, outros pontos onde a injeção pode ser feita. XSS é um assunto que merece sua atenção.

28 Comentários

  1. Lucas Moreira 28/05/2013 at 13:08 #

    ja vi muito site por ai que nao aguentava html injections bem mais simples, kkkk.

    bom artigo.

  2. Juca 28/05/2013 at 14:06 #

    alert(“olá”)

  3. Juca 28/05/2013 at 14:07 #

    <script>alert(“olá de novo”)</script>

  4. André Valenti 28/05/2013 at 14:09 #

    Aí vai uma sugestão de tradução melhor para sanitize: sanear.

    Estou aproveitando e testando se este blog está saneando as entradas corretamente :). []s!

    alert(‘Aopa!’);

  5. André Valenti 28/05/2013 at 14:09 #

    Ó, funfou mesmo, hehe… Aplicou o <b> e tirou fora o <script>.

  6. Alessandro 28/05/2013 at 14:11 #

    Boa!

    O Jsoup também oferece uma boa alternativa (um pouco mais leve) ao AntiSamy da OWASP. Usando Jsoup com o Hibernate Validator (JSR-303) você pode também usar a Annotation @SafeHtml nos campos do modelo, muito útil!

    Ale

  7. Roberto Silva 28/05/2013 at 14:12 #

    Assunto novo para mim, desconhecia este risco.
    Foi muito bem abordado, parabéns!

  8. Roberto Nogueira 28/05/2013 at 17:27 #

    Tem que proteger principalmente a entrada, ou seja, antes de chegar ao banco. Pois é muito provável que o desenvolvedor irá esquecer de algum scape em todas saídas das propriedades no site.

  9. Rafael Jesus 29/05/2013 at 09:35 #

    Caraca não conhecia esse ataque, muito bom artigo

  10. Denis Santos 03/06/2013 at 11:19 #

    Legal, simples de implementar, e é algo necessário!

  11. Flavio Nunes 03/06/2013 at 13:04 #

    Não Conhecia também. Muito bem editado.

  12. hehe 03/06/2013 at 13:11 #

    <script>alert(“olá de novo”)</script>

    Muito bom o artigo.

  13. Francisco Sokol 03/06/2013 at 13:25 #

    Obrigado pelos elogios, pessoal!

    André, obrigado pela sugestão! Traduzir termos de tecnologia em inglês é sempre complicado e polêmico.

    Alessandro, não conhecia jsoup, obrigado! Seria interessante se a anotação permitisse configurar quais as tags html são permitidas, será que ela é capaz disso? Não encontrei nada na documentação.

    Algo do tipo:
    @SafeHtml(allow={“a”, “strong”, “b”})
    private String descricao;

  14. teste 03/06/2013 at 14:28 #

    alert(“olá”);

  15. teste 03/06/2013 at 16:04 #

    +ADw-script+AD4-alert(‘XSS’)+ADw-/script+AD4-

  16. Paulo Silveira 03/06/2013 at 16:21 #

    o mais divertido aqui é o pessoal tentando fazer HTML injection por aqui, sendo que nem conseguem perceber que é um wordpress.

  17. Leandro Capuano 03/06/2013 at 16:32 #

    Esta pauta é um prato cheio pra mim, pois eu preciso validar a minha aplicação web (sem o cross script), e com estas técnicas de como evitar cross, esta perfeito, o engraçado é que eu comecei a brincar de injectar script na minha web app e elas ficam chamando script da outra web app e entramos no loop infinito, muito interessante esta brincadeira. Mais uma vez, obrigado ao pessoal da Caelum pela prevenção do cross script, o meu proximo cachorrinho vai ser chamar CrossScript. Aloha!

  18. aaaa bbb 06/06/2013 at 09:21 #

    alert(“olá de novo”)

  19. Antonio 06/06/2013 at 09:29 #

    A anotação permite alguns tipos de “whitelist”: BASIC, RELAXED, SIMPLE_TEXT: http://docs.jboss.org/hibernate/validator/4.2/api/org/hibernate/validator/constraints/SafeHtml.html

  20. Jonas Alessi 11/06/2013 at 09:25 #

    Ótimo post Francisco, poderia me ajudar ? Estou tentando criar uma policy onde irei permitir o ” ” mas não estou conseguindo.

  21. Francisco Sokol 11/06/2013 at 18:50 #

    Oi Jonas poste sua dúvida junto com o código no GUJ e eu (e outras pessoas) poderão te ajudar: http://www.guj.com.br/perguntas

  22. Josué 13/06/2013 at 23:25 #

    Valeu pela dica.

  23. Henrique 15/06/2013 at 09:05 #

    Muito bom a dica, e com relação aos fundamentos do desenvolvimento seguro boas práticas fundamentadas pela OWASP. Parabéns pelo artigo!

  24. Lincoln 22/06/2013 at 12:10 #

    Muito bom. A parte de segurança de uma aplicação deve ser sempre lembrada, algo que muitos deixam de lado.

  25. Daniel 02/07/2013 at 17:43 #

    Muito Legal a dica.Seria adequado colocar a aplicação da política dentro de um filtro??

    Parabéns.

  26. Tiago 18/07/2013 at 17:16 #

    Legal

  27. ricardo johannsen 02/10/2013 at 17:49 #

    Muito bom o artigo, seria uma boa uma serie de tutoriais usando a mesma didática prática deste aqui falando das top 10 vulnerabilidades listadas pela OWASP e como se proteger delas e também sobre problemas com os autobinding dos frameworks modernos como o Vraptor e o SpringMVC iria ficar show.

  28. msm 20/03/2015 at 12:02 #

    i use but it has error
    java.lang.NoClassDefFoundError: Could not initialize class org.owasp.html.HtmlPolicyBuilder
    what i do?

Deixe uma resposta