Never trust user input
Toutes les informations provenant de l'utilisateur doivent être contrôlées
Vous trouverez un code d'exemple dans le dossier never_trust_user_input
de l'archive.
Voyez-vous un problème de sécurité dans ce code?
Supposons qu'un utilisateur achète des articles sur un site de e-commerce. À la fin, on lui propose de donner de l'argent à une oeuvre de charité avec ce formulaire:
<form method="post">
Souhaitez vous, en plus de votre achat, donner de l'argent
à une oeuvre de charité?
<select name="charityPrice">
<option value="0">Non merci</option>
<option value="10">10 €</option>
<option value="50">50 €</option>
<option value="100">100 €</option>
</select>
<input type="submit" />
</form>
Et que, côté serveur, on calcule le prix final qu'il devra payer:
<?php
// ...
$totalPrice = $basketPrice;
if ($_POST['charityPrice']) {
$totalPrice += $_POST['charityPrice'];
}
Ici:
<select>
charityPrice
était
un nombre négatif? On se retrouverait alors à soustraire du prix total!Ici, le problème peut être simplement corrigé en vérifiant que l'utilisateur fournit bien une des valeurs proposées côté serveur (en PHP).
En d'autre termes, ne pas faire confiance aux données POST provenant de l'utilisateur.
Un code d'exemple se trouve dans le dossier sql_injection
de l'archive. Vous aurez besoin
d'éditer pdo.php
pour vous connecter à la base de données shows
.
Voyez-vous un problème ?
Si vous vous souvenez de ce qui a été dit en cours, le problème provient de cette ligne:
<?php
$query = $pdo->query('SELECT title, plot FROM series WHERE id='.$_GET['id']);
Il ne faut jamais faire ça!
Le problème avec ce code est qu'il permet à l'utilisateur d'altérer également la structure de la requête elle même.
Un exemple d'exploitation ici serai de fournir la valeur suivante au paramètre GET id
:
show.php?id=99999999 UNION SELECT email, password FROM user LIMIT 1
La requête (réindentée) devient alors:
SELECT title, plot
FROM series
WHERE id=99999999
UNION
SELECT email, password
FROM user
LIMIT 1
Ici:
99999999
qui n'existe probablement pasuser
dans laquelle on récupère l'email et le
mot de passe d'un utilisateurCette attaque nécessite de trouver le nom des tables, des champs etc. Mais ce travail est automatisable!
Pour éviter cette faille, il suffit d'utiliser une requête préparée.
Regardez à présent le code de xss_csrf
, qui sera utilisé pour cette partie et la suivante. Il faudra
également configurer pdo.php
, et s'assurer d'avoir un utilisateur en base qui a un identifiant décrit
par $userId
dans index.php
.
Voyez-vous quelque chose de dangereux?
La première faille ici est une faille de type XSS (Cross-Site Scripting). Le problème est que le contenu des commentaires n'est pas "échappé" lorsqu'ils sont affichés dans la page HTML.
Par exemple, si vous saisissez un commentaire du type:
<b>Bonjour</b>!
Le texte apparaitra en gras.
Le problème, c'est que cela vous permet également d'entrer un commentaire du type:
<script>document.location='https://google.com';</script>
Désormais, la page vous redirigera vers https://google.com
!
Cette faille permet de faire des choses bien pires, dont notamment voler le cookie de session, mais vous avez déjà compris le principe.
Pour corriger cette faille, il est nécessaire d'échapper les données de la base de données au moment de
l'affichage, par exemple à l'aide de la fonction htmlspecialchars()
.
Lorsque vous utilisez un moteur de template comme Twig, les valeurs sont échappées par défaut. C'est
à dire que c'est comme si htmlspecialchars()
était autour de tous vos affichages. Si vous voulez
réellement afficher du HTML, vous pourrez utiliser le filtre
raw.
Il subsiste une deuxième faille à laquelle il est bien plus dur de penser sur cette page.
Imaginez que quelqu'un construise ce formulaire:
<form method="post" action="http://votre-site-web.com/index.php">
<textarea rows="5" cols="100" name="comment">J'adore cette série!</textarea>
<br/>
<input type="submit" value="Envoyer" />
</form>
Où l'URL dans l'attribut action
désignerait votre site web en production.
On pourrait même agrémenter ce formulaire d'une soumission automatique déclenchée en JavaScript au chargement de la page:
<body onload="post()">
<script type="text/javascript">
function post() {
document.getElementById('form').submit();
}
</script>
Vous trouverez cet exemple dans exploit.html
Un tel formulaire peut être hébérgé n'importe où, par exemple sur un site web géré par l'attaquant, qui pourrait alors vous forcer à soumettre des données arbitraires vers le formulaire en question.
Le problème, c'est qu'en soumettant le formulaire, vous utiliserez également votre cookie de session et serez reconnus par le serveur!
Corriger cette faille est un peu plus technique, et se fait en plusieurs étapes:
hidden
dans le formulaire, que l'on appelle généralement csrf_token
(jeton CSRF)Ce travail est automatiquement assuré par un framework web.
Regardez le code source du dossier password_hash
.
Est-ce quelque chose vous choque?
Ici, le mot de passe super_password
est stocké en clair dans le fichier:
if ($_POST['password'] === 'super_password') {
Supposez que le serveur soit compromis (imaginons qu'une autre faille permette à quelqu'un d'avoir un accès en lecture aux données). Il connaîtra alors le mot de passe.
Le même principe s'applique si le mot de passe est stocké dans la base de données.
Un mot de passe ne doit jamais être stocké en clair!
Au lieu de ça, on utilise une fonction de hachage (hash en anglais), ou d'empreinte, comme par exemple les fonction password_hash() et password_verify() en PHP.