domingo, 7 de novembro de 2010

Cookie SQL Injection - Explorando uma falha de segurança na prática

Quando converso com desenvolvedores Web eu percebo que são poucos que têm conhecimento de programação segura para Web. A algum tempo atrás eu trabalhava com Java e tinha/tenho muita preocupação com a questão de segurança. Cheguei até a tentar lecionar um curso de programação segura para Web em Java na uCon Security Conference, porém devido a falta de interesse o curso acabou não acontecendo. Acredito que conhecer, no mínimo, as formas de ataques mais básicas é fundamental para qualquer desenvolvedor Web. Um bom lugar para começar é conhecendo o projeto o Open Web Application Security Project (OWASP). Lá vocês podem encontrar uma vasta gama de informações sobre segurança na Web. Um sub-projeto interessante do OWASP é o que lista as 10 vulnerabilidades mais encontradas na Web.

Como mencionei no post anterior, o código em C que postei para melhorar a performance no acesso de aplicações Django usando o NGINX e Memcached tem uma falha de segurança que permite ataques de SQL Injection. Inicialmente eu pensei em realizar a correção no código e depois postar no blog. Mas depois me ocorreu que eu poderia mostrar como explorar esta falha. Achei que isso poderia ser bastante interessante para vocês verem como funciona esse tipo de ataque.

Bem, olhando o código escrito em C talvez não seja tão evidente a existência da falha. Pois normalmente os ataques de SQL Injection teem uma forma de entrada do código malicioso e saída da informação recolhida dentro do sistema. Normalmente esse problema é encontrado em aplicações Web onde é verificada a informação retornada em algum campo que é apresentado dentro do código HTML. Mas as falhas e aplicações se comportam totalmente diferentes em sistemas diferentes. A criatividade deve entrar em ação para tentar descobrir como interferir no sistema e obter uma resposta com sucesso. Ao analisar o comportamento da aplicação em funcionamento aparentemente não encontramos nenhuma forma de obter um retorno sobre qual o dado que está armazenado. O que conhecemos é o comportamento do sistema. Quando um visitante acessa a aplicação ele pode obter uma resposta da aplicação Web em Django ou do código em C onde existe a evidência de falha. Então sabemos que nossa atenção deve ser em cima de como fazer a requisição ir para o código vulnerável. Assim, lembrando do post anterior, todas as requisições passam pelo código em C onde é feita uma consulta a base de dados pelos dados existentes na sessão do usuário que no Django (e na maioria, se não, todos os tipos de aplicações web) armazena o Session Key no cookie do browser do usuário. O código realiza uma consulta a base de dados sobre a sessão e verifica o tamanho desses dados, caso seja maior que 60 caracteres (que foi o tamanho máximo identificado por mim para uma sessão de usuário não autenticado) a requisição é repassada para o Django e é entendido que o usuário está autenticado. Caso contrário, o usuário não está autenticado e a requisição é retornada a partir dos dados existentes no Memcached. Analisando esse fluxo, podemos perceber que a chave para o ataque é identificar como saber se a requisição vem do código em C ou do Django. E usar isso para obter uma resposta, já que este SQL executado (e que contém a vulnerabilidade) só é usado para isso. Assim procurei e achei a diferença entre as respostas do Django e do código vulnerável. Quando a resposta vem do Django, existe uma variável que aparece no cabeçalho da resposta a requisição HTTP. Ao descobrir isso, eu já sabia quando uma requisição vinha do Django e quando vinha do código em C e sabia que se o resultado retornado depois da execução do código SQL se fosse maior que 60 ele ia passar pra o Django e caso fosse menor ele mesmo iria responder. Assim, eu agora precisava só descobri como usar isso para obter informações sobre o sistema, no caso, informações importantes armazenadas na base de dados.

Primeiro eu precisava escolher que informação eu iria tentar obter, dentro do contexto teria que ser algo que estivesse no banco de dados da aplicação. Eu sei que toda aplicação Django com autenticação tem uma tabela chamada auth_user onde ficam armazenados os usuários, então escolhi atacar essa tabela e obter informações sobre os usuários da aplicação.

Bem, depois de descobrir onde deveria atacar eu precisei imaginar (usar a criatividade para) pensar em como fazer a resposta da execução do SQL ser maior que 60 caso a condição que eu verificasse fosse verdadeira ou falsa. Assim eu poderia usar a informação que a resposta veio do Django ou não para validar o que eu estaria perguntando ao banco de dados.

OFF-Topic: Resolvi ir tomar um banho e ficar pensando sobre como fazer isso. Minhas idéias fluem quando estou tomando banho e é lá onde eu tenho muitas ideias interessantes. Acho que quase todas que já postei ou vou postar neste blog surgiram quando eu estava tomando banho.

No final de todo o processo de pensar testar a avaliar o funcionamento eu escrevi este código. A ideia dele é fazer uma requisição a aplicação usando cada letra do alfabeto ou caractere válido para o campo que eu queria e fazer com que a resposta vá para o Django sempre que eu achar o caractere certo. Assim eu teria que iterar caractere por caractere para descobrir um-por-um. E foi isso que eu fiz.



Este código varre todas as possibilidades sobre cada caractere e vai validando cada entrada. No final podemos obter o nome de um usuário que é staff do sistema. Este mesmo código, sem muitas modificações pode ser usado para obter muitas outras informações. Ai vale a criatividade de cada um. Isso é apenas um exemplo simples.

A criatividade é um fator fundamental a qualquer pessoa que deseja realizar um ataque. Pois cada ataque requer um cuidado ou ação bem especifica. Cada sistema tem um funcionamento e foi implementado de uma forma totalmente diferente. Por isso que dificilmente um ataque que obteve sucesso sobre um sistema vai obter o mesmo sucesso sobre outro. O importante na questão de segurança é entender o fundamento do ataque e não decorar como foi feito. O fato da forma de atacar cada sistema ser diferente leva a que poucas pessoas realizem ou tenham sucesso ao tentar realizar esse tipo de atividade.

Espero que tenham gostado do post e ainda tenho muitas ideias que não postei aqui. Muita coisa ainda está por vir. A interação de vocês por meio de comentários é muito importante para minha motivação a continuar postando coisas aqui. Por isso, escrevam suas opniões, criticas ou elogios tudo isso é sempre bem-vindo.

sexta-feira, 5 de novembro de 2010

Deployment de Django usando NGINX, Memcached e um pouco de código em C

Depois do post anterior eu começei a pensar em como eu poderia melhorar ainda mais a performance da nossa aplicação Django. Então tive a ideia de fazer cache das páginas estáticas e servi-las direto do memcached para o usuário. Isso funcionou muito bem, mas existe um problema quando essa mesma ideia é aplicada a uma aplicação onde os usuários realizam login e precisam acessar páginas personalizadas com coisas do tipo "Olá {{ nome_do_usuario }}, bem-vindo!". E pensei bastante sobre isso e deixei pra resolver este problema depois. Resolvi atacar inicialmente os visitantes externos, que não são usuários do sistema consequentemente que não estão autenticados no sistema. Neste grupo de visitantes se encontram também os engines de pesquisa como Google, Yahoo!, Bing e etc. E também os navegantes de primeira viagem que chegaram no site. Assim, quanto melhor a experiência que estes visitantes tiverem no primeiro acesso ao site maior as chances deles voltares ou se cadastrarem para usar o sistema. A velocidade de carregamento das páginas do site é um dos criterios de qualidade que estes visitantes analisam para decidirem se voltam ou não. Inclusive o Google recentemente anunciou que vai usar/usa o tempo de carregamento como critério no seu algoritmo de PageRank, ou seja, mais um motivo para motivar a desbravar esta ideia.

Para deixar o acesso dos visitantes não autenticados mais rápido não precisamos pensar muito, se você leu o post anterior e fez um teste simples deve ter percebido os ganhos em velocidade que foram extremamente absurdos. Assim, precisamos enviar esses visitantes não autenticados para o memcached diretamente sem que a requisição passe pela stack do Python / Django. Dessa forma desafogamos um pouco (ou muito) nossa aplicação pois ela só vai processar requisições que são realmente interessantes e que precisam de um processamento mais dinâmico. Se pararmos pra pensar, percebemos que os visitantes não precisam acessar as páginas com dados atualizando em tempo real (depende muito da aplicação, mas de um modo geral isso é verdade). Por exemplo, se nossa aplicação tem um forúm os visitantes do site não precisam ver o forúm se atualizando a cada acesso ou a cada segundo que ele visita o site. Já os usuários autenticados, precisam e devem ver o forúm atualizado o mais rápido possível para que eles possam interagir com os outros usuários do site. E essa ideia é aplicada a todas as páginas externas do site pois nem os engines de pesquisa ficam acessando o site a cada milisegundo pra ver o que mudou. Resumindo, os visitantes do site não precisam ver as coisas atualizadas eles provávelmente estão entrando no site pela primeira vez e tudo pra eles é novidade.

Agora que decidimos que precisamos fazer cache de tudo que é acessado por usuários não autenticados ficou fácil, não é? Não, como vamos saber quando o usuário está autenticado ou não? No código Python / Django podemos descobrir isso fácil mas como descobrir isso antes da requisição chegar ao Python? Precisamos validar se o usuário está autenticado ou não o mais rápido possível e enviar a resposta pra ele. O ideal é descobrir isso diretamente no NGINX, mas não tem uma forma de fazer isso. Inicialmente eu pensei em validar a existência do cookie do Django nas requisições e se ele existisse o usuário estaria autenticado. Porém isso não funciona pois o Django cria um cookie de sessão mesmo se o usuário não for autenticado. Sendo assim, o que fazer? Essa foi minha dúvida e decidi estudar o código do Django nessa parte de autenticação e gerenciamento de sessão para saber o que eu preciso fazer pra descobrir se o usuário está autenticado ou não. Depois de fazer isso, resumindo, cheguei a conclusão que não dava pra deserializar o objeto pickler que o Django salva no BD pra descobrir se o usuário está autenticado ou não. Então, analisando o comportamento do cookie do Django eu percebi que dava pra fazer uma validação simples, que não dá 100% de acerto sobre a questão do usuário estar autenticado ou não, mas já é uma boa (do meu ponto de vista). Eu descobri que posso validar o tamanho dos dados da sessão, não dá pra deserializar o objeto da sessão, mas dá pra validar o tamanho da string salva no BD. E é assim que eu faço a validação pra saber se o usuário está autenticado. Se os dados da sessão forem maior que um limite mínimo, quer dizer que o usuário está autenticado. No meu caso isso funciona pra mim porquê eu não salvo nada na sessão quando o usuário não está autenticado (é importante essa informação). Se você salva algo na sessão do usuário em páginas públicas da sua aplicação esse esquema, do jeito que eu faço agora, pode não funcionar pra você.

Resolvido esses problemas, eu tentei resolver o problema de acessar o banco de dados para pegar os dados que estão armazenados na sessão do usuário e validar se ele está autenticado antes da requisição ir para o Django. Essa validação tinha que ser feita no próprio NGINX ou em alguma coisa bastante rápida pra conseguir obter essa resposta. Então procurei várias alternativas. Primeiro eu pensei em usar node.js, mas cai no problema que eu ainda não conheço a tecnologia direito e então isso poderia ser um problema. Tentei procurar outra coisa, um modulo para o NGINX, talvez. Até achei um módulo que dá pra usar código em Lua no NGINX, mas eu também tinha que aprender Lua e não me pareceu necessário nesse momento também. Tentei achar uma outra solução e foi ai que achei um post em um blog mostrando um exemplo de consulta ao memcached usando código em C. Entao me ocorreu que eu poderia modificar esse código para atender as minhas necessidades. E foi isso que eu fiz. Código em C é o mais rápido que eu poderia conseguir (tá, eu sei... mas eu não sei Assembly tão bem assim, ainda).

Para explicar a ideia que eu tive, eu criei alguns diagramas de sequencia pra ficar mais fácil de entender. Abaixo eu mostro a primeira situação. Nesse caso eu mostro o que acontece quando o usuário acessa a aplicação e não está autenticado. Eu chamo o meu código em C de UserRouter, ele valida se o usuário está autenticado e se não estiver ele consulta no memcached a página que o usuário está tentando acessar e envia de volta para o NGINX no caso da página existir no memcached. Caso a página não exista ele envia a requisição para o Django normalmente.

arquitetura

No segundo cenário eu quero mostrar o que acontece quando um usuário autenticado acessa a página ou quando não existe uma página pública ainda em cache.

sem dados

A parte que coloca a página no memcached é opcional, pois se for uma página interna da aplicação ou uma página que apenas usuários autenticados acessam, ele nunca vai pra o memcached.
Então, para suportar esse funcionamento eu reusei o middleware do post anterior.

Adicionei algumas configurações ao settings.py:




E o código em C que faz toda essa mágica acontecer.





PS.: Bem, eu só vou avisando logo que esse código em C tem uma falha de SQL Injection que eu ainda vou corrigir (e atualizo aqui pra vocês).
Pra compilar esse código vocês teem que instalar algumas bibliotecas, mas essa parte é fácil se você usa o ubuntu.

rafaelcaricio@ubuntu:~/development$ sudo apt-get install libmysqlclient-dev libevent-dev libmemcached-dev

E pra compilar o código. A linha de comando é:

rafaelcaricio@ubuntu:~/development$ gcc -o v_auth_and_cache -levent -I/usr/include/mysql  -DBIG_JOINS=1  -fno-strict-aliasing   -DUNIV_LINUX -DUNIV_LINUX verify_auth_and_cache.c -Wl,-Bsymbolic-functions -rdynamic -L/usr/lib/mysql -lmysqlclient -lmemcached

Depois é só executar o servidor UserRouter colocando na porta 8000. Usando essa linha de comando:

rafaelcaricio@ubuntu:~/development$ ./v_auth_and_cache localhost 8000

E fazer algumas alterações na configuração no NGINX. Assim a requisição sempre vai primeiro pra o UserRouter e depois, se for o caso, passa para o Django.


No final eu realizei bastante testes e verifiquei que tudo isso realmente funciona e deixa o acesso a páginas externas extremamente rápido. Certo que eu ainda vou testar muito isso e melhorar esse funcionamento para poder usar no AtéPassar. Mas tenho certeza que isso vai melhorar bastante a velocidade de indexação pelos engines de busca e melhorar também a experiência dos usuários que estão acessando o site pela primeira vez. Eles vão ver tudo funcionando incrivelmente rápido. Espero ter ajudado ou aberto a mente de alguns de vocês para novas possibilidades. Em breve estarei escrevendo mais um post sobre outra ideia ou sobre alguma outra coisa que eu já fiz nas minhas horas vagas. Abraços e até logo.

Profiling de aplicações Django

Hoje tive que identificar um problema complicado no AtéPassar, uma rede social de estudantes de concursos públicos e OAB onde eu trabalho atualmente como líder técnico da equipe de desenvolvimento. A aplicação estava ficando lenta quando tinha muitos acessos simutâneos e não havia motivos aparentes para isso acontecer. Devido a esse problema tive que procurar uma forma de descobrir o que estava acontendo internamente no código da aplicação. Assim, lembrei que existe uma técnica de analise de programas que ecaixa totalmente com esta situação que é o profiling de software.

Pesquisei bastante na internet sobre como fazer isso em aplicações Django. Python tem um módulo especifico para isso, que é o hotshot. Assim acabei encontrando um projeto bem interessante que já existe uma integração com o django para rodar sobre o hotshot. Então ficou mais fácil de realizar essa tarefa de analisar o funcionamento do projeto. E para a visualização das chamadas eu utilizei o KCachegrind, um projeto OpenSource que permite visualizar os dados gerados por softwares de profiling.

Screenshot do KCachegrind


O gráfico gerado pelo KCachegrind mostra as funções do sistema e os caminhos de chamadas com o custo de cada chamada. Assim podemos visualizar e identificar qual o ponto especifico que está deixando o sistema lento.

Depois de utilizar essas ferramentas consegui identificar o problema que estava deixando o AtéPassar lento e agora estamos trabalhando bastante para resolver este gargalo. Acredito que a solução para este problema também irá gera um post interessate para este blog.

Melhoria de performance projetos Django usando Gunicorn, Nginx e Memcached

Estou estudando formas para a melhoria de performance de aplicações na web. Atualmente eu trabalho com Python/Django no desenvolvimento de aplicações web. Pesquisando na internet sobre como melhorar o tempo de respostas das requisições ao máximo encontrei vários artigos explicando diversas formas de fazer isso. Muitos desses artigos me chamaram a atenção, pois faz exatamente o que eu imaginava. O próprio Nginx se comunica com o memcached e verifica se existe, aquela determinada página que o usuário está requisitando, no cache e apenas se não tiver, é que a requisição é repassada para o stack do python/django.

 

Depois de visualizar esse funcionamento eu resolvi implementar alguns testes em uma simples aplicação rodando localmente para verificar este funcionamento e os ganhos em performance que isso pode proporcionar. Fiz uma compilação das ideias apresentadas pelos artigos que eu li sobre o assunto.

Instalando o software necessário

Começei instalando o memcached e colocando ele para rodar com 512mb de mémoria.

Pronto, depois disso podemos verificar se o memcached está rodando corretamente.

Agora o memcached está instalado e funcionando. Vamos instalar o nginx, é bem simples.

E também temos que instalar o suporte ao memcached no Python e instalar também o Gunicorn para rodar nossa aplicação.

Hora de verificar se o nginx está rodando.

Podemos ver que está acessando a url http://localhost/ e vendo a mensagem "Wellcome to nginx!".

Configurando para a aplicação rodar usando o gunicorn

Agora vamos configurar o nginx para acessar nossa aplicação que vamos colocar rodando sobre o gunicorn. Primeiro vamos configurar o nginx para enviar repassar as requisições.

No arquivo de configuração, eu coloquei:

 

Temos que deletar/desabilitar as configurações padrão do nginx, pois nem vamos usar para este caso. Eu preferi deletar o arquivo.

 

rafaelcaricio@ubuntu:~$ sudo rm /etc/nginx/sites-enabled/default 

 

 

Agora criamos um link para as configurações no diretório de sites-enabled do gunicorn.

rafaelcaricio@ubuntu:~$ sudo ln /etc/nginx/sites-available/easyproject.conf /etc/nginx/sites-enabled/easyproject.conf 

E reiniciamos o nginx para pegar as novas configurações.

 

rafaelcaricio@ubuntu:~$ sudo /etc/init.d/nginx reload

Reloading nginx configuration: the configuration file /etc/nginx/nginx.conf syntax is ok

configuration file /etc/nginx/nginx.conf test is successful

nginx.

Pronto, agora se acessarmos http://localhost vamos ver um erro 502 do nginx, isso acontece porque nossa aplicação não está rodando ainda. Vamos colocar nossa aplicação django para rodar agora. Para isso, precisamos criar a configuração do gunicorn pra rodar o projeto. Assim eu crio o arquivo gunicorn.conf.py dentro do meu projeto django.

Essas são as configurações básicas que fazem meu projeto executar corretamente. Agora podemos mandar executar o gunicorn com as configurações escolhidas.

rafaelcaricio@ubuntu:~/development/easyproject$ gunicorn_django -c gunicorn.conf.py settings.py

Pronto! Agora já podemos acessar nossa aplicação através do nginx que repassa as requisições para o gunicorn. Isso já deixa a nossa aplicação bem rápida. Porém esse é o básico que podemos fazer pra deixar tudo rodando. A minha ideia aqui é ir um pouco mais além e deixar as coisas funcionando mais rápido ainda. E para isso vou fazer um esquema de cache na aplicação django criando um middleware para salvar os resultados das requisições no memcached. E deixar o nginx perguntar ao memcached sobre a existência do resultado da requisição antes de repassar a requisição para o django.

Para isso acontecer eu modifiquei as configurações no nginx.

 

 

E também criei um novo middleware que foi adicionado ao meu projeto no django. E adicionei algumas opções ao meu settings.py. 

 

 

No settings.py eu adicionei as seguintes configurações:

 

 

Assim, todas as páginas que o django serve que sejam GET serão adicionadas ao memcached e o nginx vai pegar de lá o seu conteúdo. Assim as respostas ficarão bem mais rápidas.

Conclusão

Esta configuração é incrivelmente mais rápida, pois as requisições não vão direto para o django. O django só vai processar requisições POST, telas de erro e no caso de uma nova página ser acessada pela primeira vez. A ideia agora é pensar mais a frente, em como invalidar esse cache para que as informações mostradas aos usuários estejam sempre atualizadas. Essa tarefa não é muito complexa, apenas será uma coisa a mais que vai ter que ser feita quando houver modificações no banco de dados. Porém merece uma atenção e cuidado maior, para não ter grande impacto no processo de desenvolvimento. Vou analisar várias implementações e técnicas de invalidação de cache. Assim, no próximo post eu falarei mais sobre isso e demostrarei qual foi a solução que eu encontrei para isso.

 

Referências

http://kovyrin.net/2007/08/05/using-nginx-ssi-and-memcache-to-make-your-web-applications-faster/

http://amix.dk/blog/post/19414

http://jimmyg.org/blog/2009/ssi-memcached-nginx.html

http://www.willmcgugan.com/blog/tech/2009/3/1/fast-caching-with-django-and-nginx/

http://tabbedthinking.posterous.com/nginx-memcached-uwsgi-django

http://soyrex.com/articles/django-nginx-memcached.html

 

Acrescentando métodos ou atributos ao objeto User do Django

O Django é um framework para desenvolvimento web em python muito bem conceituado. Facilita o desenvolvimento web entre outras coisas que podem ser encontradas no site oficial do framework.
Estou usando ele para desenvolver um projeto relativamente grande, com muitas funcionalidades (atualmente em fase de desenvolvimento). Um dia desses tive que modificar o modelo de dados do sistema para adicionar mais um tipo de usuário. No inicio o sistema tinha apenas um tipo de perfil que era o de estudante, porém tive que adicionar o profile do professor no sistema. Quando um novo usuário vai entrar no sistema ele escolhe se é estudante ou professor, dependendo da escolha o sistema cria um tipo de profile diferente. Ai foi onde tive problema, pois o Django já é preparado para trabalhar com sistemas que utilizam profile, porém sistemas com apenas um profile (um tipo de usuário). Procurei bastante na internet sobre isso e acabei achando uma solução interessante, porém não me lembro mais qual foi o blog gringo que achei sobre isso (vou pesquisar e achando eu atualizo aqui). Adicionei este código no arquivo urls.py :

from django.contrib.auth.models import User

class UserGetProfileExtension:
    def _get_profile(self):
        if not hasattr(self, '_UserGetProfileExtension__cached_profile'):
            try:
                if hasattr(self, 'student'):
                    self.__cached_profile = self.student
                elif hasattr(self, 'professor'):
                    self.__cached_profile = self.professor
            except:
                self.__cached_profile = None
        else:
            self.__cached_profile = self._UserGetProfileExtension__cached_profile
        return self.__cached_profile
    profile = property(_get_profile)
User.__bases__ += ( UserGetProfileExtension, )

Agora para pegar o profile do usuário basta continuar usando:

user.profile

Espero que isso possa ajudar muita gente. Como as referências estão em inglês, este conteúdo em português é interessante. Até logo e depois vou colocando mais soluções interessantes.