
PHP reste le langage de programmation côté serveur le plus populaire. Utilisé par plus de 75% des applications dans le monde, PHP est aujourd’hui dans sa version 8. Cette dernière comme les précédentes apportent toujours de nouvelles fonctionnalités et des couches de sécurité supplémentaires.
Cependant, la sécurité de PHP se construit dès ses fonctionnalités historiques. Dans cet article, nous passerons en revue les bonnes pratiques pour sécuriser vos applications PHP. Nous reviendrons notamment sur les failles et attaques courantes ainsi que sur les défauts de configuration pouvant compromettre la sécurité de vos applications PHP.
Réduire la surface d’attaque d’une application web consiste à minimiser les points d’entrée vulnérables, en limitant les éléments susceptibles d’être exploités par des attaquants.
Cela inclut une gestion rigoureuse des configurations, des mises à jour régulières, la gestion des erreurs, la protection du code source et la sécurité par l’obscurité.
Un des aspects les plus cruciaux de la sécurité est de bien connaître les composants et la configuration de votre serveur web.
Voici quelques questions essentielles à vous poser :
Si vous ne pouvez pas répondre à ces questions de manière claire et précise, il vous manque des informations essentielles pour la sécurité de votre application.
En effet, il est crucial d’avoir une liste centralisée et à jour des composants de votre infrastructure. Cela vous permet de répondre à deux questions clés :
La configuration de la sécurité doit être un processus continu. Cela semble évident au début d’un projet, mais après plusieurs mois en production, cette tâche est souvent négligée.
Ainsi, il est essentiel de vérifier régulièrement vos configurations et vos mises à jour pour limiter les risques.
Les erreurs affichées sont précieuses pour le développement. Cependant, elles peuvent également exposer des informations sensibles lorsqu’elles sont visibles en production. Il est donc primordial de configurer les rapports d’erreurs de manière adéquate.
Voici trois paramètres importants du fichier php.ini :
Sur un environnement de développement, il est acceptable d’afficher les erreurs à l’écran, mais en production, cela doit être strictement interdit.
À la place, activez la journalisation des erreurs et choisissez soigneusement les types d’erreurs à enregistrer, particulièrement sur les sites à fort trafic, où les logs peuvent devenir volumineux.
Le paramètre error_reporting peut être configuré ainsi :
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT
En production, vous pouvez configurer display_errors à « Off » et log_errors à « On » pour un meilleur équilibre entre sécurité et débogage.
En savoir plus sur les configurations des rapports d’erreurs.
Par ailleurs, d’autres paramètres de sécurité dans php.ini doivent être revus régulièrement, comme :
Bien que la sécurité par l’obscurité ne soit pas une stratégie complète en soi, elle peut ralentir les attaquants et leur compliquer la tâche.
Par exemple :
expose_php dans le fichier php.ini : expose_php = Off. Ces techniques ne remplacent pas les mesures de sécurité fondamentales, mais elles compliquent la tâche des attaquants en rendant plus difficile la collecte d’informations exploitables.
Les injections SQL sont des attaques redoutables qui peuvent compromettre gravement la sécurité de votre application PHP. Heureusement, il est possible de s’en protéger en suivant quelques bonnes pratiques simples.
Le moyen le plus efficace de prévenir les injections SQL est d’utiliser les requêtes préparées, disponibles via l’extension PDO de PHP.
Les requêtes préparées permettent de séparer la structure de la requête SQL des données qui lui sont transmises, empêchant ainsi l’exécution de code SQL malveillant.
Voici un exemple d’implémentation avec PDO :
$pdo = new PDO('mysql:host=localhost;dbname=test', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username');
$stmt->bindParam(':username', $username);
$stmt->execute();
Les paramètres envoyés dans la requête sont traités comme des données, et non comme du code, bloquant ainsi toute tentative d’injection malveillante.
Même avec des requêtes préparées, il est important de valider et assainir les entrées utilisateur. Utilisez des fonctions adaptées à vos besoins pour vérifier le type et le format des données avant de les utiliser dans une requête SQL.
Cela inclut l’utilisation de filtres PHP tels que filter_var() pour valider des adresses email ou des entiers.
L’exécution de commandes via PHP peut être extrêmement puissante, mais elle présente aussi un risque élevé si des précautions ne sont pas prises.
Une mauvaise gestion peut permettre à des attaquants d’injecter des commandes malveillantes sur votre serveur, compromettant ainsi la sécurité de votre application.
Voici les bonnes pratiques à suivre.
La première mesure pour limiter les risques d’injection de commandes est de désactiver les fonctions PHP potentiellement dangereuses.
Cela peut être fait via la directive disable_functions dans le fichier php.ini. Une fois ces fonctions désactivées, elles ne pourront pas être exploitées, même par un attaquant qui réussirait à injecter du code PHP via une autre faille de sécurité.
Voici un exemple de configuration :
disable_functions = show_source, exec, shell_exec, system, passthru, proc_open, popen, curl_exec, curl_multi_exec, parse_ini_file, show_source
Ces fonctions sont couramment utilisées pour exécuter des commandes système, ouvrir des processus ou accéder à des informations sensibles. Leur désactivation empêche les abus en cas de faille.
Si vous devez absolument utiliser des fonctions permettant l’exécution de commandes (comme exec() ou system()), vous devez être extrêmement vigilant quant aux paramètres qui leur sont transmis.
Validez toutes les données d’entrée en utilisant des listes blanches (whitelists), c’est-à-dire, en acceptant uniquement des valeurs pré-approuvées. Cela réduit la probabilité qu’une entrée malveillante soit exécutée comme une commande.
Lorsque vous exécutez des commandes, il est essentiel d’échapper correctement les arguments pour éviter les tentatives d’injection.
PHP fournit des fonctions natives pour sécuriser l’exécution des commandes.
Ces fonctions permettent d’éviter que des utilisateurs malveillants n’injectent des commandes supplémentaires ou ne modifient la commande exécutée via des entrées mal formées.
Les détournements de sessions reposent souvent sur l’exploitation des identifiants de session pour accéder à la session d’un utilisateur légitime.
Une attaque courante est la fixation de session, où l’attaquant force la victime à utiliser un identifiant de session connu de l’attaquant, pour ensuite exploiter cette session une fois que la victime s’est connectée.
Pour prévenir ce type d’attaques, il est essentiel de suivre plusieurs bonnes pratiques.
L’usage des identifiants de session dans les URL facilite la capture ou la réutilisation de ces informations par des attaquants, notamment via des attaques de type Man in the Middle.
Pour éviter cela, configurez PHP pour n’accepter les identifiants de session que via des cookies. Cela peut être activé en définissant le paramètre session.use_only_cookies dans le fichier php.ini :
session.use_only_cookies = 1
Les cookies de session doivent être protégés contre les attaques telles que le cross-site scripting (XSS).
Deux mesures importantes à prendre :
Ces paramètres peuvent être activés dans le fichier php.ini :
session.cookie_httponly = 1
session.cookie_secure = 1
Les identifiants de session générés doivent être suffisamment complexes pour rendre leur prédiction difficile. En augmentant l’entropie des identifiants, vous réduisez les risques d’attaques brute force.
Sur les systèmes Linux, il est recommandé d’utiliser le fichier spécial /dev/urandom pour générer des identifiants de session avec un niveau d’entropie élevé :
session.entropy_file = /dev/urandom
Une mesure cruciale pour limiter les attaques de fixation de session est de régénérer l’identifiant de session lorsque l’utilisateur effectue une action sensible, telle que la connexion.
Cela empêche un attaquant d’exploiter une session fixée avant que l’utilisateur ne se soit authentifié.
PHP fournit une fonction simple pour régénérer l’identifiant de session :
session_regenerate_id(true); // true pour supprimer l'ancien identifiant
Il est recommandé de régénérer l’identifiant à des moments critiques, comme :
Pour renforcer davantage la sécurité de vos sessions, plusieurs stratégies complémentaires peuvent être mises en place :
if (!isset($_SESSION['user_agent'])) {
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
} elseif ($_SESSION['user_agent'] !== $_SERVER['HTTP_USER_AGENT']) {
die("User-Agent mismatch. Possible session hijacking attempt.");
}
Protéger votre application PHP contre les attaques XSS (Cross-Site Scripting) est essentiel, mais relativement simple si vous suivez des bonnes pratiques rigoureuses.
Le principe fondamental à appliquer est : filtrer les entrées et échapper les sorties.
Chaque donnée provenant de l’utilisateur, qu’elle provienne de formulaires, d’URL (GET), de requêtes POST, de cookies ou même des headers HTTP, doit être validée et filtrée avant d’être traitée par votre application. Cela inclut les champs cachés et toutes les entrées de l’utilisateur, qu’elles soient visibles ou non.
filter_var() pour assurer que les données correspondent à vos attentes (par exemple, un email, un entier ou une URL).htmlspecialchars() pour neutraliser les caractères spéciaux comme « <« , « > », et « & ».Ces mesures simples peuvent déjà empêcher la plupart des attaques XSS classiques.
Une protection supplémentaire contre les attaques XSS peut être implémentée via les headers HTTP. Des headers comme Content-Security-Policy (CSP) peuvent limiter l’exécution de scripts non approuvés ou détecter des attaques potentielles.
En effet Content-Security-Policy permet de définir quelles sources de scripts sont approuvées et autorisées à s’exécuter. Par défaut, il empêche l’exécution de scripts injectés via XSS.
Les cookies ne doivent jamais être utilisés pour stocker des informations sensibles, car ils sont vulnérables à des manipulations par l’utilisateur ou des attaques comme le XSS.
Voici quelques bonnes pratiques pour sécuriser l’utilisation des cookies :
Exemple d’attributs pour un cookie :
setcookie('user_session', $value, [
'expires' => time() + 3600,
'path' => '/',
'domain' => 'example.com',
'secure' => true, // Cookie envoyé uniquement via HTTPS
'httponly' => true, // Inaccessible via JavaScript
'samesite' => 'Strict' // Empêche les envois de cookies par des requêtes cross-site
]);
L’upload de fichiers est une fonctionnalité particulièrement sensible. Si elle est mal sécurisée, elle permet à des attaquants d’envoyer des fichiers malveillants sur votre serveur, y compris des scripts PHP, pouvant entraîner des prises de contrôle complètes du serveur.
Il est donc essentiel de mettre en place une stratégie de sécurité robuste.
Ne permettez l’upload de fichiers qu’aux utilisateurs ayant préalablement été authentifiés. Cela limite les tentatives d’attaques provenant d’utilisateurs non fiables ou anonymes.
Autorisez uniquement les types de fichiers strictement nécessaires à vos fonctionnalités (ex. : images, documents).
Mettez en place une liste blanche des extensions de fichiers et des formats acceptés, tels que .jpg, .png, ou .pdf. Refusez tout autre format.
Dans le répertoire où sont stockés les fichiers uploadés, placez un fichier .htaccess pour empêcher l’exécution de scripts malveillants.
Vous pouvez limiter l’accès uniquement aux types de fichiers autorisés :
deny from all
<Files ~ ".(gif|jpe?g|png|pdf)$">
order deny,allow
allow from all
</Files>
Cette configuration empêche l’exécution de fichiers PHP ou tout autre script qui pourrait être téléchargé de manière malveillante.
Ne vous fiez pas uniquement à l’extension de fichier pour valider son type. Utilisez une validation stricte du type MIME, en vérifiant que le type du fichier correspond à un des formats autorisés.
Par exemple, pour valider une image, utilisez des fonctions comme mime_content_type() ou getimagesize().
chmod() pour désactiver les permissions d’exécution, empêchant ainsi tout code d’être exécuté.Vous pouvez configurer la taille maximale des fichiers à l’échelle globale via le fichier php.ini, en ajustant le paramètre upload_max_filesize.
Si vous souhaitez définir une limite plus stricte pour un formulaire particulier, vous pouvez utiliser le paramètre MAX_FILE_SIZE dans votre formulaire HTML. Cependant, cette limite doit toujours être vérifiée côté serveur, car elle peut être modifiée par l’utilisateur avant la soumission du formulaire.
Exemple de vérification côté serveur :
if ($_FILES['file']['size'] > 1048576) { // 1MB
die("Fichier trop volumineux.");
}
Les attaques CSRF exploitent la confiance qu’un site accorde à un utilisateur authentifié, en forçant celui-ci à exécuter des actions non désirées. Pour se protéger :
La première étape pour réduire les risques liés aux attaques CSRF est de s’assurer que toutes les actions impliquant des modifications d’état (comme la création, la modification ou la suppression de données) sont effectuées via des requêtes POST, et non via des requêtes GET.
Bien que cela ne supprime pas complètement le risque d’attaques CSRF, cela réduit les attaques de base où un attaquant pourrait exploiter une requête GET (comme un simple clic sur un lien malveillant) pour déclencher une action.
La méthode la plus efficace pour se protéger des attaques CSRF est l’utilisation de jetons CSRF.
Ces jetons sont des valeurs uniques générées par le serveur et associées à la session de l’utilisateur. Lorsqu’un formulaire est soumis, le serveur compare le jeton envoyé avec celui stocké dans la session de l’utilisateur pour valider l’authenticité de la requête. Si les jetons ne correspondent pas, l’action est rejetée.
Voici comment implémenter un jeton CSRF simple.
Générer un jeton à la création d’un formulaire :
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
Ajouter le jeton au formulaire en tant que champ caché :
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<!-- autres champs -->
<button type="submit">Soumettre</button>
</form>
Vérifier le jeton côté serveur lors de la soumission :
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("Invalid CSRF token");
}
Si vous travaillez avec un framework, de nombreuses solutions modernes incluent déjà des protections CSRF intégrées.
Par exemple, Laravel, Symfony ou Django génèrent et vérifient automatiquement les tokens CSRF dans les formulaires.
Si vous ne travaillez pas avec un framework offrant cette protection nativement, vous pouvez soit implémenter les jetons vous-même (ce qui est déconseillé), soit utiliser des bibliothèques tierces.
Adopter des pratiques de sécurité robustes est essentiel pour protéger les applications PHP contre les menaces et les vulnérabilités.
Les diverses attaques courantes peuvent être anticipées et réduites par une mise en œuvre systématique de bonnes pratiques de sécurité. Toutefois, ces mesures de sécurité peuvent être complexes et chronophages à mettre en œuvre manuellement dans chaque projet.
C’est ici que les frameworks modernes apportent une solution avantageuse pour les développeurs. En effet, intégrer un framework offre de nombreux avantages :
Auteur : Amin TRAORÉ – CMO @Vaadata