Segurança em aplicações Web: XSS

Por Sérgio Lopes em 25/11/08

Bons tempos aqueles quando os computadores não eram ligados em rede, não existia Internet e as aplicações eram todas Desktop. Em tempos de Web 2.0, Mashups e Aplicativos Web, as dores de cabeça são absurdamente maiores assim como o número de aplicações expostas a ataques. Seu sistema é seguro?

Ultimamente tenho notado como poucas pessoas se preocupam de verdade com segurança na web, ou pior, como os desenvolvedores desconhecem os problemas envolvidos na programação pra web. É preciso entender como o HTTP funciona, como os browsers funcionam, o que o JavaScript é capaz de fazer e até as implicações de segurança dos ingênuos HTML e CSS.

Pensando nisso, resolvi escrever uma série de artigos sobre segurança em aplicações Web e começo falando do famoso Cross-site Scripting, carinhosamente chamado de XSS.

Samy is my hero

Em 2005, Samy Kankar, insatisfeito com sua pequena quantidade de amigos no MySpace, escreveu um worm (depois batizado de JS.Spacehero) para turbinar sua lista de amigos.

O script rodava quando alguém simplesmente visitava o perfil de Samy, adicionava o visitante à sua lista de amigos e colocava no perfil do infectado a frase “samy is my hero“. Além disso, o script se propagava para o perfil do usuário infectado, possibilitando que esse infectasse mais pessoas.

Em menos de 20h, Samy tinha um milhão de amigos e o MySpace foi tirado do ar. A aventura ingênua de Samy (com a falha que ele descobriu, poderia ter feito algo bem pior) foi narrada de forma muito cômica no site dele. E, em 2007, lhe rendeu uma condenação nos tribunais com direito à multa, serviço comunitário e afastamento de computadores.

Mas o que o bravo Samy descobriu?

Não confie nos inputs de seus usuários

O MySpace é conhecido pela papagaiada permitida nos perfis dos usuários. Diferente do Orkut, por exemplo, o dono do perfil do MySpace pode mudar praticamente todas as características visuais do site (fundo, cores, letras, imagens, vídeos etc). Para isso, o MySpace aceita que o usuário digite HTML e CSS para personalizar sua página, mas com limites para a coisa não virar zona.

O que Samy fez? Descobriu uma falha no filtro do MySpace que limita os inputs e conseguiu colocar em seu perfil um código JavaScript que seria executado por cada visitante do seu perfil. Com técnicas de Ajax e um pouco de tempo livre, Samy fez um JavaScript que executava as ações descritas acima.

XSS - Cross-site scripting

Um ataque XSS é aquele que permite a injeção de scripts no site atacado, em geral por meio de algum campo de input do usuário. Note que um ataque XSS é um ataque ao usuário do site e não ao sistema servidor em si. Isso porque o script injetado nunca será executado no servidor, mas sim nos navegadores dos clientes que visitarem a página infectada.

A regra de ouro de qualquer sistema com input de usuários é que não devemos confiar jamais naquilo que o usuário digitou. Além de XSS, podemos ser alvos de muitos outros ataques, como SQL Injection, injeção de parâmetros e outros (quem sabe abordamos num artigo futuro).

Embora pareça simples, a OWASP (Open Web Application Security Project) diz que nada menos que 9 em cada 10 sites estão vulneráveis a esse tipo de ataque. Anualmente, eles fazem um estudo das 10 falhas mais encontradas na Web, e no ano passado, XSS foi o campeão.

Como se proteger

Todo input do usuário deve ser sanitizado antes de qualquer coisa. Isso significa passar algum tipo de filtro que consiga remover tags potencialmente perigosas.

Se o seu site não deve ser customizado pelo usuário de nenhuma forma e o input dele deveria ser texto puro, basta remover toda e qualquer tag encontrada. Mas o caso mais complicado (aqui entra o MySpace) é quando você deseja permitir certas tags (negrito, por exemplo) e qual abordagem seguir para filtrar as tags não desejadas.

É considerado, hoje, total insanidade mental e causa da maioria das vulnerabilidades de XSS, tentar escrever esse filtro de input sozinho. Ele é extremamente complexo. Pense no caso simples, aquele onde não desejamos aceitar nenhuma tag. 99% das pessoas pensariam que o código abaixo faz um excelente trabalho:
String limpo = input.replaceAll("<", "").replaceAll(">“, “”);

É um código Java que transforma:
<script>alert('XSS');</script>

…nisso (claramente inofensivo):
scriptalert('XSS');/script

E o código abaixo então?
+ADw-script+AD4-alert('XSS')+ADw-/script+AD4-

Esse código é executado por qualquer navegador atual usando codificação UTF-7. E obviamente aqueles nossos “replaceAll” não resolvem o problema. Há centenas de outros exemplos que usam encodings estranhos, html entities, carateres especiais no meio etc.

E isso com o objetivo de tirar todas as tags da frente. E quando queremos que algumas sejam aceitas? Quais tags são seguras? E os atributos nessas tags? Até um <img> com atributo src apenas está vulnerável se não tomarmos cuidado.

Moral da história: fazer os replacezinhos na mão é insano. Não fazer nada a respeito é suicídio.

Arrumando o problema

Precisamos sanitizar o input dos usuários e precisamos de algo pronto que faça isso. Há trocentos projetos por aí que fazem esse tipo de serviço. Em especial, o AntiSamy do pessoal da OWASP que tem versões em Java e .Net. A Microsoft tem uma API anti-XSS para .Net. Há ainda o HtmlPurifier para PHP, o sanitize do Ruby on Rails e muitos outros em outras plataformas.

O AntiSamy é bastante customizável, permitindo que definamos níveis diferentes de purificação através de regras em um XML. Ele ainda é capaz de mostrar mensagens de erros amigáveis para os usuários. Usá-lo é bem simples:


AntiSamy as = new AntiSamy();
CleanResults cr = as.scan(suspeito);
String limpo = cr. getCleanHTML()

É considerada boa prática também purificar a saída dessas strings, por precaução. Com JSP, isso é muito fácil:

<c:out value="${suspeito}" escapeXml="true" />

Conclusão

XSS é um problema real e muitas aplicações estão vulneráveis. Samy atacou o MySpace em 2005 e conseguiu 1 milhão de amigos. A busca do Google tinha as falhas do UTF-7 comentadas acima em 2005. Apoiadores do Firefox sequestraram as maiores comunidades do Orkut em 2005 e mudaram os logos para o do Firefox. Eleitores de Hillary Clinton redirecionavam visitantes do site de Barack Obama para o site de sua adversária nas prévias da eleição americana em abril desse ano. E milhares de outros casos públicos.

Proteja sua aplicação!

Referências

Mirror DSL: facilitando o uso da API de reflection

Por Jonas Abreu em 17/11/08

No último domingo foi feito o primeiro release público do projeto Mirror (versão 1.2).

O Mirror é um projeto que tem por objetivo facilitar o uso da Java Reflection API, removendo boa parte da burocracia (como as diversas checked exceptions que são lançadas) e utilizando uma DSL para melhorar a legibilidade do código.

Com essa remoção de burocracia e a DSL, é possível transformar o seguinte código:

Field toSet = null;
for (Field f : target.getClass().getDeclaredFields()) {
    if (f.getName().equals("field")) {
        toSet = f;
    }
}
if (toSet != null && ((toSet.getModifiers() & Modifier.STATIC== 0)
        && ((toSet.getModifiers() & Modifier.FINAL== 0)) {
    toSet.setAccessible(true);
    toSet.set(target, value);
}

em algo mais legível e expressivo:

Mirror.on(target).set().field("fieldName").withValue(value);

Atualmente o Mirror possui suporte para lidar com as operações reflectivas mais comuns (como instanciar objetos, invocar métodos, ler ou escrever atributos, etc). Ele foi desenvolvido por Adriano Almeida, Diego Feitosa e eu, todos consultores/instrutores aqui da Caelum, enquanto enfretavamos problemas comuns no dia a dia.

Esperamos que possa ser útil para vocês também!

Integração Contínua

Por Cauê Guerra em 04/11/08

“Integração Contínua é uma pratica de desenvolvimento de software onde os membros de um time integram seu trabalho frequentemente, geralmente cada pessoa integra pelo menos diariamente - podendo haver multiplas integrações por dia. Cada integração é verificada por um build automatizado (incluindo testes) para detectar erros de integração o mais rápido possível. Muitos times acham que essa abordagem leva a uma significante redução nos problemas de integração e permite que um time desenvolva software coeso mais rapidamente.” Martin Fowler

Integração Contínua tornou-se muito importante na comunidade de desenvolvimento de software e isso provavelmente ocorreu devido ao grande impacto causado pelas metodologias ágeis. Em equipes que adotaram tais metodologias (eXtreme Programming, Scrum, entre outras), integração contínua é um dos pilares da agilidade, garantindo que todo o sistema funcione a cada build de forma coesa, mesmo que sua equipe seja grande e diversas partes do código estejam sendo alteradas ao mesmo tempo.

Mas porque fazer Integração Contínua ? Quais os benefícios que isso pode trazer ?

Basicamente, a grande vantagem da integração contínua está no feedback instantâneo. Isso funciona da seguinte forma: a cada commit no repositório, o build é feito automáticamente, com todos os testes sendo executados de forma automática e falhas sendo detectadas. Se algum commit não compilar ou quebrar qualquer um dos testes, a equipe toma conhecimento instantâneamente (através de email, por exemplo, indicando as falhas e o commit causador das mesmas). A equipe pode então corrigir o problema o mais rápido possível, o que é fundamental para não introduzir erros ao criar novas funcionalidades, refatorar, etc. Integração contínua é mais uma forma de trazer segurança em relação a mudanças: você pode fazer modificações sem medo, pois será avisado caso algo saia do esperado.

Mas porque eu não rodo pessoalmente os testes na minha máquina e só então faço o commit?
Simples: seu projeto pode ser tão grande, que os testes (em especial os de aceite) demoram um tempo considerável para serem executados e você não vai querer esperar todo esse tempo a cada commit pra poder continuar a trabalhar. Nesse caso, o recomendado é rodar os testes que envolvem as partes que você modificou e só então commitar, deixando para o servidor de integração contínua o trabalho de realizar todos os testes do sistema e garantir que tudo esteja funcionando. Além disso, não estamos falando apenas de testes, estamos falando de builds completos: a cada commit temos uma versão que teoricamente está pronta para entrar em produção, e isso pode envolver a realização de tarefas que não faríamos se estivessemos só testando, como por exemplo gerar um arquivo .war. O projeto pode ainda ser implantado automaticamente num servidor de desenvolvimento/homologação, e então com isso a cada commit temos o projeto rodando na web instantaneamente refletindo nossas mudanças!

Um outro exemplo interessante é o processo de geração das apostilas dos nossos cursos: a cada commit feito no repositório, o servidor faz o check-out, executa o Tubaina, que transforma o fonte em latex, convertendo o latex gerado em pdf e por fim copiando esse pdf num diretório interno, pronto para impressão na gráfica. Dessa maneira, garantimos que disponibilizamos para nossos alunos o material mais atualizado disponível.
Para tudo isso funcionar, no entanto, é preciso agarrar a idéia de commits pequenos. Fica mais fácil saber onde foi introduzido um erro quando o build quebrar se houveram pequenas mudanças, do que ter de verificar as ultimas 50 classes alteradas no ultimo commit. Outro ponto importante, é garantir ao menos um build limpo, com todos os testes passando, ao final de cada dia. Assim, teremos software pronto para entrar em produção tão cedo seja necessário.

Em outras palavras, podemos descrever Integração Contínua como integração automática com processo de build automático e que roda testes de forma automática e automaticamente detecta falhas em cada pedaço.

Abaixo as ferramentas que usamos aqui na Caelum:

  • CruiseControl.rb: desenvolvida pela ThoughtWorks, é a aplicação de integração contínua. Capaz de constantemente verificar os repositórios em busca de novos commits, fazendo check-out e rodando tarefas pré-determinadas. O interessante dessa ferramenta é que ela trabalha com qualquer tipo de projeto: ruby, java, ou qualquer outro cujo build possa ser feito através da linha de comando. Além disso, tem sua interface extremamente simples e funcional :).
CruiseControl.rb

CruiseControl.rb

CruiseControl.rb build report

CruiseControl.rb build report

  • CCMenu, ou CCTray: permite acompanhar o build de cada projeto sem ter de entrar no site do CruiseControl.rb. Isso é feito através de um ícone alertando quais projetos estão com o build quebrado.
  • Selenium: também desenvolvida pela ThoughWorks, provê maneira de realizar testes de integração em projetos Web, simulando a navegação do usuário pelas páginas do site, realizando diversas ações como clicar o mouse, prencher campos, etc… Usado para validar se o resultado de determinadas ações do usuario correspondem às que esperamos.
  • Xvfb: Servidor X11 que permite realizar todas as operações gráficas em memória, não exibindo nada na tela. Com essa ferramenta, é possível rodas os testes com Selenium sem ter as janelas do Firefox pulando na tela do nosso servidor a cada teste. Além disso, podemos rodar os testes mesmo em ambientes sem X instalado (como é o caso de servidores, por ex).

Ainda usamos plugins Maven e Ant para a geração de relatórios de cobertura e testes, que publicamos a cada novo build. Assim, conseguimos além de garantir que o código está todo integrado, garantir que está com uma cobertura adequada; afinal, de que adianta ter o sistema sem testes falhando, se ele possui menos testes que o necessário?

JUnit Report

JUnit Report

Cobertura Report

Cobertura Report

Por fim, pela experiência que temos no uso dessas ferramentas no desenvolvimento de aplicações internas, open source e em nossos clientes, podemos afirmar que elas de fato nos trazem uma produtividade antes inalcançável. Se você pensa em começar a utilizar alguma ferramentas de integracão contínua, vale a pena dar uma olhada nessas que indicamos. No entanto, existem várias outras disponíveis, e é questão de escolher a que melhor se adapta às suas necessidades. Não deixe de comparilhar suas experiências…