Olá meu grandes leitores que desejam tornar suas aplicações mais seguras! Dando seguimento a série de artigos sobre segurança, hoje falarei do CSRF.
Veja o primeiro artigo: Segurança com PHP I – SQL Injection
Veja o segundo artigo: Segurança com PHP II – PHP Injection
Veja o terceiro artigo: Segurança com PHP III – XSS
DEFINIÇÃO
CSRF (Cross Site Request Forgeries), tradução Falsificação de solicitação entre sites, é um tipo de exploração maliciosa de um website/aplicação pelo qual comandos não autorizados são transmitidos de um usuário que confia no website. Fonte: Wikipedia
Em outras palavras, é um ataque que “força” um usuário à executar ações indesejadas em uma aplicação web. Um CSRF bem aplicado, pode comprometer os dados e funcionamento do usuário caso o mesmo seja um usuário comum ao sistema, quiçá, se o mesmo for um usuário administrador ou root.
CENÁRIO
Vamos imaginar um portal muito visitado e que tenha muita repercussão em tudo o que publicado, como notícias, fotos, anunciantes e dentre outros. Existirá nele um painel administrativo protegido por uma sessão onde existe uma área para publicações. Vamos usar de exemplo de pedido para salvar uma notícia:
GET http://www.portalmuitochique.com.br/noticias/postar.php HTTP/1.1 ......... titulo=T%C3%ADtulo%20da%20not%C3%ADcia&corpo=Corpo%20da%20not%C3%ADcia&autor=Autor%20da%20Not%C3%ADcia
Traduzindo: titulo=Título da notícia&corpo=Corpo da notícia&autor=Autor da Notícia
Toda vez que for enviado um GET ou POST para este recurso(URL) e o usuário estiver autenticado na aplicação, o mesmo deve entender que é para ser publicado uma notícia no nosso portal.
FORMA DE ATAQUE
Nessa web de meu Deus, geralmente essas requisições GET ou POST são feitas por formulários, como o formulário abaixo:
<form action="http://www.portalmuitochique.com.br/noticias/postar.php" method="get"> <p> <label for="titulo">Título:</label> <input type="text" name="titulo" /> </p> <p> <label for="autor">Autor:</label> <input type="text" name="autor" /> </p> <p> <label for="corpo">Corpo:</label> <textarea name="corpo" cols="20" rows="10"></textarea> </p> <p><button type="submit">Salvar Notícia</button></p> </form>
TÁ, MAS ATÉ AGORA NÃO VI NENHUM PROBLEMA EM RELAÇÃO A SEGURANÇA.
Muita calma jovem guerreiro, você já pensou que esse mesmo formulário possa estar em outro servidor, nas mãos de qualquer outra pessoa? #OMG
Se atacante submeter esse formulário o sistema receberá o GET e irá salvar a notícia no portal.
OPA, ALTO LÁ! SE O ATACANTE NÃO ESTIVER AUTENTICADO, COMO É QUE SERÁ SALVO A NOTÍCIA NO PORTAL?
Simples, uma técnica de raro conhecimento e de alta tecnologia chamada: E-mail =)
Existe uma técnica muito conhecida que é incorporar uma imagem(<img>) de zero byte no corpo do e-mail. Como abaixo:
<img alt="" width="1" height="1" border="0" src="http://www.portalmuitochique.com.br/noticias/postar.php?titulo=T%C3%ADtulo%20da%20not%C3%ADcia&corpo=Corpo%20da%20not%C3%ADcia&autor=Autor%20da%20Not%C3%ADcia"/>
Se essa tag de imagem for incluída no e-mail, a vítima só verá uma caixinha indicando que o navegador não pode processar a imagem. No entanto, o navegador continua a enviar a solicitação para www.portalmuitochique.com.br sem qualquer indicação visual do processo.
É por isso que nos clientes de e-mail de hoje quando recebem uma imagem no corpo dos e-mails, sempre perguntam aos usuários se realmente desejam processar as imagens vindas do remetente, veja um exemplo do GMail:
MACACOS ME MORDAM, EU SEMPRE MANDO EXIBIR AS IMAGENS! E AGORA, COMO FAÇO PARA ME PREVENIR DISTO?
Existem várias técnicas para tratar esse tipo de ataque, ensinarei uma forma bem simples e direta. Vamos supor que o script para salvar a notícia (postar.php) seja este:
<?php session_start(); // ... Rotinas para verificar a autenticidade ... if(isset($_POST['titulo'])) { // Publicando a noticia $noticia = new Noticia(); $noticia->setTitulo($_POST['titulo']); $noticia->setAutor($_POST['autor']); $noticia->setCorpo($_POST['corpo']); $noticia->publicarNoticia(); }
Desta forma o atacante poderá utilizar aquela técnica da imagem de zero byte. Vamos refatorar este nosso script para evitar o ataque CSRF vamos fazer o seguinte:
- Gerar um token (ou hash) randômico, que fosse renovado em um determinado tempo;
- Colocar este token em uma variável de sessão;
- Colocar este token em um campo escondido(hidden) no seu formulário;
- Ao submeter seu formulário fazer a comparação do campo escondido com a variável de sessão;
Vamos primeiramente criar um simples script chamado gerar_token_csrf.php para gerar o token:
<?php if(!isset($_SESSION)) session_start(); if(!isset($_SESSION['csrf_token'])) $_SESSION['csrf_token'] = sha1(uniqid(rand(), true));
Entendido os passos, vamos ao código! Vamos fazer uma pequena modificação no nosso formulário:
<?php include 'gerar_token_csrf.php' ?> <form action="http://www.portalmuitochique.com.br/noticias/postar" method="post"> <input type="hidden" name="csrf_token" id="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>" /> <p> <label for="titulo">Título:</label> <input type="text" name="titulo" /> </p> <p> <label for="autor">Autor:</label> <input type="text" name="autor" /> </p> <p> <label for="corpo">Corpo:</label> <textarea name="corpo" cols="20" rows="10"></textarea> </p> <p><button type="submit">Salvar Notícia</button></p> </form>
E por último, mas não menos importante, a comparação dos 2 tokens. Vamos alterar o postar.php:
<?php include('gerar_token_csrf.php'); // ... Rotinas para verificar a autenticidade ... if(isset($_GET['csrf_token'])) { if($_GET['csrf_token'] != $_SESSION['csrf_token']) { echo "Tokens não validados!"; die(); } // Publicando a noticia $noticia = new Noticia(); $noticia->setTitulo($_POST['titulo']); $noticia->setAutor($_POST['autor']); $noticia->setCorpo($_POST['corpo']); $noticia->publicarNoticia(); }
Agora sim! Como sempre haverá uma chave grande, única e dinâmica o atacante até poderá enviar o mesmo GET ou POST com o parâmetro “csrf_token”, mas a probabilidade dele acertar esse token é quase zero!
Existem outras formas mais rigorosas para evitar este ataque, como por exemplo: antes de verificar os 2 tokens, poderia ser verificado também de onde veio a requisição.
Bom geralerinha, é isso! Mais um tipo de ataque desvendado.
Um forte abraço a todos e até a próxima!
Comentários