Le TP se déroulera autour d'une application factice : un mini-blog. Les fonctionnalités sont volontairement limitées, et le code PHP très simplifié, mais les failles de sécurité sont les mêmes que dans des applications réelles.
if (isset($_GET['id_comment'])) { $comments = $db->query("SELECT * FROM comment WHERE id = " . $_GET['id_comment']) ->fetchAll(); $comment = $comments[0]; if ($comment->id_user != $_SESSION['user']->id) { die("Permission refusée"); } }
action
et de passer l'input nommé "id_comment" du type "hidden" au type "text".function cleCryptee($valeur, $sel=" ne se devine pas") { return md5($valeur . $sel); }Ensuite, on ajoute au formulaire, sous le champ id_comment, une ligne :
echo '<input name="cle" type="hidden" value="' . cleCryptee($_REQUEST['id_comment']) ."\" />\n";Il reste, avant de déclarer le "UPDATE" SQL, à vérifier les données reçues :
if (cleCryptee($_REQUEST['id_comment']) != $_REQUEST['cle']) { die('Formulaire invalide !'); }
</dd></dl></div><span style="display: none;">Avec un tel commentaire, la page de l'article n'est bien sûr plus en HTML valide.
<script> document.getElementsByTagName("h1")[0].innerHTML="Coucou !"; </script>
strip_tags()
à chaque résultat d'une saisie utilisateur (titre ou contenu d'un commentaire)." onmouseover="alert(/coucou/)
strip_tags
précédent par un appel à htmlspecialchars
.
htmlspecialchars($comment->title, ENT_COMPAT, UTF-8)Si l'attribut title était entre apostrophes, alors il faudrait remplacer
ENT_COMPAT
par ENT_QUOTES
, mais mieux vaut utiliser toujours des guillemets.require_once("HTMLPurifier.auto.php"); $config = HTMLPurifier_Config::createDefault(); $config->set('URI', 'HostBlacklist', array('google.com')); $config->set('HTML', 'AllowedElements', array('a','img','div')); $config->set('HTML', 'AllowedAttributes', array('a.href','img.src','div.align','*.class')); $purifier = new HTMLPurifier($config); echo $purifier->purify($comment->content);
javascript:alert(/coucou !/);Alors même si ce champ est affiché avec
htmlspecialchars()
, on peut injecter n'importe que code javascript dans la page.onchange="alert(this.value);"Ce qui donne l'URL relative :
user_login.php?login="+onchange="alert(this.value);Pour une attaque plus réaliste, on intercepterait plutôt l'événement "onsubmit" du formulaire, et on crypterait l'URL, mais le principe reste le même.
jQuery.post("comment_create.php", { id_comment: 3, title: "CSRF", content: "triche !" });
$_SESSION['form1'] = TRUE; echo '<input type="hidden" name="csrf" value="form1" />';Dans le traitement PHP du formulaire :
if (isset($_POST['csrf1'])) { $csrf = $_POST['csrf1']; if (empty($_SESSION[$csrf])) { die("Le formulaire n'a pas été posté au cours d'une session."); } }
' OR '1'='1
.
' OR 1 --
.
noMagicQuotes()
?
La directive "magic_quotes_gpc", par défaut à ON avant PHP 4.3, est maintenant à OFF en général.
Pourquoi cette fonctionnalité est-elle devenue obsolète en PHP ?
PDO::quote()
, l'équivalent de mysqli::real_escape_string()
.
PDO::quote()
met automatiquement des apostrophes autor de ses arguments, à la différence de mysql_real_escape_string
.
$sql = "SELECT * FROM user " ."WHERE login=". $db->quote($_POST['login']) ." AND password=". $db->quote($_POST['password']);
id_comment
la valeur 3 OR 1=1
, alors on modifiera tous les commentaires.if ("3 OR 1=1" == 3) // TRUE if ("3 OR 1=1" === 3) // FALSENoter toutefois que si l'on utilise
$db->quote($_REQUEST['id_comment'])
, alors l'attaque ci-dessus ne fonctionnera pas car même les valeurs numériques seront entre apostrophes.
$newComment = array( "id_comment" => false ); if (isset($_REQUEST['id_comment'])) { $newComment['id_comment'] = (int) $_REQUEST['id_comment']; }
$args = array( 'id_comment' => FILTER_VALIDATE_INT, 'title' => FILTER_SANITIZE_ENCODED, 'content' => FILTER_SANITIZE_ENCODED, ); $newComment = filter_input_array(INPUT_REQUEST, $args);
$prep = $db->prepare("UPDATE comment SET title=:title, content=:content, id_user=:iduser " ." WHERE id = :idcomment"); $prep->bindParam(':idcomment', $newComment['id_comment']); } $prep->bindParam(':title', $newComment['title']); // ... if ($prep->execute()) {Les requêtes préparées garantissent une sécurité optimale.
alert(document.cookie);
error_log()
).
document.write("<img src=\"getcookies.php?cookie="+document.cookie+"\"");Le fichier "getcookies.php" contient simplement :
<?php error_log($_GET['cookie'], 3, 'cookies.log');
article_view.php?id=2&PHPSESSID=12345678901234567890123456789012