L'attaquant envoie des données (via un formulaire, une URL, une API, voir même un header HTTP, etc.) dans le système qui vont êtres injectés quelque part dans le code.
Lors de l'injection dans le code, si ces données ne sont pas proprement protégées elles peuvent alors êtres interprétées par le système, déclenchant des actions non prévues et potentiellement dangereuses.
Classique absolu de l'injection, l'injection SQL consiste à envoyer des données qui seront injectées dans une requête SQL afin d'en modifier le comportement.
<?php
require './funcs/datas.php';
$pdo = connect_pdo();
$query = 'DELETE FROM message WHERE id = ' . $_GET['id'];
$query = $pdo->prepare($query);
$query->execute();
$query = 'SELECT * FROM message';
$query = $pdo->prepare($query);
$query->execute();
$messages = $query->fetchAll();
var_dump($messages);Exemple de faille
http://127.0.0.1/cours_php_b2/examples/breach_sql.php?id=1%3B%20INSERT%20INTO%20user%20(email%2C%20password)%20values%20(%27hack%40yopmail.com%27%2C%20%27h4ck1ng%27)%3BExemple d'injection
Toujours utiliser des requêtes préparées et passer les données via des flags.
<?php
require './funcs/datas.php';
$pdo = connect_pdo();
$query = 'DELETE FROM message WHERE id = :id';
$query = $pdo->prepare($query);
$query->execute(['id' => $_GET['id']]);
$query = 'SELECT * FROM message';
$query = $pdo->prepare($query);
$query->execute();
$messages = $query->fetchAll();
var_dump($messages);Code corrigé
L'injection SQL n'est pas la seule, on retrouve la même chose avec des bases NoSQL, LDAP, avec du code, etc.
De façon générale, dès que des données fournies par l'utilisateur doivent êtres insérées dans des chaînes devant être évaluées.
La solution est toujours la même, au mieux utiliser un outil d'échappement, au pire formater les données pouvant êtres fournies.
Classique absolu de l'injection, l'injection SQL consiste à envoyer des données qui seront injectées dans une requête SQL afin d'en modifier le comportement.
<?php
require './funcs/datas.php';
$pdo = connect_pdo();
$query = 'DELETE FROM message WHERE id = ' . $_GET['id'];
$query = $pdo->prepare($query);
$query->execute();
$query = 'SELECT * FROM message';
$query = $pdo->prepare($query);
$query->execute();
$messages = $query->fetchAll();
var_dump($messages);Exemple de faille
L'attaquant intercepte les données de session d'une requête et vol le cookie de session.
Il le renvoie dans ses propres requêtes et est donc connecté comme s'il était l'utilisateur légitime.
Une solution est de stocker l'IP de l'utilisateur dans la session et supprimer cette session si l'IP change.
<?php
session_start();
session_name('breach_session');
if ($_SESSION['IP'] != $_SERVER['REMOTE_ADDR'])
{
session_destroy();
echo 'Session expirée, merci de vous reconnecter.';
exit();
}
echo 'Bravo, tu est connecté !';Exemple
Assurez-vous aussi qu'il n'est pas possible de bypass la connexion, générer un password pour l'utilisateur sans sécurité, etc.
Vous devez toujours vous demander comment l'utilisateur pour esquiver votre vérification de connexion.
Comme dans les injections, l'attaquant envoie des données qui ne sont pas sécurisées.
Ici, il envoie des données qui seront affichées dans une page web du client.
En envoyant du code HTML/JS, etc. il peut faire exécuter du code par le navigateur de l'utilisateur, et potentiellement récupérer des données.
Un exemple classique de faille XSS est de créer une redirection vers une page de vol d'identifiants.
Imaginons une page de connexion pouvant afficher un message d'erreur.
<?php
//...
?>
<?php if (!empty($_GET['error'])) { ?>
<div style="background-color: red; color: white; font-weight: bold;"><?php echo $_GET['error']; ?></div>
<?php } ?>
<form action="" method="POST">
Password : <input type="password" name="password"/><br/>
<input type="submit" value="Connexion"/>
</form>
Exemple de faille
127.0.0.1/cours_php_b2/examples/breach_xss.php?error=<script>window.location="./breach_xss_hack.php"</script>Exemple d'exploitation
Comme pour les injections, la solution est d'échapper les données avant de les afficher. Pour cela il suffit d'utiliser la fonction htmlspecialchars.
<?php
//...
?>
<?php if (!empty($_GET['error'])) { ?>
<div style="background-color: red; color: white; font-weight: bold;"><?php echo htmlspecialchars($_GET['error']); ?></div>
<?php } ?>
<form action="" method="POST">
Password : <input type="password" name="password"/><br/>
<input type="submit" value="Connexion"/>
</form>
Code corrigé
Souvent les applications exposent des accès directes à une ressource, par exemple à une entrée de bdd par son id, à un fichier via son nom, etc.
Si l'application ne vérifie pas l'identité de l'utilisateur lors de l'accès, celui-ci peut accéder à n'importe quelle ressource (potentiellement sensible) en modifiant l'identifiant de ressource.
Un exemple simple pourrait être une page permettant d'accéder au profil utilisateur.
<?php
require './funcs/datas.php';
$pdo = connect_pdo();
$id = $_GET['id'] ?? false;
if (!$id)
{
exit();
}
$query = 'SELECT * FROM user WHERE id = :id';
$query = $pdo->prepare($query);
$query->execute(['id' => $id]);
$user = $query->fetch();
var_dump($user);
?>Exemple de faille
En changeant l'id, on peut accéder au profil de n'importe qui.
Deux solution :
- Si la page est privée, vérifier que l'utilisateur authentifié est bien propriétaire de la ressource.
- Si la page est publique, utiliser un identifiant unique intermédiaire très aléatoire.
Souvent, la configuration par défaut des serveurs/outils est optimisée pour passer partout le plus facilement possible.
Parfois, la sécurité est sacrifiée dans la config par défaut.
Il faut donc absolument faire attention et configurer les différents logiciels pour limiter au maximum la surface d'attaque.
Il ne faut activer que les extensions et fonctionnalités VRAIMENT utilisées.
Les données importantes ne sont pas suffisamment protégées (chiffrement absent ou faible), et une autre faille peut permettre d'accéder à ces données et de les exploiter.
Des fichiers de configuration ou des données de connexion peuvent aussi être révélées par les messages d'erreur.
Toutes les données sensibles (password, identité, bancaires, etc.), doivent absolument êtres chiffrées.
En production, tous les messages d'erreur du langage doivent êtres cachées.
En PHP, on configure le php.ini pour fixer display_errors à False.
Beaucoup de site vérifie si l'utilisateur est bien connecté avant de lui proposer un lien pour supprimer une ressource, ajouter, modifier, etc.
Mais souvent, les sites oublient de vérifier au moment de la requête si l'utilisateur a bien le droit dessus.
Avant toute modification de ressource, il est primordiale de vérifier que l'utilisateur possède les droits sur cette ressource particulière.
De la même façon, de nombreux sites font des vérifications coté client en javascript.
Cela peut être utile pour traiter des messages d'erreur des clients, etc., mais ce n'est absolument pas une sécurité.
Javascript est manipulable par l'utilisateur, tout test fait en javascript coté client doit être refait coté serveur en PHP.
(Ma faille préférée)
Amener un utilisateur authentifié et possédant les droits sur une ressource à faire une requête vers une URL sans qu'il s'en rende compte pour effectuer une action protégée.
Exemple, une page de suppression de message.
<?php
session_start();
session_name('breach_csrf');
require './funcs/datas.php';
$pdo = connect_pdo();
$id = $_GET['id'] ?? false;
$password = $_GET['password'] ?? false;
if ($password === 'password')
{
$_SESSION['connect'] = true;
}
if (!$id)
{
echo 'Pas de ressource.';
exit();
}
if (!$_SESSION['connect'])
{
echo 'Pas connecté.';
exit();
}
$query = 'DELETE FROM message WHERE id = :id';
$query = $pdo->prepare($query);
$query->execute(['id' => $id]);
Exemple de faille
<h1>Error 404</h1>
<?php for ($i = 0; $i < 100; $i++) { ?>
<img src="./breach_csrf.php?id=<?php echo $i; ?>" alt=" "/>
<?php } ?>Exemple de CSRF
Si on ne protège pas contre le CSRF et qu'un utilisateur connecté passe sur une page avec un lien vers une URL de suppression, il va supprimer le message.
Pour se protéger, on crée en session un token unique aléatoire.
<?php
session_start();
session_name('breach_csrf');
require './funcs/datas.php';
$pdo = connect_pdo();
$id = $_GET['id'] ?? false;
$password = $_GET['password'] ?? false;
if ($password === 'password')
{
$_SESSION['connect'] = true;
$bytes = random_bytes(10);
$csrf = bin2hex($bytes);
$_SESSION['csrf'] = $csrf;
}
if (!$id)
{
echo 'Pas de ressource.';
exit();
}
if (!$_SESSION['connect'])
{
echo 'Pas connecté.';
exit();
}
if (!$_SESSION['csrf'] == $_GET['csrf'])
{
echo 'Bad CSRF.';
exit();
}
$query = 'DELETE FROM message WHERE id = :id';
$query = $pdo->prepare($query);
$query->execute(['id' => $id]);
Code corrigé
À chaque requête qui modifie une ressource, on va inclure ce jeton aléatoire à la requête HTTP pour vérifier qu'elle est légitime.
Beaucoup de sites utilises des composants externes, modules, librairies, logiciels, code copié/collé, etc.
Si ces composants ont des failles de sécurités connues, des outils de recherches automatisés de failles risquent de les détecter et de les exploiter.
Pas de secret, il faut mettre à jour en permanence les logiciels.
Utiliser uniquement des librairies fiables et réputées.
Lire le code quand on a des doutes.
Donner le minimum de droits nécessaires au bon fonctionnement de chaque module pour limiter l'impact des exploitations.
De nombreux sites utilisent des redirections soit pour contrôler le parcours de l'utilisateur, soit pour l'exclure de pages.
Si on ne protège pas la destination de la redirection et qu'on ne bloque pas l'utilisateur après la redirection, l'utilisateur peut être envoyé n'importe où.
Exemple, une page de login qui redirige après connexion.
<?php
session_start();
session_name('breach_csrf');
require './funcs/datas.php';
$pdo = connect_pdo();
$password = $_POST['password'] ?? false;
$redirect = $_GET['redirect'] ?? false;
if ($password === 'password')
{
$_SESSION['connect'] = true;
if ($redirect)
{
header('Location: ' . $redirect);
}
}
?>
<form action="" method="POST">
Password : <input type="password" name="password" /><br/>
<input type="submit" value="Connexion" />
</form>
Exemple de faille
http://127.0.0.1/cours_php_b2/examples/breach_redirect.php?redirect=https%3A%2F%2Fbit.ly%2F2RMxoNDExemple de redirect malicieux
Si on ne protège pas, on peut rediriger l'utilisateur sur un site de phishing
Pour se protéger, on limite la redirection à certaines urls.
<?php
session_start();
session_name('breach_redirect');
require './funcs/datas.php';
$pdo = connect_pdo();
$password = $_POST['password'] ?? false;
$redirect = $_GET['redirect'] ?? false;
if ($password === 'password')
{
$_SESSION['connect'] = true;
if ($redirect)
{
$servername = parse_url($redirect, PHP_URL_HOST);
if ($servername != $_SERVER['REMOTE_HOST'])
{
echo 'Bad redirection.';
exit();
}
header('Location: ' . $redirect);
}
}
?>
<form action="" method="POST">
Password : <input type="password" name="password" /><br/>
<input type="submit" value="Connexion" />
</form>
Code corrigé
Au minimum, on vérifie que ça reste sur le site.
Une page redirige vers l'accueil si on est pas connecté.
<?php
session_start();
session_name('breach_redirect');
$connect = $_SESSION['connect'] ?? false;
if (!$connect)
{
header('Location: /');
}
?>
Admin onlyExemple de redirect non sécure
Si on ne quitte pas le script après la redirection, le navigateur peu refuser la redirection et accédera à la partie admin.
Pour se protéger, on quitte TOUJOURS après une redirection.
Code corrigé
<?php
session_start();
session_name('breach_redirect');
$connect = $_SESSION['connect'] ?? false;
if (!$connect)
{
header('Location: /');
exit();
}
?>
Admin onlyDe nombreux sites utilisent l'instruction include avec des données $_GET ou $_POST pour permettre d'inclure dynamiquement une page.
Si on ne protège pas ces includes, ils peuvent êtres utilisés pour inclure des fichiers dangereux, exécuter du code tiers, etc.
La solution la plus simple pour faire ça VRAIMENT bien, c'est d'établir directement dans le code une liste de page que l'ont peut inclure, directement sous forme de tableau, et rejeter tout ce qui ne rentre pas dans cette liste.
1 - Ne jamais faire confiance à l'utilisateur et à ses données.
2 - Toujours réfléchir à comment la fonctionnalité que vous codez peu être détournée.
3 - Limiter au maximum la surface d'exposition, n'accordez que les autorisations NÉCESSAIRES !
4 - N’exécutez jamais du code non écrit par vous ou une source sûre.