martes, agosto 02, 2005

(PHP) Haciendo seguras las sessiones

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 password 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 password, 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 thearticle '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";

No hay comentarios.: