Session Hijacking (secuestro o robo de sesión) se refiere a que un individuo (atacante) consigue el identificador de sesión entre una página web y un usuario, de forma que puede hacerse pasar por el usuario y acceder a su cuenta en esa página web.
A continuación veremos algunas de las formas en las que un atacante puede robar este identificador y técnicas para intentar prevenirlo.
ATAQUE POR FUERZA BRUTA
El ataque por fuerza bruta significa probar identificadores aleatoriamente hasta encontrar uno que esté siendo usado. Es como intentar abrir una caja fuerte sin saber la combinación, poniendo números al azar.
Como en el caso de la caja fuerte, cuantos más números tenga la combinación (en este caso el identificador de sesión), más difícil será de adivinar. También ayuda el hecho de que el número o identificador sea aleatorio, y no algo que se pueda predecir. El sistema de identificadores de sesión de PHP es aceptable en este sentido.
ROBO POR SNIFFING
Este tipo de ataque se da cuando el atacante tiene un programa de sniffing en la red del usuario y puede interceptar el tráfico destinado al mismo, incluido su identificador de sesión. Es algo que ha dado mucho de que hablar a causa de Firesheep, una extensión para Firefox que permite robar las sesiones de Facebook, Twitter y otras páginas web muy conocidas en redes inalámbricas públicas.
Lo solución ideal sería el uso del protocolo HTTPS que encriptaría toda nuestra conexión, pero si esto no es posible, podemos crear una serie de comprobaciones en las que intentemos asegurarnos de la real identidad de cada usuario.
PROPAGACIÓN EN URL
Si el identificador de sesión se propaga utilizando la URL en lugar de las cookies, cualquier atacante puede robarlo desde muchos sitios:
Un enlace que el propio usuario ponga en un lugar público. Los usuarios típicos no saben para que sirve ese identificador y no le dan importancia.
El historial del navegador.
El referrer, que es un encabezado que envían muchos navegadores a las páginas web en el que les indican la URL de la que vienen.
La forma de prevenir esto es no utilizar la URL para el identificador de sesión; sino mas bien asegurarse que el sid del usuario viaje a través de cookies y únicamente cookies. En PHP esto se consigue con la instrucción:
<?php
ini_set('session.use_trans_sid',0); // no utilizar URL para sid
ini_set('session.use_only_cookies',1); // utilizar cookies para enviar sid
?>
tambien de ser possible especificar set session.cookie_secure a true, lasdos recomendaciones anteriores pueden trabajarse desde session_set_cookie_params.
ROBO EN SERVIDOR COMPARTIDO
Si tenemos nuestra página web alojada en un servidor compartido, los archivos físicos de las sesiones se guardan, por defecto, en un directorio común para todas las páginas web del servidor. Esto quiere decir que todas las personas que tengan su página web en ese mismo servidor, tienen acceso a todos los archivos de sesiones. Dado que el nombre de los archivos es "sess_" más el identificador de sesión, cualquier atacante tendrá una lista de identificadores de sesión válidos con sólo leer la lista de archivos del directorio común.
Esta vulnerabilidad se puede combatir de dos formas:
Usando la función session_save_path para guardar los archivos de sesión de la página web en un directorio dentro de su cuenta al que sólo pueda acceder PHP (ya sea por estar fuera del directorio web o con un .htaccess con la instrucción deny from all). Este método no es demasiado fiable, ya que el resto de usuarios seguirá pudiendo leer en ese directorio, sólo tienen que averiguar su localización.
Guardando las sesiones en base de datos en lugar de en archivos. Esto se consigue fácilmente usando la función session_set_save_handler. Esta solución es la más segura ya que la página web será la única que tendrá acceso a la base de datos y, por tanto, a las sesiones.
ROBO POR CROSS-SITE SCRIPTING
Si la página web es vulnerable a XSS el atacante puede insertar un código javascript que envíe las cookies de un usuario a su cuenta.
Este tipo de ataque se puede prevenir (además de evitando los ataques XSS) haciendo que las cookies de sesión tengan el atributo HttpOnly, que evita que puedan ser manejadas por javascript en la mayoría de navegadores. En PHP esto se consigue con la instrucción:
<?php
ini_set('session.cookie_httponly', 1);
?>
MÉTODOS DE PREVENCIÓN GENERALES
Ten cuidado donde guardas la informacion de la sesión del usuario, por omisión, PHP usa el gestor interno de almacenamiento files, el cual se establece mediante session.save_handler. Éste guarda los datos de sesión en el servidor en la ubicación especificada por la directiva de configuración session.save_path, pode defecto se encuentra en session.save_path = "c:/wamp/tmp"
Revisa tu php.ini y define un identificador de hash para la función HASH(), aunque recuerda que también puedes especificar el método en código
; Select a hash function for use in generating session ids.
; Possible Values
; 0 (MD5 128 bits)
; 1 (SHA-1 160 bits)
; This option may also be set to the name of any hash function supported by
; the hash extension. A list of available hashes is returned by the hash_algos()
; function.
; http://php.net/session.hash-function
session.hash_function = 0
Además de los métodos de prevención concretos vistos para cada tipo de ataque, vamos a ver algunas técnicas de prevención que ayudan a evitar el robo de las sesiones:
Descomenta y define un fuerte hash en php.ini, un nivel 5 es suficiente
; session.hash_bits_per_character
; Default Value: 4
; Development Value: 5
; Production Value: 5
Utiliza entropy para generar tus SID, desde php.ini establece un valor distinto para session.entropy_length y descomenta el session.entropy_file
; How many bytes to read from the file.
; http://php.net/session.entropy-length
session.entropy_length = 0
; Specified here to create the session id.
; http://php.net/session.entropy-file
; Defaults to /dev/urandom
; On systems that don't have /dev/urandom but do have /dev/arandom, this will default to /dev/arandom
; If neither are found at compile time, the default is no entropy file.
; On windows, setting the entropy_length setting will activate the
; Windows random source (using the CryptoAPI)
;session.entropy_file = /dev/urandom
Verifica que tus datos de sesión no se guarden en cache, la información a la que se refiere es a los headers que se envía en cada petición
; Set to {nocache,private,public,} to determine HTTP caching aspects
; or leave this empty to avoid sending anti-caching headers.
; http://php.net/session.cache-limiter
session.cache_limiter = nocache
Limitar tiempo de inactividad:
session_cache_expire: Devuelve la caducidad de la caché actual, La caducidad de la caché se reinicia al valor predeterminado de 180 minutos almacenado en session.cache_expire en el momento de iniciar una petición. Por lo tanto, se necesita llamar a session_cache_expire() por cada petición (y antes de llamar a session_start()),la información a la que se refiere es a los headers que se envía en cada petición
desde php.ini puedes establecer un tiempo prudente
; Document expires after n minutes.
; http://php.net/session.cache-expire
session.cache_expire = 180
En el php.ini esta un atributo llamado 'session.cookie_lifetime', si está en 0 va a durar hasta que el navegador se cierre tambien puedes indicar cuanto debe durar la cookie de sesion, en esta línea session.gc_maxlifetime = 1440 que esta dado en segundos, cambias el 1440 por el número de segundos que estimes conveniente.
Si no tienes acceso al php.ini puedes utilizar session_set_cookie_params con su parámetro respectivo que es lifetime en este caso se termina sesión cuando el usuario cierra el explorador web:
<?php
session_set_cookie_params(0);
session_start();
?>
Para que funcione debes eliminar debidamente las cookies de session cuando un usuario termine session.
Este script maneja valida el tiempo establecido de vida para una sesión, en caso de que la validación haya expirado destruye la sesión y redirige al usuario al área de login para que vuelva a registrarse , el siguiente código debe colocarse en la página de inicio de sesión
<?php
session_start();
$_SESSION['tiempo']=time();
?>
Este script deberá colocarse en toda página que requiera una sesión de usuario autorizada
<?php
session_start();
include('archivo.php');
.....
.....
?>
Y este será el contenido de archivo.php que se encargara de cerrar una sesión si es que esta ha expirado de acuerdo a un tiempo establecido por nosotros a conveniencia
<?php
$time = 3600; // una hora en mili-segundos
// verificamos si existe la sesión
// el nombre "session_name" es como ejemplo
if(isset($_SESSION["session_name"]))
{
// verificamos si existe la sesión que se encarga del tiempo
// si existe, y el tiempo es mayor que una hora, expiramos la sesión
if(isset($_SESSION["expire"]) && time() > $_SESSION["expire"] + $time)
{
echo'';
// también puedes utilizar header(“Location:index.php”);
// destruir informacion de session en el server
unset($_SESSION["expire"]);
unset($_SESSION["session_name"]);
session_unset();
session_destroy();
// destruir informacion de session en el cliente
$session_cookie_params = session_get_cookie_params();
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']);
// Limpiar el array $_SESSION
$_SESSION = array();
}
// creamos o redefinimos el valor de la variable de session
else
{
$_SESSION["expire"] = time();
}
}
?>
Cambiar el identificador de sesión.
Cada cierto tiempo o después de cada acción, cambiar el identificador de la sesión por otro distinto y eliminar la sesión antigua, aparte se debe generar nuevos IDs de sesión para distintos clientes. El programa del lado del servidor nunca debería usar IDs entregados por el browser para un usuario logueado, esto es, una vez que el usuario se loguea en el sistema, éste debería generar automáticamente un nuevo ID de sesión y utilizar éste en el futuro.
Desde el código que realiza el login debería utilizarse sesion_regenerate_id() a fin de no utilizar el que por defecto entrega PHP
<?php
if(($_POST['user'] == "demasiadovivo") && ($_POST['pass'] == "123456"))
{
session_regenerate_id(true);
$_SESSION['user'] = "demasiadovivo";
}
?>
La función session_regenerate_id() crea un nuevo ID de sesión. De ésta forma, por más que hayamos usado un ID insertado por el atacante antes de loguearnos, una vez logueados utilizaremos un ID totalmente distinto y desconocido por el atacante.
Otra opción es regenerar los identificadores de sesión para sesiones nuevas. Esto se consigue poniendo una "marca" en las sesiones de la página web. Cada vez que se hace un session_start(), si la sesión no tiene la "marca", cambiamos su identificador por uno nuevo (session_regenerate_id(true);) y desconocido para el atacante. Ejemplo:
<?php
session_start();
if (isset($_SESSION['mark']) === false)
{
session_regenerate_id(true);
$_SESSION['mark'] = true;
}
?>
Cambiar el identificador de sesión siempre que un usuario cambie su estado (autenticado, registrado, ...). Con la anterior medida de protección puede parecer que ya estamos protegidos, pero un atacante puede entrar en la página web, generar una sesión con "marca" y después mandar el identificador de esa sesión al usuario atacado. Así el usuario atacado acabaría con una sesión con "marca" y conocida por el atacante. Por eso, se debe regenerar el identificar de sesión siempre que un usuario realice una acción que le obligue a autenticarse. Ejemplo:
<?php
if ($user_logged_in === true)
{
session_regenerate_id(true);
$_SESSION['logged'] = true;
}
?>
Aviso: es muy importante que sea session_regenerate_id(true); y no session_regenerate_id(); ya que si no eliminamos la sesión antigua, estamos haciendo más fácil los ataques contra la página web al dejar múltiples sesiones válidas abiertas en el servidor.
Sistema de logout: dar a los usuarios una forma de salir de su cuenta y destruir la sesión.
Dentro de tu página coloca visiblemente un espacio con la opción de salir para un usuario registrado y coloca este código
<?php
// destruir informacion de session en el server
session_unset();
session_destroy();
// destruir información de session en el cliente
$session_cookie_params = session_get_cookie_params();
setcookie(session_name(), '', time() - 24 * 3600, $session_cookie_params['path'], $session_cookie_params['domain'], $session_cookie_params['secure'], $session_cookie_params['httponly']);
// Limpiar el array $_SESSION
$_SESSION = array();
?>
Verificación doble.
Usar un segundo método para intentar reconocer al usuario de la sesión. Esto puede hacerse guardando cabeceras como HTTP_USER_AGENT (navegador del usuario) o REMOTE_ADDR (IP del usuario) cuando se crea la sesión, de esta forma:
Para ello podemos guardar una serie de variables de sesión de los datos que el usuario nos proporciona en cada visita, como son la dirección IP o la información de su navegador, etc. Si estos cambian significaría que es un intento de session hijacking y por tanto es un usuario que realiza un ataque. En el siguiente ejemplo tenemos dos archivos, en el que en el primer archivo realizamos un login satisfactorio y guardamos los datos iniciales de dirección IP y tipo del navegador con el que lo hemos hecho:
<?php
session_start();
$_SESSION['REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['HTTP_USER_AGENT'] = $_SERVER['HTTP_USER_AGENT'];
?>
En pantallas posteriores, comprobaremos si en cada pantalla los datos actuales de dirección IP y datos del navegador se corresponden con los que se realizó el login como usuario en la web:
<?php
session_start();
if($_SESSION['REMOTE_ADDR'] != $_SERVER['REMOTE_ADDR'] || $_SESSION['HTTP_USER_AGENT'] != $_SERVER['HTTP_USER_AGENT']) {
exit();
}
?>
Una aplicación de las medidas expuestas anteriormente es:
<?php
// obtener siempre esta info en cada pagina
$aip = $_SERVER['REMOTE_ADDR'];
$bip = $_SERVER['HTTP_X_FORWARDED_FOR'];
$agent = $_SERVER['HTTP_USER_AGENT'];
session_start();
// crear cuando el usuario se loguee
$_SESSION['ident'] = hash("sha256", $aip . $bip . $agent);
// verificar en cada pagina que requiera usuario registrado
if(!isset($_SESSION['ident'])){
// hacer esto en cada pagina que visite un usuario registrado
$ident = hash("sha256", $aip . $bip . $agent);
if ($ident != $_SESSION['ident'])
{
end_session();
header("Location: login.php");
}
}
Exit(“Intento de Hijacking”);
?>
Un ejemplo más sencillo podría ser
<?php
session_start();
if (isset($_SESSION['HTTP_USER_AGENT']))
{
if ($_SESSION['HTTP_USER_AGENT'] != md5($_SERVER['HTTP_USER_AGENT']))
{
/* Prompt for password */
exit;
}
}
else
{
$_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']);
}
?>
O bien crear una function
<?php
static protected function preventHijacking()
{
if(!isset($_SESSION['IPaddress']) || !isset($_SESSION['userAgent']))
return false;
if ($_SESSION['IPaddress'] != md5($_SERVER['REMOTE_ADDR']))
return false;
if( $_SESSION['userAgent'] != md5($_SERVER['HTTP_USER_AGENT']))
return false;
return true;
}
?>
Otra forma de prevenir hijacking es verificando el $_SERVER['HTTP_REFERER']
pero antes de utilizarlo dale una ojeada a esto:
The address of the page (if any) which referred the user agent
to the current page. This is set by the user agent. Not all user agents
will set this, and some provide the ability to modify HTTP_REFERER as
a feature. In short, it cannot really be trusted.
http://php.net/manual/en/reserved.variables.server.php
Igual aqui dejo el codigo
<?php
if(isset($_SERVER['HTTP_REFERER']) && (strpos($_SERVER['HTTP_REFERER'], "http://tu_direccion") === 0)) {
echo "sigues con tus validaciones, no confies plenamente";
}
else
{
echo "Busca otra forma de validar";
}
?>