viernes, 9 de diciembre de 2011

LFI With PHPInfo()

El principal problema al explotar un LFI (Local File Inclusion) es encontrar un fichero donde podamos inyectar código para luego ejecutarlo a través de la vulnerabilidad. Lo más clásico es inyectar el código en los logs de cualquier servicio: web, ftp, ssh, etc. También se suele usar los ficheros que guardan las cookies de sesión o los relativos al proceso en marcha (/proc/self/...). Sin embargo, en muchos casos, no tendremos permiso de lectura sobre estos ficheros y en consecuencia no se podrá ejecutar el código inyectado.

Hace poco, gracias a un amigo, me enteré de una técnica novedosa para resolver este problema en un caso particular: cuando en la web hay un "phpinfo()".

En julio de este año se publicó un paper, en la web de INSOMNIA, donde se describen todos los detalles de esta técnica. Pueden encontrar el paper original y el exploit en estos links:

paper: http://www.insomniasec.com/releases/whitepapers-presentations
exploit: http://www.insomniasec.com/publications/phpinfolfi.py

Personalmente, esta técnica me ha parecido alucinante. Así que voy a explicar aquí lo que he entendido y naa... empecemos.

PHPInfo()

La función "phpinfo()" muestra gran cantidad de información sobre la configuración de PHP, las variables globales y el servidor web. Es frecuente que, para testear que el modulo de PHP este correctamente instalado, se cree un archivo "info.php" o de nombre similar que llame a esta función.

Fig. 1 - "info.php" / phpinfo()

Bien, si en la web que estas "auditando" hay un LFI y además un "info.php", entonces podrás utilizar esta técnica.

Archivos temporales de PHP

Cuando enviamos un archivo para que sea procesado por un script php, su contenido  es almacenado temporalmente dentro del directorio "/tmp" con un nombre que tiene este formato: "phpXXXXXX" donde "XXXXXX" son 6 caracteres aleatorios entre minúsculas, mayúsculas y números. Luego de terminado el procesamiento por parte del script el fichero temporal es borrado.

Como se dijo anteriormente, phpinfo() muestra abundante información del sistema y claro dentro de esta información no podían faltar los nombres de los ficheros temporales ;)

Para verlo de forma práctica, podemos crearnos un formulario HTML que envié un archivo a nuestro info.php y luego revisar en la sección "PHP Variables". Veamos:

<html>
 <head>
 </head>
 <body>
  <form action="/info.php" method="post" enctype="multipart/form-data">
   <input type="file" name="archivo">
   <input type="submit">
  </form>
 </body>
</html>

Fig. 2 - Enviar archivo a "info.php".

Fig. 3 - Nombre del archivo temporal.

Inyectando código en archivos temporales

Ahora bien, la idea es enviar el código que queremos inyectar usando POST como si fuéramos a subir un archivo normal. De esta manera, el código quedará almacenado en un fichero temporal dentro de "/tmp". Luego, usando el info.php, obtendremos el nombre del archivo temporal para finalmente ejecutar su contenido llamando dicho archivo desde el LFI. ¿Alucinante verdad? Pero aún falta.

El problema con esto es que el archivo es "temporal" y se borra a penas termina la ejecución del script, en nuestro caso info.php. Entonces se debe encontrar alguna forma de obtener el nombre del archivo y prolongar la ejecución de info.php un poco más para que al llamar al fichero temporal desde el LFI este aún exista.

PHP Output Buffering

Para optimizar la transferencia de datos, PHP utiliza un buffer donde almacena temporalmente la salida del script. Por defecto el tamaño máximo del buffer es de 4096 bytes. Cuando la salida del script supera el tamaño máximo del buffer la respuesta debe enviarse usando "Chunked Transfer Encoding", es decir, por partes. Podemos utilizar esta característica de PHP para resolver el problema anterior. La idea es forzar a que PHP nos envié la salida de info.php por partes e ir leyendo estas partes hasta llegar al nombre del fichero temporal. Mientras tanto aún faltaran por enviar algunas partes y la ejecución de info.php se prolongará un poco más. Así tendremos un pequeño lapso de tiempo entre el instante en que leemos el nombre del fichero temporal y el momento en que PHP termina de enviarnos todas las partes de la respuesta. Es en ese lapso de tiempo cuando podremos explotar el LFI.

Y... ¿Como forzamos a que la respuesta se envíe por partes? Pues para ello el exploit envía en los headers HTTP valores muy grandes (5000 As) y como esos valores se reflejan en la salida de info.php entonces el tamaño de la salida aumentará superando el buffer y deberá enviarse por partes.

Eso fue, para mi, ya demasiado alucinógeno xD

Conclusiones

La última pregunta es... ¿Y funciona? Pues el exploit que te bajas de la web de INSOMNIA te da una idea clara de lo que se pretende hacer, pero lo he provado tal cual viene y no funciona. Sin embargo es sabido que los exploits que publican los investigadores incluyen algún sutil error como medida "anti script kiddies".

Así que me programé mi propio exploit en java (sí, en JAVA!!) que hacía todos los pasos bien pero nunca encontraba el lapso de tiempo en que se podía explotar el LFI. Llegue a pensar que ese momento no existía y a investigar algunas formas alternativas de aprovecharme de los ficheros temporales. Pensé que quizá los nombres aleatorios no serían tan aleatorios así que revisé el código fuente de PHP para ver como construía el nombre aleatorio y lo hace llamando a las funciones mkstemp() o mktemp() de C, revise el código de las librerías C para ver como están implementadas esas funciónes y vi que ambas llaman a "__gen_tempname()" finalmente en el código de esa función vi que el nombre depende de una variable no inicializada, algunos bits aleatorios y el ID del proceso. Todo esto chocolateado con un algoritmo de módulos y divisiones. Si no me crees, puedes revisarlo. En fin, siguiendo ese camino no iba a llegar muy lejos.

Ya al borde de la desilusión se me ocurrió que quizá mi exploit no funcionaba por la forma en que java maneja internamente los sockets. Así que decidí echarle mano al exploit de INSOMNIA y buscar el error. Ya había revisado código C ¿Qué tan malo puede ser Python? Total que modifique algunas cosas del exploit y la conclusión es que ¡Sí funciona! :D

Para que me crean les dejo una demo en vídeo.





Otra conclusión que he sacado de todo esto es que debo aprender python xD

Nota: Si quieres una copia del exploit funcional puedes dejar tu comentario detallando tus malas intenciones y tus datos de contacto.

Un saludo y hasta la próxima.

22 comentarios:

  1. Hola Soy Carlos Ganoza y quiero defacear a Alguien en la Fisi mi correo es drneox@gmail.com :D

    ResponderEliminar
  2. Jajaja xD Eres un hacker muy malo, Neox. Bueno... ya te mando el script al correo...

    Y recuerda ejecútalo como root }:] hehehe...

    Saludos.

    ResponderEliminar
  3. hola solo quiero estudiar el codigo no tengo malas intenciones me podrias pasar el exploit :P

    mi correo es lactutal@gmail.com :)

    ResponderEliminar
  4. Hey man vi tu expo en limahack 2011 y pues reciente encuentro y que sorpresas das...
    Porfa aver si me envias el script asi lo estudio. oTRA ES QUE EN LA EXPO PhD en explotación con MetaSploit PARSEASTE el codigo a hexa pero con una tool o script ya que las comillas impedian en el navegador logre el resultado correcto aver si tambien t hechas una mano en eso. Gracias de antemano . Buenos videos explicativos y mejor el lenguaje que usas. Felicitaciones.
    Mi email:jesu.fernandez@hotmail.com

    ResponderEliminar
  5. Eso de pasar el código a hexadecimal para meterlo en la inyección, no tiene mucho truco... Lo hice con un programita en C, por que en la expo no tenia internet. Normalmente uso esta web:

    http://ostermiller.org/calc/encode.html

    Todos los pasos los puedes ver en este video:

    http://www.youtube.com/watch?v=zogWE2IqKlY

    Bueno, ahí les mando el script...

    Saludos.

    ResponderEliminar
  6. Man muchas gracias de antemano por e-mail y por la respuesta. T salio muy bien el video. Sabes de todos los portales tus videos son mas explicativos. Gracias man. Aver si pasas tu emial- y charlamos por msn. Atte. Jesu.fer.

    ResponderEliminar
  7. Hey no le des nada a ese Neox, es un lammer de aquellos, jajaja...

    Este... ese PHPInfo(), me parece conocido dónde lo habré escuchado! jajaja, no eras tú el que estabas haciendo las preguntas del caso a ya sabes quien, en ya sabes donde, jajajaja XD

    Nox.

    ResponderEliminar
  8. Jajaja, lo que no sabe Neox es que la tool va con sorpresa }:] Hehehe...

    See... es lo mismo que... él, expuso en... ahí. Como dije me pareció bastante interesante así que tenía que escribirlo xD

    Un saludo, Nox. Nos vemos...

    ResponderEliminar
  9. Quiero hechar un vistazo al exploit, y talvez con ese pueda defacear a la pagina de noxsoft.com.
    Saludos elix.0x87@googlemail.com

    ResponderEliminar
  10. Hola Elix, hombre... haberlo dicho antes!!! xD

    Bueno, ya esta publicado desde navidad... ahí te dejo el link

    http://alguienenlafisi.blogspot.com/2011/12/feliz-navidad.html

    Suerte, saludos.

    ResponderEliminar
  11. Exelente articulo!, y muy bien explicado.
    Habia leido en el paper de grupo insomnia, y escribi sobre
    los peligros del phpinfo. Pero me guto mucho tu articulo

    Dejo el enlace por si les interesa
    http://gonzac-studios.blogspot.com/2012/02/hack-los-peligros-del-phpinfo-y-como.html

    Saludos!!

    ResponderEliminar
  12. Hola, gracias por tu comentario :)

    Después de leer tu post, la conclusión solo puede ser: NUNCA DEJES UN PHPINFO.

    Un saludo...

    ResponderEliminar
  13. http://depositfiles.com/files/wbf6iciyd

    aka les dejo el programa para bajar actualizaciones del nod32 eset 4 bay k dios mes lo bendiga

    ResponderEliminar
  14. soy un chico malo sergioyoshiman(alt+64)hotmail.com

    ResponderEliminar
  15. Hola Sergio, el código del exploit está publicado en este link:

    http://alguienenlafisi.blogspot.com/2011/12/feliz-navidad.html

    Un saludo.

    ResponderEliminar
  16. que pasa cuando el lfi lo tenemos en un directorio protegido con un .htaccess osea 401.. y el lfi requiere

    code=../../../../../../../../../../etc/host.jpg

    no logro correr el exploit bajo estas caracteristicas.. ya modifique algunas cosas pero aun sale ("No php tmp_name in phpinfo output") pero no se si se deba a que realemnte no lo encuentra o no se estan enviando bien los parametros para validar el 401

    me ayudas?

    ResponderEliminar
  17. Ese error indica que no encuentra el nombre del archivo temporal en el output del phpinfo. Te sugeriría que verificaras manualmente si al enviar un archivo al phpinfo en la salida te devuelve el nombre temporal (como se muestra en el apartado "Archivos temporales de PHP"). Si no te devuelve el nombre temporal quiza se deba a que se ha deshabilitado el uploading de ficheros. Recuerda que esta forma de explotación depende de una condición de carrera en la que hay que leer el nombre temporal antes de que PHP termine de enviar todos los datos y que si no ganas esa carrera el código inyectado no se ejecutará.

    Un saludo.

    ResponderEliminar
  18. si puedo ver el "tmp_name" manual al subir el archivo al phpinfo.. lo que sucede que no logro modificar el script para poder loguearme dentro del members area protegido por el 401.. aun teniendo el (user : pass)
    osea el
    Authorization: Basic erwrJHUYtYUHUTtrUTYYUY : ejem

    ResponderEliminar
  19. Debería ser suficiente con añadir la cabecera Authorization al request HTTP. En todo caso usa un sniffer (wireshark por ejemplo) para verificar que efectivamente se envía la cabecera authorization.

    ResponderEliminar
  20. gracias por el tip de wireshark estaba buscando algo como eso..
    esta muy bueno el blog.. lo tengo como favorito !
    y gracias por contestar tan rapido

    ResponderEliminar
  21. hola me gustaria migrar el exploit a perl y probar si funciona en sis Windows.

    saludos..
    jimr981{arroba}gmail.com

    ResponderEliminar
    Respuestas
    1. Para Windows puedes usar esta técnica (http://blog.alguien.site/2013/01/lfi-2-rce-en-windows-con-archivos.html). Aprovecha los temporales sin necesidad de un PHPINFO.

      Un saludo.

      Eliminar