| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- <?php
- /**
- * Classe securityHeaders
- *
- * Gère les en-têtes HTTP de sécurité
- */
- class securityHeaders
- {
- /**
- * Applique tous les headers de sécurité recommandés
- *
- * @param array $options Options de configuration
- */
- public static function apply(array $options = []): void
- {
- $defaults = [
- 'csp' => true,
- 'xframe' => true,
- 'xcontent' => true,
- 'xss' => true,
- 'referrer' => true,
- 'hsts' => true,
- 'permissions' => true
- ];
- $options = array_merge($defaults, $options);
- // Ne pas envoyer si les headers sont déjà envoyés
- if (headers_sent()) {
- return;
- }
- if ($options['xframe']) {
- self::setXFrameOptions();
- }
- if ($options['xcontent']) {
- self::setXContentTypeOptions();
- }
- if ($options['xss']) {
- self::setXSSProtection();
- }
- if ($options['referrer']) {
- self::setReferrerPolicy();
- }
- if ($options['hsts'] && self::isHTTPS()) {
- self::setHSTS();
- }
- if ($options['permissions']) {
- self::setPermissionsPolicy();
- }
- if ($options['csp']) {
- self::setCSP();
- }
- }
- /**
- * Applique les headers spécifiques pour la page de login
- */
- public static function applyForLogin(): void
- {
- if (headers_sent()) {
- return;
- }
- // Headers de base
- self::setXFrameOptions('DENY'); // Plus strict pour le login
- self::setXContentTypeOptions();
- self::setXSSProtection();
- self::setReferrerPolicy('no-referrer');
- if (self::isHTTPS()) {
- self::setHSTS();
- }
- // CSP strict pour le login
- self::setLoginCSP();
- // Cache control - Ne pas mettre en cache la page de login
- self::setNoCache();
- }
- /**
- * X-Frame-Options : Empêche le clickjacking
- *
- * @param string $value DENY, SAMEORIGIN, ou ALLOW-FROM uri
- */
- public static function setXFrameOptions(string $value = 'SAMEORIGIN'): void
- {
- header("X-Frame-Options: $value");
- }
- /**
- * X-Content-Type-Options : Empêche le MIME sniffing
- */
- public static function setXContentTypeOptions(): void
- {
- header('X-Content-Type-Options: nosniff');
- }
- /**
- * X-XSS-Protection : Protection XSS du navigateur (legacy)
- */
- public static function setXSSProtection(): void
- {
- header('X-XSS-Protection: 1; mode=block');
- }
- /**
- * Referrer-Policy : Contrôle les informations de referrer
- *
- * @param string $policy Politique de referrer
- */
- public static function setReferrerPolicy(string $policy = 'strict-origin-when-cross-origin'): void
- {
- header("Referrer-Policy: $policy");
- }
- /**
- * Strict-Transport-Security : Force HTTPS
- *
- * @param int $maxAge Durée en secondes (défaut: 1 an)
- * @param bool $includeSubDomains Inclure les sous-domaines
- */
- public static function setHSTS(int $maxAge = 31536000, bool $includeSubDomains = true): void
- {
- $header = "Strict-Transport-Security: max-age=$maxAge";
- if ($includeSubDomains) {
- $header .= '; includeSubDomains';
- }
- header($header);
- }
- /**
- * Permissions-Policy : Contrôle l'accès aux APIs du navigateur
- */
- public static function setPermissionsPolicy(): void
- {
- header("Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=()");
- }
- /**
- * Content-Security-Policy : Politique de sécurité du contenu
- *
- * @param array $directives Directives CSP personnalisées
- */
- public static function setCSP(array $directives = []): void
- {
- $defaults = [
- "default-src" => "'self'",
- "script-src" => "'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com",
- "style-src" => "'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com",
- "font-src" => "'self' https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com",
- "img-src" => "'self' data: https:",
- "connect-src" => "'self'",
- "frame-ancestors" => "'self'",
- "form-action" => "'self'",
- "base-uri" => "'self'"
- ];
- $directives = array_merge($defaults, $directives);
- $csp = [];
- foreach ($directives as $directive => $value) {
- $csp[] = "$directive $value";
- }
- header("Content-Security-Policy: " . implode('; ', $csp));
- }
- /**
- * CSP strict pour la page de login
- */
- public static function setLoginCSP(): void
- {
- $nonce = self::generateNonce();
- $directives = [
- "default-src" => "'self'",
- "script-src" => "'self' 'nonce-{$nonce}' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com",
- "style-src" => "'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com https://fonts.googleapis.com",
- "font-src" => "'self' https://fonts.gstatic.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com",
- "img-src" => "'self' data:",
- "connect-src" => "'self'",
- "frame-ancestors" => "'none'",
- "form-action" => "'self'",
- "base-uri" => "'self'",
- "object-src" => "'none'"
- ];
- $csp = [];
- foreach ($directives as $directive => $value) {
- $csp[] = "$directive $value";
- }
- header("Content-Security-Policy: " . implode('; ', $csp));
- }
- /**
- * Headers pour désactiver le cache
- */
- public static function setNoCache(): void
- {
- header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
- header('Cache-Control: post-check=0, pre-check=0', false);
- header('Pragma: no-cache');
- header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
- }
- /**
- * Génère un nonce pour CSP
- *
- * @return string Nonce encodé en base64
- */
- public static function generateNonce(): string
- {
- if (!isset($_SESSION['csp_nonce'])) {
- $_SESSION['csp_nonce'] = base64_encode(random_bytes(16));
- }
- return $_SESSION['csp_nonce'];
- }
- /**
- * Retourne le nonce actuel pour l'utiliser dans les scripts inline
- *
- * @return string Nonce pour attribut nonce=""
- */
- public static function getNonce(): string
- {
- return $_SESSION['csp_nonce'] ?? self::generateNonce();
- }
- /**
- * Vérifie si la connexion est HTTPS
- *
- * @return bool
- */
- private static function isHTTPS(): bool
- {
- if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
- return true;
- }
- if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
- return true;
- }
- if (isset($_SERVER['HTTP_CF_VISITOR'])) {
- $visitor = json_decode($_SERVER['HTTP_CF_VISITOR'], true);
- if (isset($visitor['scheme']) && $visitor['scheme'] === 'https') {
- return true;
- }
- }
- if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] === '443') {
- return true;
- }
- return false;
- }
- /**
- * Applique les headers de sécurité pour les API JSON
- */
- public static function applyForAPI(): void
- {
- if (headers_sent()) {
- return;
- }
- header('Content-Type: application/json; charset=utf-8');
- self::setXContentTypeOptions();
- self::setNoCache();
- // CORS si nécessaire
- // self::setCORS();
- }
- /**
- * Configure CORS (Cross-Origin Resource Sharing)
- *
- * @param array $allowedOrigins Origines autorisées
- * @param array $allowedMethods Méthodes autorisées
- */
- public static function setCORS(array $allowedOrigins = [], array $allowedMethods = ['GET', 'POST']): void
- {
- $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
- // Si pas de restriction, n'autoriser que same-origin
- if (empty($allowedOrigins)) {
- return;
- }
- if (in_array($origin, $allowedOrigins) || in_array('*', $allowedOrigins)) {
- header("Access-Control-Allow-Origin: $origin");
- header('Access-Control-Allow-Credentials: true');
- header('Access-Control-Allow-Methods: ' . implode(', ', $allowedMethods));
- header('Access-Control-Allow-Headers: Content-Type, Authorization, X-CSRF-Token');
- }
- }
- }
|