PHP Sesións

De MediaWiki
Ir a la navegación Ir a la búsqueda

Concepto

  • Unha sesión permite manter información asociada a un usuario entre diferentes peticións HTTP.
  • HTTP é un protocolo sen estado. Isto significa que, por defecto, cada petición que realiza o navegador é independente das anteriores.
 : As sesións permiten que o servidor poida identificar diferentes peticións como pertencentes ao mesmo usuario.
  • Cada sesión ten asociado un identificador único denominado ID de sesión ou SID.
  • Por defecto, PHP envía o identificador de sesión ao navegador mediante unha cookie.
 : O nome predeterminado desta cookie é PHPSESSID.
  • Os datos almacenados nas variables de sesión gárdanse no servidor. O navegador normalmente só almacena o identificador da sesión.
  • Dentro dunha sesión podemos crear variables que manterán o seu valor entre diferentes páxinas da aplicación:

:

  $_SESSION['usuario'] = 'angel';
  • Estas variables estarán dispoñibles mentres os datos da sesión sigan existindo no servidor e a aplicación considere válida esa sesión.
  • As sesións son especialmente útiles para almacenar información relacionada co estado dun usuario:
 :** usuario autenticado;
 :** identificador do usuario;
 :** permisos;
 :** carro da compra;
 :** preferencias temporais;
 :** tokens CSRF;
 :** etc.

Importante: aínda que os datos de sesión se almacenen no servidor, non debemos considerar automaticamente segura toda a información gardada en $_SESSION. O identificador de sesión debe protexerse correctamente para evitar que outro usuario poida utilizar a sesión.


Duración dunha sesión

Non debemos confundir:

  • a duración da cookie que contén o identificador da sesión;
  • a duración dos datos da sesión no servidor;
  • o tempo máximo de inactividade que decide a aplicación.

A directiva:

session.gc_maxlifetime = 1440

ten por defecto o valor 1440 segundos (24 minutos).

Isto non significa que PHP peche automaticamente a sesión exactamente despois de 24 minutos de inactividade.

session.gc_maxlifetime indica o tempo a partir do cal os datos dunha sesión poden considerarse obsoletos e ser eliminados polo mecanismo de recollida de lixo ou garbage collector.

A execución deste mecanismo depende tamén de:

session.gc_probability
session.gc_divisor

Por exemplo, cos valores predeterminados:

session.gc_probability = 1
session.gc_divisor = 100

existe unha probabilidade de 1/100 de executar o recolector de sesións ao inicializar unha sesión.

Por este motivo, se queremos implementar un límite real de inactividade, é mellor controlalo dende a propia aplicación.

Por exemplo, para pechar unha sesión despois de 30 minutos de inactividade:

<?php
session_start();

$tempoMaximo = 30 * 60;

if (
isset($_SESSION['ultima_actividade']) &&
time() - $_SESSION['ultima_actividade'] > $tempoMaximo
) {
$_SESSION = [];
session_destroy();

```
header('Location: login.php');
exit;
```

}

$_SESSION['ultima_actividade'] = time();
?>

Máis información: Configuración de sesións en PHP


Configuración

O comportamento das sesións pode configurarse principalmente mediante o arquivo php.ini.

Dependendo da directiva e da configuración do servidor, algúns valores tamén poden modificarse mediante ini_set(), .user.ini ou a configuración do servidor web.

As directivas máis importantes son:

session.save_handler
Indica o mecanismo empregado para almacenar e recuperar os datos das sesións.
O valor predeterminado é:
session.save_handler = files
Neste caso PHP almacena as sesións en ficheiros.
As extensións de PHP tamén poden proporcionar outros mecanismos de almacenamento.
session.save_path
Indica onde se almacenarán os datos cando se emprega o controlador files.
session.name
Determina o nome da cookie que almacena o identificador da sesión.
O valor predeterminado é:
session.name = PHPSESSID
session.auto_start
Permite iniciar automaticamente unha sesión ao comezar cada petición PHP.
O valor predeterminado é:
session.auto_start = 0
Normalmente é preferible manter este valor desactivado e iniciar explicitamente a sesión mediante session_start() cando sexa necesario.
session.use_cookies
Indica se PHP utilizará cookies para almacenar o identificador da sesión no navegador.
session.use_cookies = 1
session.use_only_cookies
Obriga a utilizar exclusivamente cookies para transportar o identificador de sesión.
session.use_only_cookies = 1
Este é o comportamento recomendado.
Dende PHP 8.4 está obsoleto desactivar esta opción.
session.use_trans_sid
Permite propagar automaticamente o identificador da sesión mediante a URL.
Debe permanecer desactivado:
session.use_trans_sid = 0
Dende PHP 8.4 está obsoleto activar esta opción.
session.use_strict_mode
Fai que PHP rexeite identificadores de sesión que non foran previamente creados polo servidor.
Axuda a evitar ataques de fixación de sesión.
session.use_strict_mode = 1
É recomendable activalo.
session.cookie_lifetime
Indica durante canto tempo permanece no navegador a cookie que contén o identificador de sesión.
session.cookie_lifetime = 0
O valor 0 significa que a cookie é unha cookie de sesión e normalmente desaparecerá cando finalice a sesión do navegador.
session.cookie_path
Indica para que rutas do servidor será válida a cookie.
session.cookie_path = /
session.cookie_secure
Se está activada, a cookie da sesión só se enviará mediante conexións HTTPS.
session.cookie_secure = 1
En produción debería utilizarse HTTPS e activar esta opción.
Nun entorno de desenvolvemento local utilizando HTTP pode ser necesario tela desactivada.
session.cookie_httponly
Impide que JavaScript acceda directamente á cookie mediante document.cookie.
session.cookie_httponly = 1
Axuda a reducir determinados riscos derivados de ataques XSS.
session.cookie_samesite
Controla en que peticións procedentes doutros sitios web se envía a cookie.
Os valores habituais son Lax e Strict.
session.cookie_samesite = Lax
Lax adoita ser unha opción razoable para aplicacións web convencionais.
session.cookie_partitioned
Dispoñible a partir de PHP 8.5.
Permite crear cookies particionadas mediante o mecanismo CHIPS dos navegadores.
session.cookie_partitioned = 0
Non é necesario activala nas aplicacións web convencionais.
Resulta especialmente útil en determinados contextos nos que unha aplicación está embebida dentro doutros sitios web.
As cookies particionadas requiren conexións seguras HTTPS.
session.gc_maxlifetime
Indica cantos segundos deben pasar antes de que os datos dunha sesión poidan considerarse obsoletos.
session.gc_maxlifetime = 1440
O valor predeterminado son 1440 segundos, é dicir, 24 minutos.
Non debe empregarse como único mecanismo para implementar o peche automático por inactividade.


Configuración recomendada

Nunha aplicación executada mediante HTTPS poderiamos utilizar unha configuración similar á seguinte:

session.use_cookies = 1
session.use_only_cookies = 1
session.use_trans_sid = 0

session.use_strict_mode = 1

session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = Lax
session.cookie_lifetime = 0

session.cookie_partitioned = 0

Nota: session.cookie_secure = 1 debe utilizarse cando a aplicación funciona mediante HTTPS.

Máis información: Configuración de seguridade das sesións


Uso

Inicializar

Para iniciar unha nova sesión ou recuperar unha sesión anterior utilizamos:

session_start();

Por exemplo:

<?php
session_start();
?>

Cada vez que PHP inicia ou recupera unha sesión:

  1. comproba se o navegador enviou unha cookie co identificador da sesión;
  1. se a sesión existe, carga os seus datos en $_SESSION;
  1. se non existe unha sesión válida, crea unha nova.

A variable:

$_SESSION

é un array superglobal.

Podemos utilizala dende calquera script PHP no que previamente teñamos iniciado a sesión.


Importante:

session_start() normalmente debe executarse antes de enviar contido ao navegador.

Por exemplo:

<?php
session_start();

$_SESSION['nome'] = 'Angel';
?>

<!DOCTYPE html>

<html lang="gl">
<head>
    <meta charset="UTF-8">
    <title>Exemplo</title>
</head>
<body>
    <p>Sesión iniciada.</p>
</body>
</html>

Non debemos facer primeiro:

echo 'Ola';

session_start();

xa que PHP necesita poder enviar as cabeceiras HTTP asociadas á cookie da sesión.


Usar variables de sesión

Unha vez iniciada a sesión podemos crear variables:

$_SESSION['nome'] = 'Angel';
$_SESSION['idade'] = 25;
$_SESSION['autenticado'] = true;

E recuperalas noutra páxina:

<?php
session_start();

echo $_SESSION['nome'];
?>

É recomendable comprobar previamente que unha variable existe:

<?php
session_start();

if (isset($_SESSION['nome'])) {
echo $_SESSION['nome'];
}
?>

Tamén podemos utilizar o operador ??:

<?php
session_start();

$nome = $_SESSION['nome'] ?? 'Descoñecido';

echo $nome;
?>


Exemplo: contador de visitas

 1 <?php
 2 session_start();
 3 
 4 if (isset($_SESSION['visitas'])) {
 5 $_SESSION['visitas']++;
 6 } else {
 7 $_SESSION['visitas'] = 1;
 8 }
 9 
10 echo 'Número de visitas nesta sesión: ' . $_SESSION['visitas'];
11 ?>

Cada usuario terá o seu propio valor de $_SESSION['visitas'] asociado ao seu identificador de sesión.


Consultar o identificador da sesión

Podemos obter o identificador da sesión actual mediante:

session_id();

Por exemplo:

<?php
session_start();

echo session_id();
?>

Non debemos mostrar o identificador de sesión nunha aplicación real.

Neste exemplo faise unicamente con fins didácticos.


Borrar datos dunha sesión

Existen diferentes formas de eliminar información dunha sesión.

Eliminar unha variable

Podemos eliminar unha variable concreta mediante:

unset($_SESSION['nome']);

Por exemplo:

<?php
session_start();

unset($_SESSION['nome']);
?>

Isto elimina unicamente esa variable.


Eliminar todas as variables

Podemos baleirar $_SESSION:

$_SESSION = [];

Tamén existe:

session_unset();

que elimina todas as variables da sesión actualmente cargadas.

Por exemplo:

<?php
session_start();

session_unset();
?>

Na práctica, a forma:

$_SESSION = [];

é moi clara e habitual.


Destruír a sesión

A función:

session_destroy();

elimina os datos almacenados no servidor asociados á sesión actual.

Por exemplo:

<?php
session_start();

session_destroy();
?>

Importante: session_destroy():

  • elimina os datos da sesión almacenados no servidor;
  • non elimina automaticamente o contido actual do array $_SESSION;
  • non elimina automaticamente a cookie de sesión do navegador.

Por iso, nun proceso de logout normalmente queremos realizar as tres operacións.


Pechar completamente unha sesión

Un exemplo básico para pechar a sesión dun usuario é:

 1 <?php
 2 session_start();
 3 
 4 // Eliminamos todas as variables de sesión.
 5 $_SESSION = [];
 6 
 7 // Eliminamos tamén a cookie que contén o ID da sesión.
 8 if (ini_get('session.use_cookies')) {
 9 $params = session_get_cookie_params();
10 
11 ```
12 setcookie(
13     session_name(),
14     '',
15     time() - 42000,
16     $params['path'],
17     $params['domain'],
18     $params['secure'],
19     $params['httponly']
20 );
21 ```
22 
23 }
24 
25 // Eliminamos os datos da sesión almacenados no servidor.
26 session_destroy();
27 ?>

Deste xeito:

  1. elimínanse as variables de $_SESSION;
  1. elimínase a cookie que identifica a sesión;
  1. elimínanse os datos gardados no servidor.

Nas aplicacións complexas poden existir peticións simultáneas, polo que a destrución inmediata dunha sesión debe xestionarse tendo en conta posibles condicións de carreira.

Máis información: session_destroy()


Seguridade das sesións

O identificador de sesión funciona como unha credencial temporal.

Se un atacante consegue obter un identificador de sesión válido podería intentar utilizalo para facerse pasar polo usuario.

Por iso debemos protexer correctamente a cookie da sesión.

Algunhas recomendacións son:

  • utilizar sempre HTTPS en produción;
  • activar session.cookie_secure;
  • activar session.cookie_httponly;
  • utilizar session.cookie_samesite;
  • activar session.use_only_cookies;
  • manter session.use_trans_sid desactivado;
  • activar session.use_strict_mode;
  • rexenerar o identificador de sesión cando cambian os privilexios do usuario;
  • implementar un tempo máximo de inactividade na propia aplicación cando sexa necesario.


Non enviar o SID pola URL

Non debemos utilizar URLs deste tipo:

https://exemplo.com/paxina.php?PHPSESSID=abc123...

O identificador podería aparecer:

  • no historial do navegador;
  • en logs do servidor;
  • en ferramentas de análise;
  • en capturas de pantalla;
  • en enlaces compartidos;
  • na cabeceira HTTP Referer en determinadas situacións.

Por iso PHP debe utilizar cookies para transportar o SID.

En PHP 8.5:

session.use_only_cookies = 1
session.use_trans_sid = 0


Fixación de sesión

Nun ataque de fixación de sesión un atacante intenta obrigar á vítima a utilizar un identificador de sesión previamente coñecido polo atacante.

Unha medida importante para reducir este risco é:

session.use_strict_mode = 1

Ademais, cando un usuario se autentica correctamente deberíase rexenerar o identificador da sesión.

Por exemplo:

<?php
session_start();

// Supoñemos que xa comprobamos usuario e contrasinal.

session_regenerate_id();

$_SESSION['usuario_id'] = $usuarioId;
$_SESSION['autenticado'] = true;
?>

É recomendable rexenerar o identificador antes de almacenar na nova sesión os privilexios obtidos tras a autenticación.

Máis información: session_regenerate_id()


Exemplo completo

Vexamos un exemplo moi simple utilizando tres páxinas.

Arquivo UD4_session_Ex1_paxina1.php

 1 <?php
 2 session_start();
 3 
 4 $_SESSION['nome'] = 'Angel';
 5 ?>
 6 
 7 <!DOCTYPE html>
 8 
 9 <html lang="gl">
10 <head>
11     <meta charset="UTF-8">
12     <title>Sesións - Páxina 1</title>
13 </head>
14 
15 <body>
16 
17 <h1>Asignouse o valor Angel á variable de sesión 'nome'.</h1>
18 
19 <h2>
20     <a href="UD4_session_Ex1_paxina2.php">
21         Ver o seu valor noutra páxina
22     </a>
23 </h2>
24 
25 </body>
26 </html>

Php session 1.jpg


Arquivo UD4_session_Ex1_paxina2.php

 1 <?php
 2 session_start();
 3 
 4 $nome = $_SESSION['nome'] ?? 'Non existe';
 5 ?>
 6 
 7 <!DOCTYPE html>
 8 
 9 <html lang="gl">
10 <head>
11     <meta charset="UTF-8">
12     <title>Sesións - Páxina 2</title>
13 </head>
14 
15 <body>
16 
17 <h1>
18     O valor da variable de sesión é:
19     <?= htmlspecialchars($nome, ENT_QUOTES, 'UTF-8') ?>
20 </h1>
21 
22 <p>
23     ID de sesión:
24     <?= htmlspecialchars(session_id(), ENT_QUOTES, 'UTF-8') ?>
25 </p>
26 
27 <p>
28     <strong>
29         O identificador móstrase unicamente con fins didácticos.
30         Nunha aplicación real non debería mostrarse ao usuario.
31     </strong>
32 </p>
33 
34 <h2>
35     <a href="UD4_session_Ex1_paxina3.php">
36         Borrar a sesión
37     </a>
38 </h2>
39 
40 </body>
41 </html>

Php session 2.jpg


Arquivo UD4_session_Ex1_paxina3.php

 1 <?php
 2 session_start();
 3 
 4 $_SESSION = [];
 5 
 6 if (ini_get('session.use_cookies')) {
 7 $params = session_get_cookie_params();
 8 
 9 ```
10 setcookie(
11     session_name(),
12     '',
13     time() - 42000,
14     $params['path'],
15     $params['domain'],
16     $params['secure'],
17     $params['httponly']
18 );
19 ```
20 
21 }
22 
23 session_destroy();
24 ?>
25 
26 <!DOCTYPE html>
27 
28 <html lang="gl">
29 <head>
30     <meta charset="UTF-8">
31     <title>Sesións - Páxina 3</title>
32 </head>
33 
34 <body>
35 
36 <h1>A sesión foi eliminada.</h1>
37 
38 <h2>
39     <a href="UD4_session_Ex1_paxina1.php">
40         Volver ao inicio
41     </a>
42 </h2>
43 
44 </body>
45 </html>

Php session 3.jpg


Comprobar a cookie da sesión

Podemos comprobar a cookie enviada por PHP utilizando as ferramentas de desenvolvemento do navegador.

Por exemplo, en Firefox ou Chrome:

  1. Abrir as Ferramentas de desenvolvemento.
  1. Acceder á sección de almacenamento ou aplicación.
  1. Seleccionar Cookies.
  1. Buscar a cookie chamada normalmente PHPSESSID.

Tamén podemos observar a cabeceira HTTP enviada polo servidor:

Set-Cookie: PHPSESSID=...

Nunha aplicación correctamente configurada mediante HTTPS poderemos atopar atributos como:

Secure
HttpOnly
SameSite=Lax

Non se debe copiar nin compartir o valor dunha cookie de sesión activa.


Ataques CSRF (Cross-Site Request Forgery)

Un ataque CSRF intenta conseguir que un navegador autenticado realice unha operación nun sitio web sen que o usuario teña intención de facela.

Por exemplo, un usuario podería estar autenticado en:

https://banco.exemplo

e visitar posteriormente unha páxina maliciosa que intente provocar unha petición contra ese sitio.

Como o navegador pode enviar automaticamente as cookies asociadas ao dominio, o servidor podería recibir unha petición acompañada dunha sesión válida.

Unha das técnicas máis utilizadas para protexer formularios é utilizar un token CSRF.


Xerar o token

Podemos utilizar random_bytes() para xerar un valor aleatorio criptograficamente seguro.

<?php
session_start();

if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>

O token almacénase na sesión do usuario.


Incorporar o token no formulario

Incluímos o token nun campo oculto:

<form action="procesar.php" method="post">

```
<!-- Outros campos do formulario -->

<input
    type="hidden"
    name="csrf_token"
    value="<?= htmlspecialchars(
        $_SESSION['csrf_token'],
        ENT_QUOTES,
        'UTF-8'
    ) ?>"
>

<input type="submit" value="Enviar">
```

</form>


Verificar o token

Ao recibir o formulario comprobamos que:

  • existe un token almacenado na sesión;
  • recibimos un token no formulario;
  • ambos coinciden.

Para comparar tokens é preferible utilizar hash_equals().

<?php
session_start();

if (
!isset($_SESSION['csrf_token'], $_POST['csrf_token']) ||
!is_string($_POST['csrf_token']) ||
!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])
) {
http_response_code(403);
exit('Acción non autorizada');
}

// O formulario é válido.
// Procesamos os datos...
?>

hash_equals() realiza a comparación intentando evitar que o tempo empregado na operación revele información sobre o token.


Invalidar o token

Se queremos que o token só poida utilizarse unha vez:

unset($_SESSION['csrf_token']);

Un novo formulario deberá xerar posteriormente un novo token.


SameSite e CSRF

Configurar:

session.cookie_samesite = Lax

ou, cando sexa posible:

session.cookie_samesite = Strict

axuda a reducir determinados ataques CSRF ao limitar o envío da cookie en peticións iniciadas dende outros sitios.

Porén, SameSite non debe considerarse un substituto universal dos tokens CSRF.

Nas operacións que modifican datos é recomendable combinar varias medidas:

  • utilizar métodos HTTP apropiados, especialmente POST;
  • utilizar tokens CSRF;
  • configurar correctamente SameSite;
  • comprobar os permisos do usuario;
  • utilizar HTTPS.


Novidades relacionadas coas sesións en PHP 8.5

PHP 8.5 incorpora soporte directo para cookies particionadas.

As seguintes funcións admiten agora a opción:

partitioned

entre as opcións da cookie:

  • session_start();
  • session_set_cookie_params();
  • session_get_cookie_params();
  • setcookie();
  • setrawcookie().

Por exemplo:

<?php
session_set_cookie_params([
    'lifetime' => 0,
    'path' => '/',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Lax',
    'partitioned' => true
]);

session_start();
?>

As cookies particionadas están pensadas principalmente para situacións nas que unha aplicación utiliza cookies nun contexto entre sitios.

Para unha aplicación web convencional normalmente:

session.cookie_partitioned = 0

é suficiente.

Máis información: Novidades de PHP 8.5


Resumo

  • As sesións permiten manter datos entre diferentes peticións HTTP.
  • Os datos das variables de sesión almacénanse normalmente no servidor.
  • O navegador conserva un identificador de sesión mediante unha cookie.
  • Para utilizar $_SESSION debemos iniciar ou recuperar a sesión mediante:

:

  session_start();
  • Para crear unha variable:

:

  $_SESSION['nome'] = 'Angel';
  • Para eliminar unha variable:

:

  unset($_SESSION['nome']);
  • Para eliminar todas as variables:

:

  $_SESSION = [];
  • Para destruír os datos da sesión:

:

  session_destroy();
  • session.gc_maxlifetime non representa un temporizador exacto de peche da sesión.
  • En aplicacións reais debemos protexer o identificador de sesión mediante HTTPS, cookies seguras e configuración apropiada.
  • Recoméndase activar:

:

  session.use_strict_mode = 1
  session.use_only_cookies = 1
  session.cookie_secure = 1
  session.cookie_httponly = 1
  session.cookie_samesite = Lax
  • O SID non debe enviarse mediante a URL.
  • Despois dunha autenticación ou dun cambio de privilexios é recomendable rexenerar o identificador mediante:

:

  session_regenerate_id();
  • Para protexer formularios fronte a CSRF podemos empregar tokens xerados con:

:

  bin2hex(random_bytes(32));

e comparalos mediante:

hash_equals();


Exercicios propostos


Referencias