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:
Código:
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:
Código:
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:
Código:
<?php session_start(); if (isset($_SESSION[‘HTTP_USER
($_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
Código:
<?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
Código:
<?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).
Muito interessante, essa é sem dúvida uma preocupação constante na minha cabeça, e acredito que de todo desenvolvedor PHP.
Valeu pelas dicas, bom mesmo!