02 julio 2013

Cazando bugs: plugin NextGen Gallery (CVE-2013-3684)

Esta es la historia de la caza de un bug. Revisando los cientos de casos de phishing que asolan la seguridad de los Internautas encontré que el 90% de los sitios reportados usaban CMS populares, repartiéndose a partes iguales Wordpress y Joomla! las tareas de albergar las copias fraudulentas de las entidades afectadas. Generalmente los 'malos' buscan sitios con estos CMS para explotarlos y meter su código. Esto no significa que Wordpress o Joomla! sean vulnerables; en la mayor parte de los casos son los administradores los que convierten un buen software en una puerta trasera para los phishers y botnets.

En algunos casos terminan sacando las cuentas de los administradores probando pequeños diccionarios (os sorprenderían los usuarios admin con contraseña admin que hay por ahí), o obteniendo las contraseñas con algunos de los cientos de troyanos que siguen activos. Pero el mayor vector de ataque son los plugins y temas desactualizados que se van dejando por ahí.

Durante mis investigaciones me encuentro con bugs conocidos y públicos  como el infame timthumb que 2 años después de su publicación sigue causando estragos, pero otras veces me encontraba con sitios comprometidos que aparentemente estaba correctamente actualizados.

Tras mirar decenas de casos empecé a encontrar patrones (seguro que solo estaban en mi cabeza) y ver que muchas de las instalaciones de Wordpress tenían plugins en común. Uno de ellos era NextGen Gallery, con mas de 7 millones de descargas. Un buen sitio donde empezar a mirar. Así que descargué la última version (1.9.12 en aquel momento) desde la página de Wordpress, donde todavía podréis encontrar versiones antiguas.

Imaginaba que el fallo no podría ser muy complicado; necesitaba algo que permitiera ejecución de código PHP remoto o subida de ficheros. Así que la primero que podría hacer era buscar algo que permitiera la subida de ficheros y saltarme las restricciones que tuviera. Un simple find me daría la primera pista:

Ignoremos los .js de momento, no busco explotar un XSS o similar en el navegador. Pero vamos a echar un vistazo a ese admin/upload.php. Cuando miro estos scripts voy buscando el código que me interesa y los leo en diagonal para intentar no perder tiempo leyendo miles de lineas que no aportan nada a mi objetivo. Si es necesario, los vuelvo a mirar cuando necesito saber más del código, así que eliminaré las partes que, de momento, no me interesan y lo marcaré con [... snip ...]:

El comentario es de la cabecera es prometedor. Un pequeño fichero donde comprueba que existe una cookie de validación (según el comentario swfupload puede eliminarla) y la sesión (volveremos a esto más tarde) y una llamada nggAdmin::swfupload_image(), pero nada de código que permita subir. Lo que si me llama la atención es que este script no es una librería ni una clase, su codigo se ejecuta cuando se invoca. También veo que necesita que la constante NGGALLERY_ABSPATH este fijada, por lo que no parece que se pueda invocar directamente, parece que en algún lugar se hace un include del fichero. Y vemos también que lee el parámetro galleryselect del POST.

Pero antes, hay que asegurarse si este script realiza la función que dice y me pongo a revisar nggAdmin::swfupload_image(). Debería estar en admin/functions.php ya que es el único include que hay:


Vemos que trata con la variable superglobal $_FILES y que ejecuta la función move_uploaded_file(). $_FILES guarda los ficheros enviados en un POST con Content-Type multipart/form-data que se usa cuando existe un elemento input del tipo file. Cuando PHP recibe esta petición descarga el fichero en una ubicación temporal y rellena $_FILES. Usando la función move_uploaded_file() trasladamos ese fichero a la ubicación que deseemos. Así que efectivamente admin/upload.php hace lo que dice. Vemos que el parámetro galleryselect le llega a esta función como $galleryID y que tiene que ser un numero entero.

 Para obtener este id válido es fácil si navegamos por las galerías del sitio y observamos los links del código HTML:


En el id del primer div podemos ver que el ID de la galería es el 1 y el post el 68. Y en la etiqueta <a href> de la imagen vemos que la galería se llama test. Así que ya tenemos un id de galería que pasarle a galleryselect y la ruta donde va a quedar el fichero que subamos (wp-content/gallery/test)
Pero todavía nos enfrentamos a 2 restricciones. La primera es saltarse la validación de sesión, la segunda es como invocar admin/upload.php con nuestros datos.

Saltarse la validación de sesión que debería ser lo más complicada se vuelve trivial e incluso divertida cuando volvemos a revisar el script:

Lo primero que hace es validar la cookie usando wp_validate_auth_cookie(). Se supone que esta función comprueba si el usuario esta autentificado o si la cookie a expirado, pero el autor del script, en caso de que la cookie este comprobada, la vuelve a comprobar. Vale, es extraño de por si, pero si os fijais bien, en caso de que la cookie no sea válida y wp_validate_auth_cookie() devuelva falso... ¡No hace nada! O lo que es lo mismo, deja continuar con la subida de ficheros. ¡Premio!

Ahora solo queda investigar como explotar esta vulnerabilidad. Haciendo un par de greps por upload.php nos damos cuenta de que el único sitio donde se llama a upload.php es en nggallery.php, el fichero principal del plugin:


El include del fichero vulnerable lo realiza en la función handle_upload_request() y esta es añadida a la acción init, que Wordpress ejecuta antes de procesar cada petición. La función handle_upload_request() comprueba si el parámetro nggupload existe. Si es así, se procede a ejecutar la subida de ficheros y salir limpiamente. Funciona con cualquier URL válida dentro de Wordpress: en un post, en el index, en /wp-admin, etc. donde se incluya en el GET el parametro nggupload. Debido al bug que hemos encontrado, no comprobará el usuario. Así que nuestro exploit esta listo para ser escrito:


A este bug se le asigno el CVE-2013-3684 y se reportó a los autores y en 2 semanas publicaron la versión 1.9.13 con una escueta frase en el Changelog indicando que la vulnerabilidad estaba solucionada:
Secured: Ensure that only logged in users can upload images

Pero como la vida es un camino duro y difícil  este no era el bug que estaba buscando. Dejo a las intrépidas mentes de los lectores revisar el código y descubrir que hace que este fallo no permita subir código ejecutable.

----------------------------------

Artículo cortesía por Marcos Agüero (wiredrat @ gmail.com)

5 comments :

Jorge García dijo...

Buen artículo wire ;)

Jose Romero dijo...

Hola Marcos, se me hace que no puedes subir ejecutables, por el chmod() que debe dejar los archivos como solo lectura. (no tiene sentido un archivo de imagen ejecutable) ¿No?

wiredrat dijo...

No exactamente. Subir ejecutables estaría bien, pero si pudiera usarse para subir scripts (interpretados, no ejecutados) solo necesitarían permisos de lectura (para el usuario con el que corra el servidor web), que por supuesto va a tener. Pero eso es lo que no se puede hacer... La respuesta esta en las partes de código que he omitido.

Josué González dijo...

Buenas. Supongo que comprobará que sólo puedas subir archivos JPG o PNG, no?.


Buen artículo, un saludo!.

Jonathan Artista dijo...

Hola! tengo un problema con ese plugin a ver si alguien puede ayudarme.. Ya van varias veces que el servidor, donde tengo alojado mi sitio wordpress, me bloquea la IP culpa de este plugin que hace muchas llamadas y el servidor lo toma como un ataque.. cómo puedo solucionar el problema sin tener que cambiar de plugin? me es muy útil.. Gracias!