PHP - Roubo de Sessão
Previna-se contra um dos ataques mais comum às sessões
Escrito por Tiago Souza em
PHP. Data: 24/06/2007
Licença: Alguns direitos reservados. Dar créditos ao autor e linkar este original
Ver tópico original no fórum.
O roubo de sessões é, sem sombras de dúvidas, o tipo de ataque mais comum às sessões. Da mesma maneira da fixação de sessão, se você utiliza somente o session_start(), você está vulnerável, apesar deste exploit não ser tão simples.
Ao invés de focar em como evitar que a identificação da sessão seja capturada, vou focar em como evitar que a captura da id da sessão traga problemas. O objetivo deste artigo é complicar ao máximo a personificação, lembrando que cada complicação que aplicamos ao nosso código aumenta a segurança e frusta o atacante. Para fazer isso, vamos ver os passos necessários para roubar uma sessão. Em cada um dos casos, vamos assumir que a identificação da sessão foi comprometida.
Com um mecanismo simples de sessão, apenas uma identificação da sessão que seja válida é necessária. Neste caso, vamos melhorar nosso código adicionando mais verificações através das requisições HTTP.
Lembre-se: Não é recomendável utilizar dados do nível do protocolo TCP/IP (como o endereço IP), pois este protocolo não foi feito para acomodar atividades no nível HTTP. Um único usuário pode ter diversos endereços IP's a cada requisição, assim como múltiplos usuários podem ter o mesmo IP (um proxy, por exemplo).
Vamos lembrar uma típica requisição HTTP:
CODE
GET / HTTP/1.1
Host: uidroot.com
User-Agent: Mozilla/5.0 Gecko/20061010 Firefox/2.0
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234
Apenas o cabeçalho Host é requerido pelo HTTP/1.1, logo, não é ideal confiar nos outros dados. Entretanto, a única coisa que precisamos é checar a consistência, porque queremos complicar a personificação sem afetar os usuários legítimos, não é mesmo?!
Vamos imaginar que depois da requisição passada, vêm a seguinte requisição, como um User-Agent diferente:
CODE
GET / HTTP/1.1
Host: uidroot.com
User-Agent: Mozilla Compatible (MSIE 7)
Accept: text/xml, image/png, image/jpeg, image/gif, */*
Cookie: PHPSESSID=1234
Veja que o cookie é o mesmo, mas devemos considerar esta requisição como vindo do mesmo usuário?!
É MUITO improvável que o usuário mudaria o User-Agent entre requisições, certo?! Vamos modificar nosso mecanismo de sessão para fazer uma nova verificação:
CODE
<?php
session_start();
if (isset($_SESSION['HTTP_USER_AGENT']))
{
if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
{
// O user_agent da requisição antiga não bate com o atual
// Neste ponto, é importante fazer novamente a autenticação do usuário
// pedindo seu login/senha
exit;
}
}
else
{
$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}
?>
O código acima obriga o atacante a usar não apenas uma identificação de sessão válida, mas também o user-agent correto. Isto complica um pouco a vida do atacante e deixa a sessão um pouco mais segura.
Será que podemos melhorar o código?! Leve em consideração que o método mais comum de obter os cookies é explorando falhas em um browser vulnerável, como o Internet Explorer. Esses exploits geralmente envolvem em redirecionar a vítima ao site do atacante, logo, o atacante saberá qual o user-agent da vítima e poderá obtê-lo corretamente e passar pela nossa verificação de user-agent.
Bom, imaginem se, além do md5 do user-agent, poderíamos passar mais alguns pedaços de informação. Isto dificultaria e muito o nosso potencial atacante. Temos que manter uma boa lógica para evitar que usuários legítimos sejam afetados por verificações que sejam feitas de maneira incorreta. Nosso objetivo é diminuir problemas e não aumentá-los. Vamos escrever o seguinte código:
Página identifica.php
CODE
<?php
session_start();
// Retira as variáveis da sessão.
$_SESSION = array();
//Destrói o cookie da sessão
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time()-42000, '/');
}
// Destrói a sessão
session_destroy();
//Inicia uma nova sessão
session_start();
$codigo = $_SERVER['HTTP_USER_AGENT'];
$codigo .= 'É com você Adeildo';
$digital = md5($codigo);
echo '<form action="consulta.php?chk=' . $digital . '" method="post">';
//Seu formulario de autenticação
?>
Página consulta.php
CODE
<?php
//Existe $_GET['chk']?
if(!isset($_GET['chk'])){
header("Location:identifica.php");
die();
}
$codigo = $_SERVER['HTTP_USER_AGENT'];
$codigo .= 'É com você Adeildo';
$digital = md5($codigo);
//O $_GET['chk'] bate com a id digital?
if ($digital != $_GET['chk']){
header("Location:identifica.php");
die();
}
//sessão é válida
session_start();
//Processa o resto do código
?>
Este código funciona assim:
O usuário normal faz login na página identifica.php e vai para consulta.php
O atacante, de posse da id da sessão e user_agent, acessa direto a página consulta.php, mas ele (assim esperamos) não tem o $_GET['chk'], ou seja, a identificação virtual gerada unicamente na página de login identifica.php. O código adicional "É com você Adeildo" não deve ser divulgado, pois ele garante que o nosso md5() gerado seja único. Em outras palavras, se o atacante tentar passar só o md5 do user-agent, ele não conseguirá acesso, o md5 deve ser do user-agent + $codigo secreto e não apenas do user-agent.
Existem ainda, muitas outras maneiras de melhorar o sistema de sessões, mas isso depende da sua criatividade e do seu sistema de sessões em si. Não se esqueça que o alvo são os atacantes e não os usuários legítimos.
Ao pegar um exploit de sessão, nunca acuse o potencial atacante, mas sim solicite a senha novamente. Se você pegar um usuário legítimo e acusá-lo de algo que ele não fez, pode apostar, você terá que, além de arrumar o sistema de sessão, dar uma boa explicação ao usuário (ou ao seu chefe).
Autor: Rodolfo Andrade
Você gostou? Comente no fórum!
Comentários:
Micox disse:
Eu começei a estudar (e entender) roubo de sessoes a pouco tempo (Cross-Site Request Forgeries (CSRF))
Porém, no meu pouco conhecimento vou discordar de alguns pontos:
1) Esta comparação aqui sempre vai dar TRUE:
($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
Nunca um valor vai ser igual ao seu md5. Acredito que o autor aí queria dizer na verdade:
$USER_AGENT_ATUAL != $USER_AGENT_ANTIGO.
Não é isso?
2) Os métodos que ele usou para "complicar" não são seguros. São falhos. É muito fácil para um possível invasor pegar o valor de qualquer variável GET que esteja trafegando pela rede ou pegar o USER_AGENT. Ou seja: é só ele duplicar estes valores na requisição dele ué.
3) Ele diz que não é bom usar IP neste controle. Porém, é uma forma muito melhor que a escolhida por ele.
a- sim, o ip do cara pode mudar durante a requisição, porém isto acontece pouquissimas vezes, e quando acontecer é só botar o user pra logar novamente ué.
b- vários caras podem ter o mesmo IP atrás do proxy sim, nem tudo é perfeito né. Daí você vai ter que contar com 2 coisas: probabilidade do atacante estar na mesma rede ser baixa E usar outros métodos em conjunto com a validação do IP. Nem tudo é perfeito.
Apesar da validação por IP ter falhas, eu não tenho idéia de outros métodos melhores que ele, no meu pouco conhecimento do assunto. Se alguém souber aí fala pra nóis.
//Illan, se vc achar um tutorial bom sobre Cross-Site Request Forgeries (CSRF) posta aí pragente. Eu vi pouco sobre o assunto e isto é algo sempre esquecido.
xKuRt disse:
Quanto ao tutorial, basear a segurança de uma página apenas em um ID de sessão e user agent é bem falho...
Tiago Souza disse:
Recebi um questionamento por MP, prefiro responder à dúvida de forma pública, então, segue abaixo o questionamento:
Creio ser prudente usar a função strip_tags(), mas tentando ataques em meus próprios domínios, não consegui fazer mais do que incluir iframes dentro da página. Mesmo que eu tente executar um arquivo malicioso ele estaria executando dentro do iframe executado, certo?!
Você cita o caso do Orkut, mas naquele caso é como se fosse um mural de recados, daí dá pra colocar um laço num alert e atrapalhar a vida de quem está tentando executar a página por exemplo.
Nesse caso dos forms de busca seria apenas a resposta para você mesmo.
Gostaria da sua opinião sobre isso para que eu pudesse entender melhor e me defender melhor, claro!!
Ok, vamos lá... sobre o HTML injection, ele é utilizado principalmente para roubar cookies. Então, ai no caso você adiciona uma tag <iframe>, só que você passa o cookie via GET, se um cara estiver logado, por exemplo, você pode pegar o cookie de identificação da sessão do cara.
Claro, dependendo de como é o sistema de sessão, você pode roubar a sessão dele. Lógico, se o sistema tiver uma boa proteção contra roubo de sessão, isso não adianta. Considere quando eu digo sistema de boa proteção = checar User-Agent a cada requisição, para ver se bate com a sessão atual.
Se não tiver uma boa proteção, é como se você estivesse logado como o usuário antigo, como o usuário que você roubou o cookie*
Ah, e sobre o Orkut, realmente, dá pra fazer um laço e meter alert atééééééé!!!!! Basicamente, tudo que se faz em JS né?! Redirect, nova janela, alert, prompt, etc...
Bom galera, então é isso...
=)
Quanto ao tutorial, basear a segurança de uma página apenas em um ID de sessão e user agent é bem falho...
Bom, na verdade, o link original mesmo é esse aqui
=)
Tiago Souza disse:
//Illan, se vc achar um tutorial bom sobre Cross-Site Request Forgeries (CSRF) posta aí pragente. Eu vi pouco sobre o assunto e isto é algo sempre esquecido.
Claro, inclusive tenho um aqui onde o editor da Dark Reading, grande Kelly Jackson Higgins mostrou mais detalhes à respeito deste tipo de ataque que combina XSS e CSRF, e pelo fato de usarem o navegador da vítima, torna-se perigosíssimo!!
Ver o restante dos comentários no fórum (e aproveitar pra comentar também !).