Ver Mensaje Individual
  #5 (permalink)  
Antiguo 06/09/2012, 05:44
Avatar de Triby
Triby
Mod on free time
 
Fecha de Ingreso: agosto-2008
Ubicación: $MX->Gto['León'];
Mensajes: 10.106
Antigüedad: 15 años, 11 meses
Puntos: 2237
Respuesta: [Aporte] Seguridad básica en PHP

Novena Parte -> Manejo de usuarios

Para tratar de proteger las sesiones es conveniente que en cada cambio de estado (login y logout) generemos una nueva id de sesión y eliminemos las variables que no serán necesarias:

Código PHP:
Ver original
  1.  
  2. // Ingreso de usuario
  3. function login($id, $usuario, $correo, $rol) {
  4.     // Se supone que ya verificamos que los datos son correctos
  5.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  6.  
  7.     // Creamos sólo las variables que necesitaremos
  8.     $_SESSION = array(
  9.         'usuario' => array(
  10.             'id' => $id,
  11.             'nombre' => $usuario,
  12.             'correo' => $correo,
  13.             'rol' => $rol
  14.         )
  15.     );
  16.     // Redirigimos a página de usuario
  17.     header('Location: bienvenido.php');
  18.     exit;
  19. }
  20.  
  21. // Finalizar sesión
  22. function logout() {
  23.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  24.  
  25.     // Eliminamos información de sesión
  26.     $_SESSION = array();
  27.  
  28.     // Redirigimos a página principal
  29.     header('Location: index.php');
  30.     exit;
  31. }

Aunque el uso de md5() no es tan aconsejable, podemos darle un poco más de confiabilidad agregando información y, de paso, nos ayudará tratando de hacer más segura nuestra sesión:
Cita:
Iniciado por IMPORTANTE
Es recomendable ver la sugerencia de Abimaelrc para reforzar la seguridad en este punto: http://www.forosdelweb.com/f18/aport...8/#post4330821
Código PHP:
Ver original
  1.  
  2. // Un pequeño agregado para usar en md5()
  3. DEFINE('SALT_MD5', 'AbC%"$%"');
  4.  
  5. // El usuario ya inició sesión?
  6. if(esUsuario()) {
  7.     // Ok, es una sesión válida
  8.     // Aquí el código para usuarios con sesión iniciada
  9. } else {
  10.     // No ha iniciado sesión
  11.     // Aquí el código para "invitados"
  12. }
  13.  
  14. // Obtener cadena de comprobación
  15. function strMd5($cadena) {
  16.     return md5(SALT_MD5 . $cadena . SALT_MD5);
  17. }
  18.  
  19. // Ingreso de usuario
  20. function login($id, $usuario, $correo, $rol) {
  21.  
  22.     // Creamos sólo las variables que necesitaremos
  23.     $_SESSION = array(
  24.         'usuario' => array(
  25.             'id' => $id,
  26.             'nombre' => $usuario,
  27.             'correo' => $correo,
  28.             'rol' => $rol,
  29.             'comprobacion' => strMd5("$id|$usuario|$correo|{$_SERVER['REMOTE_ADDR']}");
  30.         )
  31.     );
  32.     // Redirigimos a página de usuario
  33.     header('Location: bienvenido.php');
  34.     exit;
  35. }
  36.  
  37. // Finalizar sesión
  38. function logout() {
  39.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  40.  
  41.     // Eliminamos información de sesión
  42.     $_SESSION = array();
  43.  
  44.     // Redirigimos a página principal
  45.     header('Location: index.php');
  46.     exit;
  47. }
  48.  
  49. // Comprobar si es una sesión válida
  50. // Devuelve verdadero sólo si el usuario inició sesión y la cadena de comprobación es correcta
  51. // Si el usuario cambia de IP, entonces finalizará su sesión
  52. function esUsuario() {
  53.     if(isset($_SESSION['usuario'])) {
  54.         $cadena = "{$_SESSION['usuario']['id']}|{$_SESSION['usuario']['nombre']}|{$_SESSION['usuario']['correo']}|{$_SERVER['REMOTE_ADDR']}";
  55.         if(strMd5($cadena) == $_SESSION['usuario']['comprobacion']) {
  56.             // Sesión válida
  57.             return true;
  58.         }
  59.         // Sesión no válida, posiblemente es un ataque
  60.         // Cerramos sesión
  61.         logout();
  62.     }
  63.     return false;
  64. }

Nota 1: La dirección IP es tan poco confiable como la información del navegador, sin embargo, los datos añadidos en la cadena de comprobación harán que un atacante se tenga que esforzar más para obtener acceso al sitio.

Nota 2: Sí, lo sé, puede resultar molesto que la sesión caduque automáticamente cuando encendemos la computadora y regresamos al sitio, todo porque la IP cambió, entonces, esta estrategia podría ser usada sólo para usuarios con permisos administrativos.

Hay que tener en cuenta que la sesión podría cerrarse aún cuando el usuario tenga abierta la página, pero esté inactivo durante mucho tiempo; igualmente, debería haber la posibilidad de guardar los datos del usuario para mantener la conexión activa entre cada visita. En estos casos, debemos hacer uso de cookies, teniendo cuidado de guardar sólo información que nos permita obtener id de usuario y comparar con otros datos para saber que se trata de una sesión válida.

Más o menos así quedaría el script al final:


Código PHP:
Ver original
  1.  
  2. // Un pequeño agregado para usar en md5()
  3. DEFINE('SALT_MD5', 'AbC%"$%"');
  4.  
  5. // El usuario ya inició sesión?
  6. if(esUsuario()) {
  7.     // Ok, es una sesión válida
  8.     // Aquí el código para usuarios con sesión iniciada
  9. } else {
  10.     // No ha iniciado sesión
  11.     // Aquí el código para "invitados"
  12. }
  13.  
  14. // Obtener cadena de comprobación
  15. function strMd5($cadena) {
  16.     return md5(SALT_MD5 . $cadena . SALT_MD5);
  17. }
  18.  
  19. // Ingreso de usuario
  20. function login($id, $usuario, $correo, $rol) {
  21.  
  22.     // Creamos sólo las variables que necesitaremos
  23.     $_SESSION = array(
  24.         'usuario' => array(
  25.             'id' => $id,
  26.             'nombre' => $usuario,
  27.             'correo' => $correo,
  28.             'rol' => $rol,
  29.             'comprobacion' => strMd5("$id|$usuario|$correo|{$_SERVER['REMOTE_ADDR']}")
  30.         )
  31.     );
  32.     // Creamos cookie 'usuario' con id y cadena de comprobación
  33.     // Expira en un mes y está disponible en la raiz del sitio y todas las subcarpetas
  34.     setcookie('usuario', "$id|{$_SESSION['usuario']['comprobacion']}", time() + (86400 * 30), '/');
  35.  
  36.     // Redirigimos a página de usuario
  37.     header('Location: bienvenido.php');
  38.     exit;
  39. }
  40.  
  41. // Finalizar sesión
  42. function logout() {
  43.     // Creamos una nueva id de sesión y eliminamos la anterior (true)
  44.  
  45.     // Eliminamos información de sesión
  46.     $_SESSION = array();
  47.  
  48.     // Borramos info de cookie
  49.     setcookie('usuario', 'sin sesión', time() + 3600, '/');
  50.  
  51.     // Redirigimos a página principal
  52.     header('Location: index.php');
  53.     exit;
  54. }
  55.  
  56. // Comprobar si es una sesión válida
  57. // Devuelve verdadero sólo si el usuario inició sesión y la cadena de comprobación es correcta
  58. // Si el usuario cambia de IP, entonces finalizará su sesión
  59. function esUsuario() {
  60.     // Si no existe la variable de sesión
  61.     if( ! isset($_SESSION['usuario'])) {
  62.         // Entonces buscamos en cookie
  63.         $cookie = (isset($_COOKIE['usuario'])) ? $_COOKIE['usuario'] : 'sin sesión';
  64.         if($cookie != 'sin sesión' && $cookie != '') {
  65.             // Separamos ud y cadena de comprobación
  66.             $tmp = explode('|', $cookie);
  67.  
  68.             // Sólo será válida si son dos elementos
  69.             if(count($tmp) == 2) {
  70.                 $id = (int) $tmp[0];
  71.                 $sql = "SELECT id, nombre, correo, rol FROM usuarios WHERE id = $id";
  72.                 // Leemos la consulta con nuestra librería preferida
  73.  
  74.                 // Si los datos son válidos, creamos variables de sesión
  75.                 $_SESSION['usuario'] = array(
  76.                     'id' => $id,
  77.                     'nombre' => $usuario,
  78.                     'correo' => $correo,
  79.                     'email' => $email,
  80.                     'rol' => $rol,
  81.                     'comprobacion' => $tmp[1] // La cadena obtenida de cookie
  82.                 );
  83.             }
  84.         }
  85.     }
  86.  
  87.     if(isset($_SESSION['usuario'])) {
  88.         $cadena = "{$_SESSION['usuario']['id']}|{$_SESSION['usuario']['nombre']}|{$_SESSION['usuario']['correo']}|{$_SERVER['REMOTE_ADDR']}";
  89.         if(strMd5($cadena) == $_SESSION['usuario']['comprobacion']) {
  90.             // Sesión válida, renovamos cookie para mantenerlo conectado
  91.             setcookie('usuario', "$id|{$_SESSION['usuario']['comprobacion']}", time() + (86400 * 30), '/');
  92.  
  93.             return true;
  94.         }
  95.         // Sesión no válida, posiblemente es un ataque
  96.         // Cerramos sesión
  97.         logout();
  98.     }
  99.     // No hay sesión válida, nos aseguramos de que la cookie siga sin datos
  100.     setcookie('usuario', 'sin sesión', time() + 3600, '/');
  101.     return false;
  102. }

Últimas recomendaciones:

- Nunca guardar las contraseñas directamente en la base de datos, es recomendable codificarla con md5(), sha1() o cualquier otro método que no permita decodificar y agregando algunos caracteres como en el ejemplo anterior, que usamos la constante SALT_MD5.
- Si en el ejemplo anterior cambiamos la cadena de comprobación quitando el correo y agregando la contraseña codificada nos facilitaría comprobar si la cookie de usario es realmente válida.
- Por favor, no copies y pegues el código esperando que funcione, todo lo hice en el bloc de notas y, seguramente, habrá muchos errores; analiza, entiende y adapta lo que creas que te pueda servir.
- Cualquier duda será respondida en este mismo tema.

Bueno, creo que es todo, sólo falta aclarar que no hay sistema 100% seguro y que esto es sólo una guía para tratar de evitar ataques y cualquier comentario, sugerencia o añadidura será bienvenida.
__________________
- León, Guanajuato
- GV-Foto

Última edición por Triby; 13/12/2012 a las 17:31