|
|
@@ -0,0 +1,267 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+/**
|
|
|
+ * Classe secureSession
|
|
|
+ *
|
|
|
+ * Gère le démarrage sécurisé des sessions avec les paramètres de sécurité recommandés.
|
|
|
+ * Cette classe centralise la configuration de sécurité des sessions PHP.
|
|
|
+ */
|
|
|
+class secureSession
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * Démarre une session sécurisée avec les paramètres de sécurité recommandés.
|
|
|
+ *
|
|
|
+ * @param array $options Options supplémentaires pour la configuration de session
|
|
|
+ * @return bool True si la session a été démarrée avec succès
|
|
|
+ */
|
|
|
+ public static function start(array $options = []): bool
|
|
|
+ {
|
|
|
+ // Si une session est déjà active, ne pas en redémarrer une nouvelle
|
|
|
+ if (session_status() === PHP_SESSION_ACTIVE) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Configuration de sécurité des cookies de session
|
|
|
+ $cookieParams = [
|
|
|
+ 'lifetime' => $options['lifetime'] ?? 0, // Session cookie (expire à la fermeture du navigateur)
|
|
|
+ 'path' => $options['path'] ?? '/',
|
|
|
+ 'domain' => $options['domain'] ?? '',
|
|
|
+ 'secure' => $options['secure'] ?? self::isHttps(), // HTTPS uniquement si disponible
|
|
|
+ 'httponly' => true, // Empêche l'accès JavaScript au cookie de session
|
|
|
+ 'samesite' => $options['samesite'] ?? 'Lax' // Protection CSRF (Lax ou Strict)
|
|
|
+ ];
|
|
|
+
|
|
|
+ // Applique les paramètres des cookies de session
|
|
|
+ session_set_cookie_params($cookieParams);
|
|
|
+
|
|
|
+ // Configuration supplémentaire de sécurité
|
|
|
+ ini_set('session.use_strict_mode', '1'); // Refuse les IDs de session non initialisés
|
|
|
+ ini_set('session.use_only_cookies', '1'); // Utilise uniquement les cookies, pas l'URL
|
|
|
+ ini_set('session.cookie_httponly', '1'); // Protection XSS
|
|
|
+ ini_set('session.cookie_samesite', $cookieParams['samesite']); // Protection CSRF
|
|
|
+
|
|
|
+ // Si HTTPS est disponible, force les cookies sécurisés
|
|
|
+ if ($cookieParams['secure']) {
|
|
|
+ ini_set('session.cookie_secure', '1');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Utilise un algorithme de hachage fort pour les IDs de session
|
|
|
+ ini_set('session.hash_function', 'sha256');
|
|
|
+ ini_set('session.hash_bits_per_character', '5');
|
|
|
+
|
|
|
+ // Régénération de l'ID de session pour prévenir la fixation de session
|
|
|
+ if (isset($options['session_name'])) {
|
|
|
+ session_name($options['session_name']);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Démarre la session
|
|
|
+ $started = session_start();
|
|
|
+
|
|
|
+ // Régénère l'ID de session périodiquement pour plus de sécurité
|
|
|
+ if ($started && !isset($_SESSION['_session_created'])) {
|
|
|
+ $_SESSION['_session_created'] = time();
|
|
|
+ $_SESSION['_session_ip'] = self::getClientIP();
|
|
|
+ $_SESSION['_session_user_agent'] = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Régénère l'ID toutes les 30 minutes
|
|
|
+ if ($started && isset($_SESSION['_session_created'])) {
|
|
|
+ $sessionAge = time() - $_SESSION['_session_created'];
|
|
|
+
|
|
|
+ if ($sessionAge > 1800) { // 30 minutes
|
|
|
+ self::regenerateId();
|
|
|
+ $_SESSION['_session_created'] = time();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Validation de l'intégrité de la session
|
|
|
+ if ($started && !self::validateSession()) {
|
|
|
+ self::destroy();
|
|
|
+ return self::start($options); // Redémarre une nouvelle session propre
|
|
|
+ }
|
|
|
+
|
|
|
+ return $started;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Régénère l'ID de session de manière sécurisée.
|
|
|
+ *
|
|
|
+ * @return bool True si la régénération a réussi
|
|
|
+ */
|
|
|
+ public static function regenerateId(): bool
|
|
|
+ {
|
|
|
+ if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return session_regenerate_id(true); // true = supprime l'ancienne session
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Détruit complètement une session de manière sécurisée.
|
|
|
+ *
|
|
|
+ * @return bool True si la session a été détruite avec succès
|
|
|
+ */
|
|
|
+ public static function destroy(): bool
|
|
|
+ {
|
|
|
+ if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Efface toutes les variables de session
|
|
|
+ $_SESSION = [];
|
|
|
+
|
|
|
+ // Détruit le cookie de session
|
|
|
+ if (ini_get('session.use_cookies')) {
|
|
|
+ $params = session_get_cookie_params();
|
|
|
+ setcookie(
|
|
|
+ session_name(),
|
|
|
+ '',
|
|
|
+ time() - 42000,
|
|
|
+ $params['path'],
|
|
|
+ $params['domain'],
|
|
|
+ $params['secure'],
|
|
|
+ $params['httponly']
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Détruit la session
|
|
|
+ return session_destroy();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Valide l'intégrité de la session en vérifiant l'IP et le User-Agent.
|
|
|
+ *
|
|
|
+ * @return bool True si la session est valide
|
|
|
+ */
|
|
|
+ private static function validateSession(): bool
|
|
|
+ {
|
|
|
+ // Vérification de l'IP (optionnelle, peut causer des problèmes avec certains FAI)
|
|
|
+ if (isset($_SESSION['_session_ip'])) {
|
|
|
+ $currentIP = self::getClientIP();
|
|
|
+ if ($_SESSION['_session_ip'] !== $currentIP) {
|
|
|
+ // Option stricte : retourner false
|
|
|
+ // Pour l'instant, on log seulement
|
|
|
+ error_log("Session IP mismatch: {$_SESSION['_session_ip']} vs $currentIP");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Vérification du User-Agent
|
|
|
+ if (isset($_SESSION['_session_user_agent'])) {
|
|
|
+ $currentUA = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
|
|
+ if ($_SESSION['_session_user_agent'] !== $currentUA) {
|
|
|
+ error_log("Session User-Agent mismatch");
|
|
|
+ return false; // Plus strict pour le User-Agent
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Détecte si la connexion est en HTTPS.
|
|
|
+ *
|
|
|
+ * @return bool True si HTTPS est activé
|
|
|
+ */
|
|
|
+ private static function isHttps(): bool
|
|
|
+ {
|
|
|
+ if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'on') {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Récupère l'IP réelle du client (gère les proxies).
|
|
|
+ *
|
|
|
+ * @return string L'adresse IP du client
|
|
|
+ */
|
|
|
+ private static function getClientIP(): string
|
|
|
+ {
|
|
|
+ $headers = [
|
|
|
+ 'HTTP_CF_CONNECTING_IP', // Cloudflare
|
|
|
+ 'HTTP_X_FORWARDED_FOR',
|
|
|
+ 'HTTP_X_FORWARDED',
|
|
|
+ 'HTTP_X_CLUSTER_CLIENT_IP',
|
|
|
+ 'HTTP_FORWARDED_FOR',
|
|
|
+ 'HTTP_FORWARDED',
|
|
|
+ 'HTTP_CLIENT_IP',
|
|
|
+ 'REMOTE_ADDR'
|
|
|
+ ];
|
|
|
+
|
|
|
+ foreach ($headers as $header) {
|
|
|
+ if (!empty($_SERVER[$header])) {
|
|
|
+ $ip = $_SERVER[$header];
|
|
|
+ // Si plusieurs IPs (proxy chain), prendre la première
|
|
|
+ if (strpos($ip, ',') !== false) {
|
|
|
+ $ips = explode(',', $ip);
|
|
|
+ $ip = trim($ips[0]);
|
|
|
+ }
|
|
|
+ // Valider que c'est une IP valide
|
|
|
+ if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
|
|
+ return $ip;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Configure le timeout d'inactivité de session.
|
|
|
+ *
|
|
|
+ * @param int $minutes Nombre de minutes d'inactivité avant expiration
|
|
|
+ * @return bool True si le timeout est configuré
|
|
|
+ */
|
|
|
+ public static function setInactivityTimeout(int $minutes): bool
|
|
|
+ {
|
|
|
+ if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ $timeout = $minutes * 60;
|
|
|
+
|
|
|
+ if (isset($_SESSION['_session_last_activity'])) {
|
|
|
+ $elapsed = time() - $_SESSION['_session_last_activity'];
|
|
|
+
|
|
|
+ if ($elapsed > $timeout) {
|
|
|
+ self::destroy();
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ $_SESSION['_session_last_activity'] = time();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Récupère les informations de sécurité de la session actuelle.
|
|
|
+ *
|
|
|
+ * @return array Informations de sécurité
|
|
|
+ */
|
|
|
+ public static function getSecurityInfo(): array
|
|
|
+ {
|
|
|
+ if (session_status() !== PHP_SESSION_ACTIVE) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'session_id' => session_id(),
|
|
|
+ 'session_name' => session_name(),
|
|
|
+ 'created' => $_SESSION['_session_created'] ?? null,
|
|
|
+ 'last_activity' => $_SESSION['_session_last_activity'] ?? null,
|
|
|
+ 'ip' => $_SESSION['_session_ip'] ?? null,
|
|
|
+ 'is_https' => self::isHttps(),
|
|
|
+ 'cookie_params' => session_get_cookie_params()
|
|
|
+ ];
|
|
|
+ }
|
|
|
+}
|