Captcha : défauts et nouveautés

mieux protéger une application internet

Ce site ne sera plus alimenté de contenu après août 2014. Tous les nouveaux articles seront redigés pour www.waitingforcode.com
Le spam rapporte gros. Quelques articles dont les auteurs ont essayé de mesurer l'ampleur économique du problème (1,2, 3) le prouvent. Il est donc évident que les sites protègent tout ce qu'ils peuvent (formulaires de contact, de commentaires...). Solution la plus répandue est captcha.

Captcha est un test basé sur le principe de question-réponse. L'internaute reçoit une question (très souvent une image composée de quelques caractères) et
en retour il est obligé de donner sa réponse. Si sa réaction est correcte, le système considère l'internaute comme un utilisateur viable, à priori ayant de bonnes intentions.

L'article va aborder la problématique d'un système captcha. D'abord on verra comment surpasser cette protection. Elle ne sera pas très sophistiquée. Grâce à cela on pourra voir quels points peuvent poser le problème à un attaquant potentiel. Cela nous permettra aussi d'améliorer l'outil de protection. A la fin on traitera la question des systèmes captcha alternatifs à un "copier-coller" habituel.

Préparation
Au début on crée des images avec des caractères qu'on utilisera pour nos captcha de test. Les fichiers d'exemple sont dans le package à la fin de l'article. On se limiter à des caractères suivants : 0, 1, 6, 8, 9, B, C, D, E, H, L, O, R, S, T.

Après il faut se charger de "traduire" chaque caractère en une chaîne de caractères. Pour cela on utilisera le fichier break_captcha.php. Les commentaires qui vous aideront à mieux le comprendre sont inclus dans le code.

break_captcha.php

// my_folder => folder with your character files
// my_char => character to "translate"
$img=imagecreatefromjpeg($_SERVER['DOCUMENT_ROOT'].'/my_folder/my_char.jpg');

// use edge_filter to translate exactly the character file
$nimg = imagefilter($img, IMG_FILTER_EDGEDETECT);
imagejpeg($img, $_SERVER['DOCUMENT_ROOT'].'/my_folder/captcha2.jpg');

// get image dimensions (height and weight)
$width = imagesx($img);
$height = imagesy($img);

// detect colors from top to the bottom
for($w = 1; $w <= $width; $w++) {
for($h = 1; $h &lt;= $height; $h++) {
// get image colors and translate it into HEX code
$colorat = imagecolorat($img, $w, $h);
$rgb = imagecolorsforindex($img, $colorat);
$red = round(round(($rgb['red'] / 0x33)) * 0x33);
$green = round(round(($rgb['green'] / 0x33)) * 0x33);
$blue = round(round(($rgb['blue'] / 0x33)) * 0x33);
$thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue);
// we are interested by black color; if it isn't black, replace the color by "-"
if($thisRGB != "000000") {
$thisRGB = "-";
}
$letterPallet = "$letterPallet $thisRGB";
}
}


Après l'exécution de ce code sur toutes les images, on reçoit un tableau avec les couleurs "traduites":

$caracaters = array("0" => "- - - - - - - - - - - - - - - - - 000000 - - - 000000 000000 000000 000000 000000 000000 000000 000000 - - - - - - 000000 - - 000000 000000 - - - - - - 000000 000000 000000 - - - - 000000 - 000000 000000 - - - - - - - - - 000000 000000 - - - 000000 - 000000 - - - - - - - - - - - 000000 - - - 000000 - 000000 - - - - - - - - - - - 000000 - - - 000000 - 000000 000000 - - - - - - - - - - 000000 - - - 000000 - - 000000 - - - - - - - - - - 000000 - - - 000000 - - 000000 000000 000000 000000 - - - - - - 000000 000000 - - - 000000 - - - - 000000 000000 000000 000000 000000 000000 000000 000000 - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"1" => "- - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - 000000 000000 - - - - - - - - - - - 000000 - - - - 000000 - - - - - - - - - 000000 000000 - 000000 - - - - 000000 - - - - - - - - - 000000 - - 000000 - - 000000 - - - - - - - - - - - 000000 - - 000000 - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 - 000000 - - 000000 - - - - - - - - - - - - - - 000000 - - 000000 - - - - - - - - - - - - - - 000000 - - 000000 - - - - - - - - - - - - - 000000 000000 - - 000000 - - - - - - - - - - - - - 000000 000000 - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"B" => "- - - - - - - - - - - - - - - - - 000000 - 000000 - 000000 000000 - - - - - - - - - - - - 000000 - 000000 - - 000000 000000 000000 000000 000000 000000 000000 - - - - - - 000000 - 000000 - - - - - - - 000000 000000 - 000000 000000 - - - 000000 000000 000000 - - - - - 000000 - - - - - 000000 - - - 000000 000000 000000 - - - - 000000 000000 - - - - - 000000 - - - 000000 000000 000000 - - - - 000000 000000 - - - - 000000 000000 - - - 000000 000000 - - - - - 000000 - - - - - 000000 000000 - - - 000000 - - - 000000 000000 - 000000 - - - - - 000000 - - - - 000000 - - 000000 000000 - - 000000 - - - - - 000000 - - - - 000000 - - - - - - - 000000 000000 000000 000000 000000 - - - - - 000000 - - - - - - - - - 000000 000000 - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"9" => "- - - - - - - - - - - - - - - - - 000000 - - - 000000 000000 000000 000000 000000 000000 - - - - - - - - 000000 - - 000000 000000 - - - - 000000 - - - - - - - - 000000 - - 000000 - - - - - 000000 000000 - - - - 000000 - - 000000 - - 000000 - - - - - - 000000 - - - - 000000 - - 000000 - 000000 000000 - - - - - 000000 000000 - - - - 000000 - - 000000 - - 000000 - - - - - 000000 000000 - - - 000000 000000 - - 000000 - - 000000 000000 - - - - 000000 - - - - 000000 - - - 000000 - - - 000000 000000 - - - - - - 000000 000000 - - - - 000000 - - - - 000000 000000 000000 000000 000000 000000 000000 000000 - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"C" => "- - - - - - - - - - - - - - - - - 000000 - - - 000000 000000 000000 000000 000000 000000 000000 - - - - - - - 000000 - - 000000 000000 - - - - 000000 000000 000000 - - - - - - 000000 - 000000 000000 - - - - - - - 000000 000000 - - - - - 000000 - 000000 - - - - - - - - - - 000000 - - - - 000000 000000 000000 - - - - - - - - - - 000000 - - - - 000000 000000 - - - - - - - - - - - 000000 - - - - 000000 000000 - - - - - - - - - - - 000000 - - - - 000000 000000 - - - - - - - - - - - 000000 - - - - 000000 000000 - - - - - - - - - - - 000000 - - - - 000000 - - - - - - - - - - - 000000 000000 - - - - 000000 - - - - - - - - - - 000000 000000 - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"R" => "- - 000000 000000 000000 000000 000000 000000 000000 - - - - - - - - 000000 - - 000000 - - 000000 - - 000000 - 000000 000000 000000 000000 000000 000000 - 000000 - - 000000 - - - - - - - - - - - 000000 - - 000000 - - 000000 - - - - - 000000 - - - - - - - - 000000 - - 000000 - - - - - - - - - - - - - - 000000 - - 000000 - - - - 000000 000000 000000 - - - - - - - 000000 - - 000000 - - - 000000 000000 - - 000000 - - - - - - 000000 - - 000000 000000 000000 000000 000000 - - - 000000 000000 - - - - - 000000 - - - - - - - - - - - - 000000 - - - - 000000 - - - - - - - - - - - - 000000 000000 - - - 000000 - - - - - - - - - - - - - 000000 000000 - - 000000 - - - - - - - - - - - - - - 000000 - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"S" => "- - - - 000000 000000 000000 - - - - - - - - - - 000000 - - - 000000 000000 000000 000000 000000 - - - - - 000000 000000 - - 000000 - - 000000 000000 - - - 000000 - - - - - - 000000 - - 000000 - - 000000 000000 - - - - 000000 - - - - - 000000 - - 000000 - - 000000 - - - - - 000000 - - - - - 000000 - - 000000 - - 000000 - - - - - 000000 - - - - - 000000 - - 000000 - - 000000 - - - - - 000000 - - - - - 000000 - - 000000 - - 000000 - - - - - 000000 - - - - - 000000 - - 000000 - - 000000 - - - - - 000000 - - - - 000000 - - - 000000 - - 000000 000000 - - - - 000000 000000 - - 000000 000000 - - - 000000 - - - - - - - - - 000000 000000 000000 000000 - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"D" => "- 000000 000000 000000 000000 - - - - - - - - - - - - 000000 - 000000 - - 000000 000000 000000 000000 000000 000000 - - - - - - - 000000 - 000000 - - - - - - - 000000 000000 000000 000000 000000 000000 - - 000000 000000 000000 - - - - - - - - - - - 000000 - - - 000000 000000 000000 - - - - - - - - - - 000000 000000 - - - 000000 000000 000000 - - - - - - - - - - 000000 000000 - - - 000000 000000 000000 - - - - - - - - - - 000000 000000 - - - 000000 000000 000000 - - - - - - - - - - 000000 - - - - 000000 - - - - - - - - - - - 000000 000000 - - - - 000000 - 000000 000000 - - - - - - - - 000000 - - - - - 000000 - - 000000 000000 000000 - - - - 000000 000000 - - - - - - 000000 - - - 000000 000000 000000 000000 000000 000000 000000 - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"H" => "- - 000000 - - - - - - - - - - - - - - 000000 - - 000000 000000 000000 000000 000000 000000 000000 - - - - - - - - 000000 - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 - 000000 - - - - - - - 000000 - - - - - - 000000 000000 - 000000 - - - - - - - 000000 - - - - - - - - - 000000 - - - - - - - 000000 - - - - - - - - - 000000 - - - - - - - 000000 - - - - - - - - - 000000 - - - - - - 000000 000000 - - - - - - - - - 000000 - 000000 - - - - - - - - - - - - - - - 000000 - 000000 000000 000000 000000 - 000000 - - - - - - - - - - 000000 - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 - - 000000 - - - - - - - - - - - - - - 000000 - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"E" => "- - - - - - - - - - - - - - - - - 000000 - 000000 - - - - - - - - - - - - - - - 000000 - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 - - 000000 - 000000 - - - - - - - - 000000 000000 - 000000 000000 - - 000000 - 000000 - - - - 000000 - - - - - - 000000 - - - 000000 - 000000 - - - - 000000 - - - - - - 000000 - - - 000000 - 000000 - - - - 000000 - - - - - - 000000 - - - 000000 - 000000 - - - - 000000 - - - - - - 000000 - - - 000000 - 000000 - - - - 000000 - - - - - - 000000 - - - 000000 - 000000 - - - - 000000 - - - - - - 000000 - - - 000000 - - - - - - 000000 - - - - - - 000000 - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"L" => "- - - - - - - - - - - - - - - - - 000000 - - 000000 000000 000000 - - - - - - - - - - - - 000000 - - 000000 000000 000000 000000 000000 000000 000000 - - - - - - - - 000000 - - - - - - - 000000 000000 000000 000000 000000 000000 - - - - 000000 - - - - - - - - - - - 000000 000000 000000 000000 - - 000000 - - - - - - - - - - - - - - 000000 - - 000000 - - - - - - - - - - - - - 000000 000000 - - 000000 - - - - - - - - - - - - - 000000 - - - 000000 - - - - - - - - - - - - - 000000 - - - 000000 - - - - - - - - - - - - 000000 000000 - - - 000000 - - - - - - - - - - - - 000000 000000 - - - 000000 - - - - - - - - - - - - 000000 - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"O" => "- - - - 000000 000000 000000 000000 000000 000000 000000 000000 - - - - - 000000 - - - 000000 000000 - - - - - - 000000 000000 - - - - 000000 - - - 000000 - - - - - - - - - 000000 - - - 000000 - - 000000 000000 - - - - - - - - - 000000 000000 - - 000000 - - 000000 - - - - - - - - - - - 000000 - - 000000 - - 000000 - - - - - - - - - - - 000000 - - 000000 - - 000000 - - - - - - - - - - - 000000 - - 000000 - - 000000 000000 - - - - - - - - - 000000 000000 - - 000000 - - 000000 000000 - - - - - - - - - 000000 - - - 000000 - - - 000000 - - - - - - - - 000000 000000 - - - 000000 - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 - - - - 000000 - - - - - - - 000000 000000 000000 000000 - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"T" => "- - - - - - - - - - - - - - - - - 000000 - 000000 000000 - - - - - - - - - - - - - - 000000 - 000000 - - - - - - - - - - - - - - - 000000 - 000000 - - - - - - - - - - - - - - - 000000 - 000000 - - - - - - - - - - - - - - - 000000 - 000000 - - - - - - - - - - - - - - - 000000 - - - 000000 000000 000000 000000 000000 - - - - - - - - - 000000 000000 000000 - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 - - - 000000 000000 - - - - - - - - - - - - 000000 - - - 000000 000000 000000 - - - - - - - - - - - - - - - 000000 000000 000000 - - - - - - - - - - - - - - - 000000 000000 000000 - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"6" => "- - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - 000000 000000 000000 000000 000000 - 000000 000000 000000 - - - - 000000 - - - 000000 000000 - - - - - - - 000000 000000 - - - 000000 - - 000000 000000 - - - 000000 - - - - - - 000000 - - 000000 - - 000000 - - - - 000000 - - - - - - 000000 - - 000000 - - 000000 - - - - 000000 - - - - - - 000000 - - 000000 - - 000000 - - - - 000000 - - - - - - 000000 - - 000000 - 000000 000000 - - - - 000000 - - - - - 000000 000000 - - 000000 - - - - - - - 000000 000000 - - - 000000 000000 - - - 000000 - - - - - - - - 000000 000000 000000 000000 000000 - - - - 000000 - - - - - - - - - - - - - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000",
"8" => "- - - - - - - - - - - - - - - - - 000000 - - - - 000000 000000 - - - - - - - - - - - 000000 - - - 000000 000000 000000 000000 - - - 000000 - - - - - - 000000 - - 000000 - - - 000000 - - 000000 000000 - 000000 000000 - - - 000000 - - 000000 - - - - 000000 000000 - - - - 000000 - - - 000000 - - 000000 - - - - 000000 - - - - - 000000 000000 - - 000000 - 000000 000000 - - - - 000000 - - - - - 000000 000000 - - 000000 - 000000 000000 - - - - 000000 - - - - - 000000 - - - 000000 - - 000000 - - - 000000 - 000000 - - - - 000000 - - - 000000 - - 000000 000000 000000 000000 - - 000000 - - - - 000000 - - - 000000 - - - - - - - - 000000 000000 000000 000000 000000 - - - - 000000 - - - - - - - - - - 000000 000000 - - - - - 000000 - - - - - - - - - - - - - - - - - 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000"
);


L'attaque
Maintenant on peut passer à l'attaque. On rajoutera une boucle au code qu'on vient de créer. Il va se présenter ainsi (avec les commentaires inclus dans le texte) :

for($w = 1; $w <= $width; $w++) {
$letterPallet = "";
for($h = 1; $h <= $height; $h++) {     
$colorat = imagecolorat($img, $w, $h);     
$rgb = imagecolorsforindex($img, $colorat);     
$red = round(round(($rgb['red'] / 0x33)) * 0x33);
$green = round(round(($rgb['green'] / 0x33)) * 0x33);
$blue = round(round(($rgb['blue'] / 0x33)) * 0x33);
$thisRGB = sprintf('%02X%02X%02X', $red, $green, $blue);     
if($thisRGB != "000000") {       
$thisRGB = "-";     
}     
$letterPallet = "$letterPallet $thisRGB";
}
// check $caracters array and find corresponding chars   foreach($caracaters as $c => $caracter) {
if(strpos($caracter, $letterPallet) !== false) {
$foundCaracters[$c] = (int)$foundCaracters[$c]+1;
}
}
// every 16px make new check
if($w > 0 && $w%16 == 0) {
//print_r($foundCaracters);
// char the most represented in the array is considered like CAPTCHA letter
$maxValue = max($foundCaracters);
$maxKey = array_keys($foundCaracters, $maxValue);
$captcha .= $maxKey[0];
$foundCaracters = array();
//echo <h1>$w ==> Start new counter</h1>";
}
// If you test dynamic_captcha.jpg, set this test to 7
// We have all captcha characters; we stop the loop
if(strlen($captcha) == 8 ) {
break;
}
}
// show CAPTCHA generated by this function
echo $captcha;
// for dynamic_captcha.jpg it shows 01B9CRS
// for dynamic_captcha2.jpg it show ELOT1689</code>


Améliorations
Après ce court test on peut tirer quelques conclusions dont le but sera la refonte de notre système captcha.

  1. Le nombre des caractères différent. Comme vous pouvez le constater dans le dernier test de notre méthode (if(strlen($captcha) == 8 )), des longueurs différentes peuvent poser de réels problèmes à des spammeurs.

  2. Les espaces irréguliers. Si l'on prévoit des espaces différents pour chaque caractère du CAPTCHA. Grâce à cela l'attaquant ne pourra pas signaler qu'un caractère a été correctement détecté (la condition "if($w > 0 && $w%16 == 0)" sera alors invalide).

  3. La rotation des caractères. Elle aussi peut semer la confusion dans le mécanisme de l'attaquant.


Les alternatives
Les solutions alternatives existent et elles sont de plus en plus marrantes pour l'internaute. Elles ne se basent plus sur les événements clavier (keyup, keydown...). L'événement principal est désormais le clique. Vous êtes donc amenés à glisser-déposer (drag & drop) un élément correct, cliquer sur la chose définie dans le libellé ou déverrouiller un cadenas.

Voici quelques liens qui méritent une attention particulière :

  1. http://demo.modernization.co.ke/factcha/client/form.html

  2. http://blog.lukeblackamore.com/2009/10/sexy-captcha-new-drag-and-drop-captcha.html

  3. http://jordankasper.com/jquery/captcha/examples.php

  4. http://www.myjqueryplugins.com/QapTcha/demo


L'article écrit en rythme de:
Slaï - La dernière danse
Bartosz KONIECZNY 08-05-2011 21:11 sécurité des applications web
Un conseil Symfony2

Comment valider les checkboxes avec Symfony2 ?

La validation des checkboxes sous Symfony2 se déroule avec une contrainte appelée ChoiceConstraint.

Voici l'exemple de l'utilisation:

$metadata->addPropertyConstraint('orderPreferedGift', new Choice(array('choices' => Gifts::getGiftTypes(true), 'multiple' => true, 'min' => 1,  'multipleMessage' => "Veuillez choisir au moins un type de cadeau", 'groups' => array('validationGroup'))));


Cette contrainte est très puissante. On peut déterminer par exemple la quantité des champs minimale ou maximale à cocher par utilisateur. Il est également possible de vérifier le types des valeurs.