Lo bueno de tener un tester que te odia (ddaz) y un BOFH al que no le eres grato (DrModding), es que tus aplicaciones deben ser seguras, rápidas y confiables. Puesto que serán los primeros en quejarse de tu aplicación. Por eso tengo que estar siempre optimizando todo y volviéndolo mas seguro. Por eso ahora que andaba optimizando a ZOD, me tope con que las sesiones no eran seguras, por lo cual me hice a la tarea de volverlas un poco mas seguras por parte de la aplicación, porque aunque hay un procedimiento para hacerla desde el servidor, no creo que el BOFH me de permisos de root. Así que comencemos:
La sesiones son en si, se envían y se guardan en el cliente, por eso son fácilmente manipulables por un usuario mal intencionado. Por ello lo recomendable es mandar las secciones encriptadas, ademas de que hay que verificar el tiempo de respuesta del usuario (porque el tiempo de vida de la sesión se puede modificar por el usuario). Así que para no complicarme; cree una clase de PHP de sesiones seguras. La explicación me la brincare, porque esta fácil de entender el código:
<?php
class sesion {</p>
private clavex = '';
// _SET: es para agregarle un valor a una sesion, de no estar registrada la registra
public function __set($variable,$valor){
if(session_is_registered(md5($variable+$this->clavex))==false)
$this->registrar(md5($variable+$this->clavex));
$_SESSION[md5($variable+$this->clavex)]=base64_encode($valor);
}
// _GET: Obtiene el valor de una sesion, de no estar registrada regresa false
public function __get($variable){
if(session_is_registered(md5($variable+$this->clavex)))
return base64_decode($_SESSION[md5($variable+$this->clavex)]);
else
return false;
}
// Registra la variable como sesion
private function registrar($variable){
if(!session_is_registered(md5($variable+$this->clavex)))
session_register(md5($variable+$this->clavex));
}
// Borra y destruye una sesion
public function borrar($variable){
$_SESSION[md5($variable+$this->clavex)]='';
session_unregister(md5($variable+$this->clavex));
}
// Destruye por completo todas las sesiones
public function limpiar(){
session_unset();
session_destroy();
}
// Crea un ID de sesion relacionado a una llave y a la IP del usuario
public function __construct($clave=NULL,$tiempo=60){
if(is_null($clave)){
self::limpiar();
exit;
}elseif(!is_null($clave)){
$ip=(getenv("HTTP_X_FORWARDED_FOR"))?getenv("HTTP_X_FORWARDED_FOR"):getenv("REMOTE_ADDR");
if(md5(md5($clave)+md5($ip)))==(session_id()))and(time()-base64_decode($_SESSION[md5('SESSION_TIME')])<$tiempo){
session_id(md5(md5($clave])+md5($ip)));
session_start();
$_SESSION[md5('SESSION_TIME')]= base64_encode(time());
$this->clavex=$clave;
}elseif((!session_id())and($tiempo==0){
session_id(md5(md5($clave)+md5($ip)));
session_start();
$_SESSION[md5('SESSION_TIME')]= base64_encode(time());
$this->clavex=$clave;
}else{
self::limpiar();
exit;
}
}else{
self::limpiar();
exit;
}
}
}
Bien, en si la clase, verifica la sesión por medio de una llave que solo se conoce en el servidor, así como la IP del usuario (por si llegaran a atrapar la sesión no podrá ser usada mas que por la IP con la que se genero). Así mismo, se agrega una sesión de tiempo con el tiempo encriptado en base64 (como mencione antes, esto es solo para el ejemplo, siempre recomiendo crear su propia encriptacion). La forma de usar esta clase, es incluyendo el archivo y crear el objeto de sesión:
<?php
$sesion=new sesion('llave',[tiempo_de_la_sesion]);
$sesion->nombre_de_la_sesion = valor_de_la_sesion;
$valor = $sesion->_nombre_de_la_sesion;
echo $valor;
La forma de usarlo es sencilla. Hay que recordar que la “llave” que incluimos, es tanto para encriptar la sesión como para desencriptarla, por lo cual debe ser la misma; así mismo si no ponemos el tiempo de vida de la sesión, por de facto usara 60 segundos (el tiempo debe ser puesto en segundos).
Actualización: A continuación pongo todo el codigo junto, esta probado y funciona correctamente:
<?php
class sesion {
private $clavex = '';
private $k = '';
// Crea un ID de sesion relacionado a una llave y a la IP del usuario
public function __construct($clave=NULL,$tiempo)
{
if(is_null($clave)){
self::limpiar();
print_r("Autorización no válida. Acceso denegado");
exit();
}elseif(!is_null($clave)){
if(!isset($_SESSION)){session_start();}
$ip=getIP();
$md5=(md5($clave)+md5($ip));
if($tiempo==0){
$this->tiempo=$this->encriptar(time());
$this->md5s=$this->encriptar(md5($clave)+md5($ip));
$this->clavex=$clave;
}elseif((md5($clave)+md5($ip)==$this->desencriptar($this->md5s))and((time()-$this->desencriptar($this->tiempo))<$tiempo)){
$this->tiempo=$this->encriptar(time());
$this->md5s=$this->encriptar(md5($clave)+md5($ip));
$this->clavex=$clave;
}else{
self::limpiar();
print_r('Autorización no válida. Acceso denegado');
exit;
}
}else{
self::limpiar();
print_r('Autorización no válida. Acceso denegado');
exit;
}
}
// _SET: es para agregarle un valor a una sesion, de no estar registrada la registra
public function __set($variable,$valor)
{
if(session_is_registered(md5($variable.$this->clavex))==false)
$this->registrar(md5($variable.$this->clavex));
$_SESSION[md5($variable.$this->clavex)]=$this->encriptar($valor);
}
// _GET: Obtiene el valor de una sesion, de no estar registrada regresa false
public function __get($variable)
{
if(session_is_registered(md5($variable.$this->clavex)))
return $this->desencriptar($_SESSION[md5($variable.$this->clavex)]);
else
return false;
}
// Registra la variable como sesion
private function registrar($variable)
{
if(!session_is_registered(md5($variable.$this->clavex)))
session_register(md5($variable.$this->clavex));
}
// Borra y destruye una sesion
public function borrar($variable)
{
$_SESSION[md5($variable.$this->clavex)]='';
session_unregister(md5($variable.$this->clavex));
}
// Destruye por completo todas las sesiones
public function limpiar()
{
session_unset();
}
// Funcion para saber cuantos
public function usuarioEnlinea() {
$count=0;
$handle=opendir(session_save_path());
if($handle==false) return-1;
while (($file = readdir($handle))!=false)
if (ereg("^sess",$file))
if(time()-fileatime(session_save_path().'/'.$file)<120) // 120 secs = 2 minutes session
$count++;
closedir($handle);
return $count;
}
//Funciones para encriptamiento propietario de las sesiones
private function ed($t)
{
$r = md5($this->k);
$c=0;
$v = "";
for ($i=0;$i<strlen($t);$i++) {
if ($c==strlen($r)) $c=0;
$v.= substr($t,$i,1) ^ substr($r,$c,1);
$c++;
}
return $v;
}
public function encriptar($t)
{
if(!is_array($t)){
$t=urlencode($t);
srand((double)microtime()*1000000);
$r = md5(rand(0,32000));
$c=0;
$v = "";
for ($i=0;$i<strlen($t);$i++){
if ($c==strlen($r)) $c=0;
$v.= substr($r,$c,1) .(substr($t,$i,1) ^ substr($r,$c,1));
$c++;
}
$v = base64_encode($this->ed($v));
$v = str_replace('+','AbCdE',$v);
return $v;
}else{
foreach($t as $x=>$y)
$s[$x] = $this->encriptar($y);
return $s;
}
}
public function desencriptar($t)
{
if(!is_array($t)){
$t = str_replace('AbCdE','+',$t);
$t = $this->ed(base64_decode($t));
$v = "";
for ($i=0;$i<strlen($t);$i++){
$md5 = substr($t,$i,1);
$i++;
$v.= (substr($t,$i,1) ^ $md5);
}
return urldecode($v);
}else{
foreach($t as $x=>$y)
$s[$x] = $this->desencriptar($y);
return $s;
}
}
}
?>
ACTUALIZACION: Faltaba definir una funcion getIP(), cuya función es optener la IP real (o lo mas real posible) del usuario.
<?php// Verifica la ip real de un usuario
function getIP(){
if(getenv("HTTP_CLIENT_IP")&&strcasecmp(getenv("HTTP_CLIENT_IP"),"0.0.0.0")){
$ip=getenv("HTTP_CLIENT_IP");
}elseif(getenv("HTTP_X_FORWARDED_FOR")&&strcasecmp(getenv("HTTP_X_FORWARDED_FOR"),"0.0.0.0")){
$ip=getenv("HTTP_X_FORWARDED_FOR");
}elseif(getenv("REMOTE_ADDR")&&strcasecmp(getenv("REMOTE_ADDR"), "0.0.0.0")){
$ip=getenv("REMOTE_ADDR");
} elseif(isset($_SERVER['REMOTE_ADDR'])&&$_SERVER['REMOTE_ADDR']&&strcasecmp($_SERVER['REMOTE_ADDR'],"0.0.0.0")){
$ip=$_SERVER['REMOTE_ADDR'];
}else{
error('Autorización no válida. Acceso denegado');
exit;
}
return $ip;
}
Me pudieras enviar por favor la clase, creo q el inicio está medio cortado, estoy intentando hacer varios sitios más seguros.. cosa q me llevará un buen rato terminar de entender todo esto… saludos!! Chava(Seleccionar comentario)
Hola,
muy buen ejemplo de como manejar las sesiones con clases y clase
Sólo un comentario para la gente que llega aquí, y es que usar la IP no es muy recomendable en sitios web (para intranets no hay problema), ya que por ejemplo los usuarios de AOL (y algunos otros) cambian de IP más de una vez durante la sesión.
Eso sería, saludos! Patricio Estrella(Seleccionar comentario)
Muy buenas,
me parece muy interesante esta clase. Pero para servidores que tengan versiones anteriores de PHP (me refiero a PHP 4) no funciona no.
Saludos! jorcanvi(Seleccionar comentario)
No funcióna la clase me manda error en “private clavex = ”;” alguien tiene alguna idea de por que?
De antemano gracias Cuauhtémoc Torres Vela(Seleccionar comentario)
Cuauhtémoc efectivamente traía un error, pero ya lo corregi y puse una actualización con todo el codigo junto para que puedas usarlo. Antioroku(Seleccionar comentario)
Hola que tal Antioroku gracias por responder, otra duda, en la línea 14 del código $ip=getIP() la función getIP no está definida en el código, de antemano gracias
Cuauhtémoc(Seleccionar comentario)
Se me paso esa funcion, pero ya la he actualizado Cuatémoc. Antioroku(Seleccionar comentario)
podrias poner algun ejemplo de su uso por favor esta muy completo, me gusto.
lo voy a poner en uso.
saludos! sikel(Seleccionar comentario)
Oie una consulta, mira yo se programar en java y reconosco que la forma de trabajar con clases es bien similar, llevo ya igual un buen tiempo metiendome en php y me cuesta entender algunas cosas, por ejemplo
# public function __set($variable,$valor){
# if(session_is_registered(md5($variable+$this->clavex))==false)#
# $this->registrar(md5($variable+$this->clavex));
# $_SESSION[md5($variable+$this->clavex)]=base64_encode($valor);
# }
La forma en que tratas al objeto agregandole el valor de this si me podrian explicar la forma en que trabaja this en esta clase Jorge(Seleccionar comentario)
Mira la cuestión esta así:
<?php
class sesion {
private $clavex = ”;
private $k = ”;
Al principio de la función, declaro una variable privada llamada $clavex. Para poder usar esta variable privada dentro de las función (o cualquier otra) debes utilizar la sintaxis $this-> (refiriéndose a la clase en cuestión, en este caso class sesion) y luego la funcion o variable que deseas utilizar o accesar. En este caso en particular se refiere a la variable privada $clavex, por eso se usa $this->clavex (clavex ya no lleva el simbolo $ cuando es llamada de esta forma). Antioroku(Seleccionar comentario)
Antioroku esta excelente la clase que compartes, me la he pasado estudiando, estoy haciendo un sitio modular en php, y quiero manejar sesiones para que puedan acceder a unas paginas que necesiten privilegios, la autentificacion del usuario lo manejo normal con un text de usuario y otro de password, y ahi es donde quiero implemenetar tu aporte, podrias poner un ejemplo de como seria, si no es mucha molestia gracias, esta excelente. Marco Antonio(Seleccionar comentario)
algun ejemplo de tu clase?
lo tienes funcionando para verificarlo jack(Seleccionar comentario)
Me parece bien tu class. Yo hice una tb, con algunas cosas que no tiene la tuya y sin otras que me parecen interesantes.
http://citosid.wordpress.com/2009/02/20/csession/ Abraham(Seleccionar comentario)
como captura los datos de un form
a que calse ahi k hacerle el llamado para que capture nombre de usuario y contraseña. ricardo(Seleccionar comentario)
¿Que las sesiones se guardan en el cliente? xD
Pero mira que le gusta a la peña complicarse la vida…
P.D: Por si no quedó claro.. NO las sesiones no se guardan en el pc del cliente. Aunque si en los servidores y estos pueden ser compartidos pero si pueden coger tu archivo de sesion, quizá también puedan coger tu clase para desencriptar los datos…
ahm.. y la ip en los usuarios de AOL cambia por cada petición que hacen al server… SI, por cada php, html, imagen… aunque suelen mantener los dos primeros rangos… Ivan de la Jara(Seleccionar comentario)
Por lo de tu primer PD, no quiero sonar grosero, pero primero informate un poquito antes: http://www.php.net/manual/en/intro.session.php
Y en cuanto a lo de AOL, tienes un poco de razon, pero la manera de solucionar eso, es hacerlo a travez de una mascara de IP, es cierto que cambia la IP, pero siempre se mantiene un rango. Antioroku(Seleccionar comentario)
vaya… veo que sigues pensando que los datos de sesion se guardan en el cliente cual cookies. Te explico… lo que se guarda en el cliente es la cookie de sesion, solo el id, no los datos en si. Porque sino no tendría sentido crear sesiones, se usarían cookies ya que precisamente tienen esa funcionalidad, aunque mermada porque no siempre funciona… Los datos de la sesion se guardan en archivos de texto, normalmente en el directorio temporal del servidor.
Una vez hice algo similar usando un trozo del user agent del navegador y otro de la ip y era bastante seguro aunque no permitía al mismo usuario conectarse desde dos sitios diferentes al mismo tiempo… cosa que en ese caso, un mensajero instantáneo, pues no era lo mejor.
Hoy estuve mirando oAuth a ver como lo hacían pero sigo sin verlo del todo, no lo he entendido bien, supongo que sera algo similar al ssl. Ivan de la Jara(Seleccionar comentario)
seria bueno amigo que pusieras un ejemplo, yo no pude hacerlo funcionar
. tambien seria bueno que colocaras un zip con los archivos . saludos!! y felicitaciones parece bueno lastima que …. Diego(Seleccionar comentario)
Amigo, falta un ejemplo Cafe(Seleccionar comentario)