Proteção CSRF no CodeIgniter 2

24 de setembro de 2012, em Tira Dúvidas, Tutoriais, por

Proteção CSRF no CodeIgniter 2

No artigo sobre o lançamento do CodeIgniter 2 um leitor pediu maiores explicações sobre a nova proteção CSRF no CodeIgniter 2. Certamente, esta pode ser a dúvida de mais pessoas, então, acredito que cabe um artigo falando mais a respeito desta nova feature do CI 2 que veio para aumentar ainda mais o nível de segurança do framework PHP.

Este artigo é altamente inspirado no post “CSRF Protection in CodeIgniter 2.0: A closer look“, do blog de Bastian Heist.

CSRF é uma das principais vulnerabilidades em web sites e aplicações web, em geral. Como anunciado, o CodeIgniter 2 vem com uma proteção nativa para CSRF.

Segundo o OWASP Top 10 for 2010, que analisa os top 10 dos riscos mais críticos de segurança em aplicações web, o CSRF ocupa o quinto lugar, uma posição que teve durante mais de quatro anos. Os desenvolvedores geralmente tendem a subestimar o problema ou não sabem como implementar uma proteção eficaz.

O que é CSRF?

O que é o CSRF e como ele funciona? CSRF, também conhecido como XSRF, é a sigla para “Cross Site Request Forgery“. Um ataque CSRF força o navegador de uma vítima que esteja logada a algum lugar a enviar uma requisição falsa para uma aplicação web vulnerável, que realiza a ação desejada em nome da vítima. Isso permite que o invasor forçe o navegador da vítima a gerar pedidos e a aplicação os aceita como se fossem pedidos legítimos.

Então, o que é necessário para um ataque CSRF é:

  • Um site ou aplicação web vulnerável
  • Uma vítima que esteja logada ao site-alvo

Vamos assumir que o site www.exemplo.com é vulneráveil e permite aos usuários comprar produtos de sua loja online. Por razões didáticas, vamos usar parâmetros GET para demonstrar o problema, no entanto POST é afetado igualmente. A URL que um usuário logado tem que visitar para comprar um produto é o seguinte:

http://www.exemplo.com/comprar.php?idproduto=x&qtde=y

Ao chamar essa URL, submetemos os parâmetros “idproduto” e “qtde” com valores X e Y para o script. O script utiliza esses parâmetros para processar o pedido. Um atacante pode agora fazer um navegador da vítima enviar um pedido para este site. Isso pode ser feito simplesmente colocando uma tag de imagem em um site que ele controla:

<img src="http://www.example.com/comprar.php?idproduto=2&qtde=1000" />

O navegador da vítima não sabe que a URL de referência não é uma imagem, apenas envia uma solicitação HTTP para a URL indicada para recuperar o que quer que seja gerado pelo script. E aqui está o truque: como a vítima está logado em exemplo.com, o navegador envia toda a sessão da vítima e dados de autorização com a solicitação! A vítima, sem saber, enviou um pedido para comprar 1000 unidades do produto para exemple.com, e o site não tem idéia de que o pedido é ilegítimo… O pedido será executado!

Princípios de proteção contra CSRF

Então, como podemos proteger um site contra os ataques CSRF? O princípio subjacente é fácil. Um ataque CSRF é baseada no fato de que o site atacado não tem como saber se os dados que recebe realmente vieram de um formulário do próprio site, então, o que precisamos é uma forma de conectar as duas solicitações HTTP (formulário de solicitação e envio de formulário) de modo a obter essa informação. Podemos, então, certificamo-nos de que os dados que recebemos foram realmente inserido por um usuário em nosso site.

Existem várias maneiras de fazer isso. O mais comum, o usado pelo CodeIgniter, inclui um campo oculto em cada formulário no site. Este campo é oculto chamado token CSRF. O token CSRF é um valor aleatório que muda a cada solicitação HTTP enviada. Tão logo ele seja inserido nos formulários do site, ele também é salvo na sessão do usuário. Quando o formulário é submetido, o site verifica se o token salvo na sessão é igual ao que está no site. Se assim for, o pedido é legítimo.

O token muda cada vez que uma página é solicitada, o que significa que um invasor teria que “adivinhar” o token atual para realizar com êxito um ataque CSRF.

Proteção contra CSRF no CodeIgniter 2

Para começar, abra o arquivo de configuração do CodeIgniter (/application/config/config.php) e habilite a proteção CSRF (na versão atual, fica na linha 294):

$config['csrf_protection'] = TRUE;

A Security Class gera, automaticamente, um valor único para o token CSRF para cada solicitação HTTP. Quando o objeto é criado, o nome e o valor do token são definidos.

// Append application specific cookie prefix to token name
$this->csrf_cookie_name = $this->csrf_token_name;
// Set the CSRF hash
$this->_csrf_set_hash();

Em /system/libraries/Security.php, é possível ver o que a função realmente faz:

function _csrf_set_hash()
{
    if ($this->csrf_hash == '')
    {
        // If the cookie exists we will use it's value.  We don't
        // necessarily want to regenerate it with
        // each page load since a page could contain embedded
        // sub-pages causing this feature to fail
        if ( isset($_COOKIE[$this->csrf_cookie_name] ) AND
          $_COOKIE[$this->csrf_cookie_name] != '' )
        {
            $this->csrf_hash = $_COOKIE[$this->csrf_cookie_name];
        } else {
            $this->csrf_hash = md5(uniqid(rand(), TRUE));
        }
    }
    return $this->csrf_hash;
}

A função verifica primeiro a existência do cookie. Se ele existir, o seu valor atual é usado. O motivo também é o código: se a security class é instanciada várias vezes, cada pedido seria substituído pelo anterior. Isso faria com que apenas o último valor gerado fosse usado. Isso pode acontecer quando várias subpáginas são usadas em uma página ou ao carregar um formulário via AJAX.

A função cria um valor hash disponível globalmente e o salva para uso futuro – o valor do token é gerado. Agora ele tem que ser inserido em cada formulário no site. Para fazer isso, a função form_open(), do Form Helper, é usada (/system/helpers/form_helper.php):

function form_open($action = '', $attributes = '', $hidden = array())
{
    [...]
    // CSRF
    if ($CI->config->item('csrf_protection') === TRUE)
    {
        $hidden[$CI->security->csrf_token_name] =
          $CI->security->csrf_hash;
    }
 
    if (is_array($hidden) AND count($hidden) > 0)
    {
        $form .= sprintf("\n%s",form_hidden($hidden));
    }
    return $form;
}

Esta função agora não só cria a tag form de abertura, mas, também, acrescenta automaticamente o token CSRF ao formulário. Isso significa que, a fim de implementar uma proteção CSRF completa, você sempre tem que usar form_open() para criar formulários. O token já está definido e é submetido automaticamente a cada formulário.

Depois da submissão, ele tem que ser verificado e comparado com o valor guardado na sessão. Isso é feito pela Input Class (/system/core/Input.php) durante o processamento de input:

// CSRF Protection check
if ($this->_enable_csrf == TRUE)
{
    $this->security->csrf_verify();
}

O método csrf_verify() da Security Class é chamado cada vez que um formulário é submetido. Aqui é onde o token atual é checado (/system/libraries/Security.php):

function csrf_verify()
{
    // If no POST data exists we will set the CSRF cookie
    if (count($_POST) == 0)
    {
        return $this->csrf_set_cookie();
    }
 
    // Do the tokens exist in both the _POST and _COOKIE arrays?
    if ( ! isset($_POST[$this->csrf_token_name]) OR
         ! isset($_COOKIE[$this->csrf_cookie_name]) )
    {
        $this->csrf_show_error();
    }
 
    // Do the tokens match?
    if ( $_POST[$this->csrf_token_name]
         != $_COOKIE[$this->csrf_cookie_name] )
    {
        $this->csrf_show_error();
    }
 
    // We kill this since we're done and we don't
    // want to polute the _POST array
    unset($_POST[$this->csrf_token_name]);
 
    // Re-generate CSRF Token and Cookie
    unset($_COOKIE[$this->csrf_cookie_name]);
    $this->_csrf_set_hash();
    $this->csrf_set_cookie();
 
    log_message('debug', "CSRF token verified ");
}

O método csrf_verify() método faz duas coisas: se nenhuma informação POST é recebida, o valor do token no cookie CSRF é definido; se dados via POST são recebidos, ele verifica se o valor do token apresentado corresponde ao valor na sessão – se este for o caso, o valor do token CSRF é apagado e gerado novamente para o próximo pedido, o pedido é reconhecido como legítimo e a interação pode recomeçar novamente.

Conclusão sobre proteção CSRF no CodeIgniter 2

Com esta melhoria de segurança, agora é realmente fácil de implementar proteção CSRF no CodeIgniter. Tudo o que você precisa fazer é definir o valor de configuração para TRUE e CodeIgniter faz o resto. O fato de que você pode continuar usando os métodos usuais e não tem que se lembrar de fazer mais nada fazem dessa implementação, realmente, algo muito inteligente e prático.

Se, por qualquer motivo, você ainda não estiver usando CodeIgniter 2, ainda é possível considerar copiar os métodos de proteção CSRF do código-fonte do CI 2 e atualizar a sua instalação 1.7.2 com ele (isso vai lhe dar um grande ganho em segurança).

E, com mais esta implementação de segurança, o CodeIgniter continua provando que é o melhor framework PHP!

PS: Para o caso de se usar AJAX + CodeIgniter, é provável que erros de verificação de token aconteçam. Segundo consta no fórum oficial do CodeIgniter, numa dúvida sobre AJAX e a proteção CSRF, é possível “contornar” isso usando o jQuery Cookie Plugin.

11 comentários em "Proteção CSRF no CodeIgniter 2"

gravatar

Marcelo Fabiano  em 1 de outubro de 2012

Esse post caiu do céu pra mim tava quero mto saber sobre está opção do CI !!!! mto bom este post nota 10 !!! Parabens Tárcio Zemel!

gravatar

Tárcio Zemel  em 4 de outubro de 2012

Eu é que agradeço pela audiência, Marcelo! Grande abraço!

gravatar

Richard Feliciano  em 5 de dezembro de 2012

Só uma dica, se estiver dando um post para seu controller, via ajax(com a jquery) e a proteção CSRF estiver ligada, provavelmente $this->input->post(); não vai funcionar, após tentar algumas vezes(e ganhar alguns cabelos brancos) descobri que só funciona se você usar $_POST[''].

gravatar

Tárcio Zemel  em 7 de dezembro de 2012

Tem uma técnica "especial" pra trabalhar com CSRF e AJAX, brevemente farei um post a respeito.

Att

gravatar

Andreus Timmm  em 27 de março de 2013

Parabéns Tárcio, ótimo artigo e iniciativa.

gravatar

Tárcio Zemel  em 27 de março de 2013

Obrigado, Andreus!

Lembrando que isso já vem tudo pronto com o CI, o artigo foi só explicando "os bastidores"! ;-)

gravatar

Erich Casagrande  em 22 de maio de 2013

Poxa muito bacana, curto ler sobre melhores práticas e segurança !
Post muito bom!

gravatar

Iria  em 2 de julho de 2013

E se não uso Helper uso html como faço para utilizar ?

gravatar

Luan  em 17 de outubro de 2014

E se não uso Helper uso html como faço para utilizar ?

gravatar

Stifler  em 14 de dezembro de 2015

Hey there,I don’t know if you’ll ever read it since it’s and old post, but I didn’t find any other way to contact you, so here it is:First, thnaks for this great library, I really like to work with codeigniter and this library is helping alot!Secondly, in the latest version I found a little bug in the method create_dbref , line 1409 (Mongo_db.php):return (array) MongoDBRef::get($this->db, $obj);But it should be something like this:return (array) MongoDBRef::create($collection, $id, $db);

gravatar

Carlos  em 4 de março de 2016

Parabéns pelo tutorial, show de bola.

Só não ficou claro para mim uma coisa.
Não somos obrigado a utilizar o form_open, podemos criar os campos separadamente, correto?

Outra coisa é o SUBMIT. vamos comparar utilizando a função "csrf_verify" que é comparada com o COOKIE, mas no caso estou utilizando SESSION, preciso então setar o valor na hora que o cara logar na session certo?

gravatar

eder campos  em 18 de março de 2016

qual a tecnica estou tendo problemas com ajax e ssl forbbiden

    Comente!