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) // FALSE
Noter 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