• 10 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Mis à jour le 27/11/2018

Manipulez les données non fiables avec prudence

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Grâce à la partie précédente, vous avez compris l’importance de la gestion sécurisée de l’identité de vos utilisateurs. Vous avez appris à mettre en place de bonnes pratiques pour traiter cet aspect essentiel de la sécurité des applications.

Il est également capital pour vos applications de traiter les données échangées ou stockées avec une grande prudence, car elles portent en elles de nombreux dangers. Les sous-estimer serait une erreur colossale. C’est pourquoi je vous propose, dans cette partie, d'étudier :

  1. Les mécanismes de manipulation de données non fiables ;

  2. Les enjeux de la protection des données ;

  3. Le traitement sécurisé des bases de données ;

  4. La gestion des erreurs et des journaux d'historisation.

Commençons, dans ce chapitre, avec les mécanismes de manipulation des données non fiables.

Méfiez-vous de toutes les données manipulées

Lorsque l'on parle de données non fiables, nous avons souvent en tête l'interaction avec l'utilisateur. Or, ce n'est pas la seule source de données pouvant poser problème. Toutes données dynamiques, quelle que soit leur provenance, doivent être considérées comme non fiables.

Exemple d'attaques par injection SQL

Imaginons l'exemple d'une application web avec une barre de recherche sous forme d’un formulaire. L’utilisateur rentre sa recherche sous forme de chaînes de caractères, l’application utilise le langage SQL pour la transformer en requêtes sur la base de données, puis l’application affiche les résultats.

Au niveau de l’application web, on trouve donc un code équivalent à celui-ci :

String requete = “SELECT * FROM table WHERE nom LIKE “ + parametre”;

Si tout se passe bien, qu'un utilisateur rentre par exemple,  le nom de son chien. L’application web traite la demande, envoie la requête SQL suivante à la base de données :

SELECT * FROM ‘tablechien’ WHERE nom LIKE ‘medor’;

Et tout se passe bien.

Imaginons maintenant qu’un utilisateur malveillant entre dans le champ de recherche quelque chose comme ceci :

medor’; DROP TABLE ’tablechien

La requete SQL effectivement traduite sera :

SELECT * FROM tablechien WHERE nom LIKE medor; 
DROP TABLE tablechien;

Pour ceux d’entre vous familiers avec le langage SQL, l’ajout de  DROP TABLE  supprime simplement la table de la base de données. Ce mécanisme est une injection SQL.

Le problème sous-jacent ici est que la base de données manque d’informations de contexte. Eh oui, le serveur de base de données ne sait pas faire la distinction entre des données entrées par l'utilisateur (comme le nom du chien ici) et le code pur. C’est cette raison essentielle qui rend les données utilisateur non fiables.

Cet effort de vigilance constant sur les données non fiables vous permet de réduire significativement le risque encouru face à des menaces d’injection. On en distingue 2 grands ensembles : les injections côté serveur et les injections côté client.

Les injections côté serveur

Nous allons voir 2 exemples d’injection côté serveur et les moyens pour s’en prémunir.

Attaque par injection de commande

Quels sont les risques ?

Comme son nom l’indique, cette attaque profite d’un site mal protégé pour injecter des commandes directement dans l’application. Prenons ce script PHP qui s'exécute à l’adresse :  http://www.scriptphp.php?nomdefichier  . Ce dernier prend le paramètre de l’URL et l’inscrit à l’écran.

<?php
$filename=$_GET['filename'];
print($filename\n);
?>

Imaginons que l’on envoie l’URL suivante au serveur :

http://www.scriptphp.php?filename=monfichier.txt;rm * -rf

La console qui exécute les commandes va comprendre le  ;  comme une commande, et l'exécuter comme telle. En l'occurrence, la commande unix  rm * -rf  formate le contenu du répertoire en cours.

Protégez-vous de ce type d'attaques

Pour se prémunir de ce genre de désagrément, on ne doit jamais faire confiance à ce que l’utilisateur envoie comme données, et s’assurer que le juste contexte est préservé. Pour ce faire, on doit échapper l’intégralité des données soumises par l'utilisateur.

Par exemple, en PHP, la fonction  escapeshellcmd()  échappe tous les caractères de la chaîne de caractères soumise qui pourraient avoir une signification spéciale dans une commande Shell.

Les caractères suivants seront échappés :  &#;`|*?~<>^()[]{}$\,  \x0A  et  \xFF.

'  et  "  ne sont échappés que s'ils ne sont pas par paire. Sous Windows, tous ces caractères ainsi que  %  et  !  sont remplacés par un espace.

Rappelez-vous donc que pour amoindrir ce genre de vulnérabilités, le bon contexte doit  être préservé jusqu'à l'exécution. On peut le faire en encodant correctement les données soumises.

Attaque par injection SQL

Quels sont les risques ?

L'exemple que nous avons vu en introduction est simple mais efficace :  un formulaire HTML non sécurisé permet de soumettre des données non pertinentes comme des requêtes SQL. Cela a pour conséquences la modification ou bien encore la suppression de données. Vu les conséquences désastreuses que peut engendrer une injection SQL, vous comprenez pourquoi cette dernière est considérée comme une des failles de sécurité les plus répandues du Web.

Un autre exemple d’injection SQL pourrait être l’utilisation de clauses booléennes. Prenons cet exemple en C#:

string userName = ctx.getAuthenticatedUserName();
string query = "SELECT * FROM items WHERE owner = "'" 
+ userName + "' AND itemname = '"  
+ ItemName.Text + "'";
sda = new SqlDataAdapter(query, conn);
DataTable dt = new DataTable();
sda.Fill(dt);

Ce code construit la requête SQL suivante :

SELECT * FROM items
WHERE owner = 
AND itemname = ;

Toutefois, comme la requête est construite de manière dynamique en concaténant une base constante avec une entrée utilisateur, le contexte n’est pas conservé jusqu'à l'exécution. En d’autres termes, à un moment on perd la trace de ce qui est du code pur, de ce qui est une entrée utilisateur.

Par exemple, si l'utilisateur malveillant au nom de  michel  entre  name' OR '1'='1'  pour  itemame  , alors la requête devient :

SELECT * FROM items
WHERE owner = 'michel'
AND itemname = 'name' OR '1'='1';

L’ajout de la condition  OR '1'='1'  engendre une clause  WHERE  toujours vraie. Elle correspond donc à une requête beaucoup plus simple :

SELECT * FROM items;

Cette simplification permet à l'utilisateur malveillant de dévoyer le fonctionnement initial en affichant toutes les données de la table sans égard à une utilisation spécifique.

Ces quelques exemples (il y en a d’autres) exploitent la faiblesse des applications web : le contexte de la donnée n’est pas toujours maîtrisé de bout en bout. Voici quelques lignes de défense pour vous protéger des attaques par injection SQL.

Contraignez les entrées utilisateurs

Dans vos développements, arrangez-vous pour fixer le format, type et taille des champs de formulaires. Par exemple, une adresse email doit respecter un format spécifique, un nom ne contenir que des lettres, etc.

Utiliser des requêtes préparées.

Le principe de préparer les requêtes pour ensuite les utiliser. Ainsi, au lieu de recalculer la requête à chaque fois qu’on lui envoie, la base de données va la calculer une seule fois. Les fois suivantes, la base de données n’aura qu’à les exécuter. Un des avantages de cette technique, c’est de ne plus traiter les données comme faisant partie de la requête, mais vraiment comme des données.

Lors de la préparation d’une requête, la base de données crée en réalité des cases dans lesquelles elle va lier les valeurs qu’on lui donne. On remet donc du contexte dans l'exécution des requêtes.

Par exemple, partons d’une requête de sélection simple :

SELECT car_name, car_type FROM car WHERE car_id = 1

Cette requête peut être découpée en deux parties :

  • La partie fixe est celle que l’on va envoyer à la base de données pour préparer l’exécution ;

  • La partie variable est celle qui va changer en fonction de la voiture qu’on veut charger. Il s’agit de 1, c’est cette valeur que nous allons lier.

<?php
$pdo = new PDO('mysql:host=127.0.0.1;dbname=mabase', 'root', 'motdepasse');

$stmt = $pdo->prepare('SELECT * FROM message WHERE message_id = :id');
$stmt->bindValue(':id', 3, PDO::PARAM_INT);
$stmt->execute();

print_r($stmt->fetchObject());
?>

Dans ce code, quel que soit l’identifiant, la requête ne changera pas, seule la valeur change.

Les injections côté client

Nous allons voir dans cette section quelques-unes des plus dangereuses failles côté client. Commençons avec la faille XSS.

La faille XSS

Qu'est-ce qu'une faille XSS ?

Dans ce cas, il ne fera aucune différence entre le code du site et celui injecté par l'attaquant, et exécutera le code sans sourciller. Les possibilités sont nombreuses : redirection vers un autre site, vol de cookies, modification du code HTML de la page ; en bref, tout ce que ces langages de script vous permettent de faire.

Imaginons le code HTML suivant :

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="utf-8" />
</head>
<body>
    <h1>Mon super moteur de recherche</h1>
    
    <form type="get" action="">
        <input type="text" name="keyword" />
        <input type="submit" value="Rechercher" />
    </form>
    
    <?php
    if(!empty($_GET['keyword']))
    {
        echo "Résultat(s) pour le mot-clé : ".$_GET['keyword'];
    }
    ?>
</body>
</html>

Ce code ne fait rien de compliqué : il ne fait qu'afficher un champ qui nous servira de petit moteur de recherche. Ce code affiche simplement le contenu de la recherche, le mot-clé que vous avez entré.

Si vous essayez de mettre dans le champ de recherche le code HTML

<h1 style="color:blue;"><u>Un titre personnalisé avec du CSS !</u></h1> , vous obtiendrez un résultat assez particulier.

Essayez-donc avec ce petit exercice interactif ci-dessous ! Copiez-collez cette ligne de code dans la barre de recherche, modifiez la valeur de  style  si vous avez des bases de CSS, et cliquez sur  Rechercher . Vous verrez le résultat !

https://www.codevolve.com/api/v1/publishable_key/2A9CAA3419124E3E8C3F5AFCE5306292?content_id=8a9b202c-84f5-4869-bb19-339c2b2e6bc4

On pourrait même injecter dans le formulaire du code JavaScript, comme par exemple :

<script>alert('Ton site est vulnérable aux failles XSS');</script>

Ce code aurait pour effet d’afficher une boîte de dialogue dans le navigateur.

Vous imaginez bien que ce qu’il est possible de faire est sans limite. Avec JavaScript, on peut :

  • Modifier ou ajouter des clefs à la base de registre de la machine victime ;

  • Afficher une fenêtre demandant à l’utilisateur de rentrer son login et son mot de passe puis de valider, après quoi le résultat sera envoyé par courriel à l’attaquant ;

  • Récupérer les cookies présents sur la machine victime ;

  • Exécuter des commandes systèmes ;

  • Construire un lien vers un site malveillant et diriger l’internaute vers celui-ci.

Ces quelques exemples sont des failles XSS réfléchies ou non permanentes, car elles ne sont pas stockées dans un fichier ou une base de données.

Les failles permanentes

Prenons l’exemple d’un blog où vous pouvez écrire des commentaires qui seront stockés dans une base de données, de manière à ce que tous les utilisateurs puissent les voir. Si votre site n’est pas protégé, un utilisateur malveillant pourrait incorporer dans un commentaire un morceau de script Javascript par exemple. Dans ce cas, le site stocke le commentaires avec le script dans la base de données.

Quand un autre utilisateur se connecte au site pour voir les nouveaux commentaires, la page HTML est construite, mais elle contient le morceau de script, qui sera donc exécuté par le navigateur.

Le schéma suivant illustre le mécanisme :

L'attaque se fait en 4 étapes. 1. Injection du code nocif par l'attaquant dans un site vulnérable 2. Visite de la page infectée par la victime 3. Envoi de la page demandée contenant le code dangereux par le site à la victime. 4. Exécution du code su
Faille XSS permanente

L’attaque réussit car, encore une fois, le navigateur ne peut différencier les données du code. 

Protégez-vous grâce à l'échappement de caractères

Par exemple, en PHP, la fonction  htmlspecialchars()  transforme ces symboles en leur équivalent HTML, les rendant ainsi inopérants.

Dans cette première ligne de défense concernant la validation stricte des entrées, il faut également vérifier que les données fournies sont bien du type attendu (entier, chaîne de caractères, etc).

La plupart des frameworks du marché (Django, Symfony, .Net) utilisent des méthodes de ce type pour se protéger des failles XSS.

Protégez-vous grâce au principe d'assainissement

Comment cela se passe-t-il pour des balises HTML entrées grâce à des éditeurs de type WYSIWYG comme TinyMCE ? Le texte qui y est écrit peut comprendre des balises HTML légitimes. Comment fait-on pour ne pas entraver le fonctionnement de l’éditeur tout en se protégeant des failles XSS ?

Eh bien, on utilise le principe d’assainissement(sanitation en anglais) grâce à des bibliothèques qui enlèvent tous les éléments dangereux des données malveillantes. Par exemple, en PHP, on peut utiliser les fonctions  filter_var(). L’OWASP met également à disposition une bibliothèque complète pour les environnement Java.

Protégez-vous grâce à la Content Security Policy

Par exemple, sans entrer dans le détail, on peut restreindre l'exécution de scripts au domaine de notre application et à l’API Google Map.

HTTP

Content-Security-Policy: script-src 'self' ajax.googleapis.com

HTML

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="/js/app.js"></script>
<script src="http://unautredomaine.com/bad.js"></script>

Résultat

Refused to load the script 'http://unautredomaine.com/bad.js' because it violates the following Content Security Policy directive: "script-src 'self' ajax.googleapis.com".

Les possibilités sont nombreuses et je vous invite à lire cette documentation.

Injection HTML

Vous commencez à être familier avec le principe : une injection HTML consiste à injecter dans un champ  input  du code HTML qui sera interprété comme tel par un site non protégé. L’exemple fourni plus haut où l’utilisateur entre  <h1 style="color:blue;"><u>essai</u></h1> est une illustration d’une injection HTML.

Pour s’en prémunir, on échappe tous les caractères dangereux grâce aux outils donnés par son environnement de développement.

Injection CSS

Une injection CSS se présente lorsque l’application importe une feuille de style à partir d’une URL donnée par l’utilisateur, ou lorsque des entrées utilisateur permettent d’écrire des règles CSS dans l’application (en exploitant une faille XSS permanente par exemple).

Les conséquences d’une injection de code CSS arbitraire sont nombreuses :

  • On peut exécuter du Javascript grâce aux fonctions  expression()  de IE ;

  • On peut utiliser les sélecteurs CSS pour lire des parties du code HTML de l’application. Ce cas peut s’avérer dangereux si l’utilisateur malveillant peut lire des jetons CSRF (dont nous avons parlé dans le chapitre 4 de la partie 2) ;

  • On peut capturer toutes les données passées en paramètres d’une requête HTTP  GET  en créant une feuille CSS importées d’une URL du domaine de l’attaquant.

Protégez-vous des injections CSS

Vous y êtes maintenant habitués, la première des défenses est d’échapper toutes les entrées utilisateurs.

Une autre possibilité est de tester les entrées utilisateurs avec une liste d’entrées acceptées, connues par l’application. Dans le cas d’une entrée non valide, l’application rejette la soumission.

Utilisez les sandboxes avec HTML5

Pour limiter les risques, le W3C a ainsi ajouté l’attribut  sandbox  dans sa spécification du HTML5, qui permet de restreindre les actions possibles dans l’iframe sur les navigateurs récents.

Le principe est simple : on cherche à cloisonner l’environnement des éléments à exécuter et on n’autorise que le strict nécessaire. Une fois utilisé, l’attribut sandbox des balises iframe restreint principalement :

  • L'exécution des scripts ;

  • L’envoi de requêtes AJAX ;

  • Le stockage sur le navigateur ;

  • L’envoi de formulaire.

Pour plus d’informations et notamment sur la manière de lever une partie des restrictions, je vous renvoie à la documentation.

Résumé

Dans le prochain chapitre, je vous propose de nous intéresser à la protection de vos données.

Exemple de certificat de réussite
Exemple de certificat de réussite