Las sesiones de PHP son una herramienta muy usada en el desarrollo de
web: permiten 'recordar' datos del usuario entre una visita y otra. Un
ejemplo tipico es recordar que un usuario esta loggeado en un sitio,
para que no tenga que ingresar su * nuevamente cuando vuelve a
entrar.
Ivan R. describio en un documento como es posible tomar el
control de sesiones abiertas por otros usuarios de maneras bastante
sencillas (esto trae muchos problemas se seguridad, como podran
imaginarse).
Aqui les propongo unas piezas de codigo que hacen mucho
mas seguras las sesiones: al encriptar ciertos datos, se vuelve
imposible que otros usuarios puedan espiar las sesiones de los usuarios
y tambien impide que malvados hax0rs tomen el control de nuestras
sesiones.
Las sesiones de PHP funcionan asi: cuando el
usuario entra por primera vez al sitio, se le envia una cookie que
contiene un identificador (llamado session id o SID). Luego desde el
PHP se pueden asociar datos a este identificador.
Cada vez que el
usuario entra al sitio, el sistema de sesiones trae los datos que
corresponden a este usuario (en realidad, los que corresponden al
identificador de la cookie).
Tambien PHP ofrece otra manera de pasar
el SID, cuando browsers no soportan cookies: en estos casos, es posible
pasar el SID por el URL.
Los problemas que aparecen son los siguientes:
1)
Si uno logra averiguar cual es el Session ID de una session, es posible
pasar este dato via GET o creando una cookie y tomar el control de la
sesion.
Lo que lo hace peligroso es que conseguir el SID es bastante
sencillo: *) Los datos de las sessiones estan almacenados en /tmp/ (por
defecto, aunque se puede cambiar) y tienen de nombre: sess_SID(por ej,
sess_DAQSAFKOAKDOASK)
*) Cuando el PHP pasa el SESSIONID por el URL
y el usuario hace click sobre un link a una pagina externa, esta recibe
como dato cual fue la pagina anterior en la que el usuario estuvo
(REFERER_URL) que incluye el SID. PHP ofrece una manera de evitar que
se activen sesiones con el SID en el URL si el usuario viene desde un
sitio externo, pero este chequeo es muy facil de evitar
2)
Cuando la aplicacion PHP esta alojada en un servidor compartido
(webhosting barato), cualquier usuario puede ver los SESSIONIDs
ejecutando simplemente "ls /tmp/sess_*"
Otro problema es que
cualquier usuario con permisos para leer archivos del webserver puede
ver el contenido de las sesiones, con solo abrir el archivo de session
que desee (/tmp/sess_*)
Soluciones:
PHP permite especificar
funciones alternativas para manejar las sesiones. Usando este feature
(session_set_handler) desarrolle unas funciones que teoricamente
solucionan este problema:
La idea es, que aparte de la cookie de session se le envia otra que contiene una cadena de texto generada al azar.
Luego
el nombre del archivo donde estan guardados los datos de la sesion es
generado por el hash MD5 resultante de concatenar el ID de session y el
KEY (o sea, $filename="sess_sec_".md5($SID.$KEY)
.
Haciendo esto logramos que sea imposible deducir el ID de la session
viendo el nombre del archivo que contiene los datos y mas importante,
hacemos que sea imposible tomar el control de una session engañando al
servidor pasandole el SID, porque con un KEY equivocado (o vacio), el
server no encontrara el archivo que contiene los datos de la sesion y
creara una nueva.
Para agregar una capa mas de seguridad y
prevenir otros ataques, los datos de sesion guardados en el archivo
estan encriptados usando la cadena KEY de *, por lo que
solamente el poseedor de la KEY correcta podra tener acceso a los datos
(asi evitamos que otros usuarios del mismo servidor metan las narices
en nuestras sesiones).
Aun faltan definir algunas funciones del
sistema de sesiones, pero yo no las use nunca, asi que por ahora, no
las voy a implementar
Aunque los problemas de seguridad que
planteo no preocuparan a mas de uno, es bueno saber que las sessiones
no son 100% seguras y que existen alternativas faciles de solucionar el
problema (solo hace falta insertar las funciones y usar las funciones
de sesion comunmente)
El codigo pueden bajarlo desde aqui o verlo funcionando aqui
/*
This piece of code was developed by Martin Sarsale (martin@n3rds.com.ar)
as a response to the problem shown by Ivan R. (ivanr@webkreator.com) on the
article 'PHP Session Security' (http://www.webkreator.com/php/configuration/php-session-security.html)
This is beta code, Im looking for some suggestions to enhance it!
*/
function sess_open($sess_path, $session_name){
global $_SEC_SESSION;
$sess_sec=ini_get('session.name')."_sec";
# Apart from the session cookie we set another one, with the same name plus
# '_sec' at the end
# On that cookie, we set a random 32byte string (I'll refer to this string
# as 'key')
if (!isset($_COOKIE[$sess_sec])){
$md5=md5(uniqid(''));
setcookie($sess_sec,$md5,ini_get('session.cookie_lifetime'),
ini_get('session.cookie_path'),
ini_get('session.cookie_domain'));
$_SEC_SESSION['int']['key']=$_COOKIE[$sess_sec]=$md5;
$_SEC_SESSION['data']=serialize(array());
$empty=1;
}else{
$_SEC_SESSION['int']['key']=$md5=$_COOKIE[$sess_sec];
}
# The name of the file that contains the session info,
# starts with 'sec_sess_' and it's followed by the md5 string of the
# session_id concatenated with the previous key.
# This avoids people of reading the ID of the session from the session files
# (to hijack the session)
$_SEC_SESSION['int']['filename']=$filename_sec="$sess_path/sec_sess_".md5(session_id().$md5);
if (isset($empty)){
return 1;
}
if (!file_exists($filename_sec)){
fclose(fopen($filename_sec,'w'));
}
if (!$_SEC_SESSION['int']['fd']=fopen($filename_sec,'r')){
$_SEC_SESSION['data']=serialize(array());
return 0;
}
# The data on that file is dedrypted using the previous key
$data_enc=fread($_SEC_SESSION['int']['fd'],filesize($filename_sec));
fclose($_SEC_SESSION['int']['fd']);
if ($data_enc!=''){
$cipher=MCRYPT_DES;
$data=@mcrypt_ecb($cipher,$_SEC_SESSION['int']['key'],$data_enc,MCRYPT_DECRYPT);
}else{$data='';}
$_SEC_SESSION['data']=$data;
$_SEC_SESSION['int']['hash']=md5($_SEC_SESSION['data']);
return 1;
}
function sess_close(){
return true;
}
function sess_read($key){
return $GLOBALS['_SEC_SESSION']['data'];
}
function sess_write($id,$data){
global $_SEC_SESSION;
$sd=$data;
if ($_SEC_SESSION['int']['hash'] != md5($sd)){
$fd=fopen($_SEC_SESSION['int']['filename'],'w');
$cipher=MCRYPT_DES;
# Here we crypt the data with our key...
$data=@mcrypt_ecb($cipher,$_SEC_SESSION['int']['key'],$sd,MCRYPT_ENCRYPT);
fputs($fd,$data);
fclose($fd);
chmod($_SEC_SESSION['int']['filename'],0600);
}
}
function sess_destroy($key){
return(@unlink($GLOBALS['_SEC_SESSION']['int']['filename']));
}
function sess_gc($maxlifetime){}
session_set_save_handler('sess_open','sess_close','sess_read','sess_write','sess_destroy','sess_gc');
session_start();
if (!isset($_SESSION['times'])){
$_SESSION['times']=0;
}
$_SESSION['times']++;
print "This session ID is: ".session_id().
" but the name of the file that contains the data is ".
$_SEC_SESSION['int']['filename']."n";
print "Btw, this is the ".$_SESSION['times']." you see this page
(it works!)n";
?>