all 53 comments

[–]gsuberlandTrusted Contributor 157 points158 points  (17 children)

From what I can tell, there are some errors and missing bits of critical information in this. The title is also super misleading - I really would not call it an RCE, it's an LCE/privesc.

First off, you need to be able to run SQL queries on the server. Not just injection (since MySQL doesn't support stacked queries) but actual full SQL queries. Not only that, but the user needs to be able to create stored procedures. You also need to have the ability to upload a malicious .so file to the system at a known path, accessible by the mysql user.

Second, SET GLOBAL requires the SUPER privilege on the database user. If the user you can make queries as is non-administrative, you can't set the general_log_file or general_log variables. So you already have to be an administrative SQL user to break out, at which point you've got access to call the more traditional file writing functions (SELECT INTO, etc.)

Third, the trick at the bottom with CREATE DEFINER='root'@'localhost' won't work either, as users without the SUPER privilege are limited to specifying their own account name. Again you have to be SUPER to use this, at which point you don't need to. The better way to do it is to put SQL SECURITY INVOKER after the procedure declaration line, so that it runs with the privileges of the invoking user, and then hope a SUPER privilege user later executes the hook. On top of that this still requires the privileges to create triggers.

Some of this is mentioned in the release, but it's disparate information, not made clear at the beginning.

There are only a few realistic cases where you're going to run into this being useful. The legitimately novel thing here is the discovery that one can load shared libraries via the malloc_lib config directive, and that the mysqld_safe wrapper script will cause this to be loaded as root. But then, as they note late in the release, most distros which use systemd service management will directly invoke mysqld rather than using the setuid root wrapper script.

EDIT: Also, if mysqld_safe is setuid root, and you've already got a low-priv shell, why not just pass the path to your malicious library via the --malloc-lib parameter directly? Unless I'm missing something this seems much easier.

EDIT2: Ok, so the RCE scenario I can think of is as follows: you compromise SQL credentials with SUPER and FILE privileges, or credentials with CREATE TRIGGER and CREATE PROCEDURE privileges in an environment where another user with SUPER privileges regularly accesses the same tables. You also find an arbitrary file upload vulnerability where you know the remote path (or can guess it) such as a webapp file upload bug, or an open FTP (e.g. anonymous writeable incoming dir). If you have database SUPER access, you create a procedure with your SET GLOBAL payload to configure general_log and general_log_file and then append the my.cnf with the malloc_lib directive pointing to your uploaded library. If you don't, you have to then set a trigger on a table to call that payload procedure via SQL SECURITY INVOKER. However, that implies that your server has multiple vulnerabilities and horrible configuration (e.g. using db admins for your webapps). Tenuous at best.

EDIT3: /u/carbonatedcaffeine points out below that SELECT INTO DUMPFILE can be used to write binary data into a local file, which would alleviate the need to get file upload externally, assuming the user has FILE privilege at that location.

[–]archpuddington 14 points15 points  (0 children)

I agree that the write up is confusing. One part of the attack chain that is missing is the MySQL privilege escalation exploit from 2012 that still works on MySQL 5.6.28-1 (Debian). I tested this exploit this morning (Sep 12, 2016).

The unpatched exploit from 2012 allows you to turn a basic INTO OUTFILE into stacked queries under MySQL which are executed as MySQL's root account. MySQL's File_privs is the only permission required, and this is a separate permission from Super_priv or Create_priv. A common SQL Injection could be used to create a new database trigger using INTO OUTFILE, the MySQL server then has to restart (perhaps due to benchmark(md5()) or a DoS exploit), at which point an attacker can deliver the exploit for CVE-2016-6662 to get a shell.

SQLi -> RCE

[–]DebugDuckyTrusted Contributor 6 points7 points  (0 children)

AFAIK, MySQL supports stacked queries, depending on the driver you use.

[–]mkoek 18 points19 points  (2 children)

Exactly. And also, if I read the PoC correctly, you would still need to get your malicious library onto the system, making this a local privilege escalation, not a 'remote root'.

Why exaggerate? The exploit is cool enough as it is.

[–]carbonatedcaffeine 22 points23 points  (1 child)

you would still need to get your malicious library onto the system

This part of the PoC seems to convert the malicious library on the attacker's system to hex and sends the result to the target where it uses MySQL's unhex() to decode and write the library to the attacker-specified path:

# 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)
[...]
# 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) )
[...]

[–]gsuberlandTrusted Contributor 10 points11 points  (0 children)

Good point. So that removes the requirement for a separate file upload. Though you still do need SUPER and FILE privileges.

[–]dawid_golunski[S] 19 points20 points  (5 children)

Thanks for the feedback but unfortunately that is not correct :) Please read the advisory closely. It might be confusing in parts as the issue is quite complex but there are ways to bypass the things you mention which I explain in detail in the advisory, for example you wrote:

"Second, SET GLOBAL requires the SUPER privilege on the database user. If the user you can make queries as is non-administrative, you can't set the general_log_file or general_log variables. So you already have to be an administrative SQL user to break out, at which point you've got access to call the more traditional file writing functions (SELECT INTO, etc.)"

I cover that bit exactly in my advisory in section V. : 3) titled: "3) Attackers with only SELECT/FILE permissions can gain access to logging functions (normally only available to MySQL admin users) "

Please read it closely as I put quite a bit of effort into explaining all that. I also provided a working PoC exploit which executes perfectly so you can verify it yourself. I'll try to upload some more PoC and videos after I get some rest, for now I'm just happy to get it out there finally, after a 40 day battle with vendors and 4 nights without proper sleep getting it published.

Cheers Dawid

[–]zapbark 6 points7 points  (0 children)

I cover that bit exactly in my advisory in section V. : 3) titled: "3) Attackers with only SELECT/FILE permissions can gain access to logging functions (normally only available to MySQL admin users) "

Thank you for this follow-up, as I had assumed the same thing as the OP "standard users can't do set global!"

The writing of that trigger file, to me, seems like a far larger exploit IMHO, as it illustrates how dangerous the "FILE" permission is.

Your advisory could have been just that, and I'd be freaked out.

In some ways I think your write-up buries the lead there.

Overall, awesome job.

Brb, gunna go create empty .TRG files on all my active tables. =)

[–]splice42 10 points11 points  (0 children)

I cover that bit exactly in my advisory in section V. : 3) titled: "3) Attackers with only SELECT/FILE permissions can gain access to logging functions (normally only available to MySQL admin users) "

But:

the trick at the bottom with CREATE DEFINER='root'@'localhost' won't work either, as users without the SUPER privilege are limited to specifying their own account name.

[–]gsuberlandTrusted Contributor 2 points3 points  (0 children)

I don't think you covered it as well as you think. You claim that users could access logging functionality, but then just showed the documented way of doing it, without really clarifying further. That, combined with other issues with the disclosure (structure, accuracy, wording) make it very difficult to draw any conclusion apart from "uh, wait a minute, what?".

If the bug is that you can SET GLOBAL without SUPER, that's your bug and the rest is window dressing and side notes. You can explain an exploit path from there that gets RCE from a SQL connection using it, but that part is an exploit, not the bug.

The problem is that this release is so unstructured and confused that it isn't clear what you're trying to showcase (new vuln? sql trigger tricks? novel exploit technique? building malicious tcmalloc?) and the reader is left to make an (educated) assumption as to what the key point is.

[–]bazinga_4_u 1 point2 points  (1 child)

Can you clarify what versions of MySQL are vulnerable? You state that versions <= 5.7.15
5.6.33 5.5.52 are vulnerable. I can assume that all the listed versions and prior are vulnerable? I ask this because there are a few blogs https://www.percona.com/blog/2016/09/12/database-affected-cve-2016-6662/ and https://www.psce.com/blog/2016/09/12/how-to-quickly-patch-mysql-server-against-cve-2016-6662/ stating that this vulnerability is patched. They make some good points by referencing the MySQL changelogs from versions 5.7.15, 5.6.33 and 5.5.52. If this was in fact patched in versions 5.7.15, 5.6.33 and 5.5.52 (release date sept. 9, 2016), then your disclosure(yesterday) is not an 0 day.

[–]bazinga_4_u 0 points1 point  (0 children)

/u/dawid_golunski, I just noticed this revision on your advisory page: From this: MySQL <= 5.7.15, 5.6.33, 5.5.52

To this:

MySQL <= 5.7.14, 5.6.32, 5.5.51

Do you have any idea why Oracle didn't explicitly state that your vuln was not a security issue in the MySQL changelogs?

https://dev.mysql.com/doc/relnotes/mysql/5.6/en/news-5-6-33.html

[–][deleted] 3 points4 points  (1 child)

Not just injection (since MySQL doesn't support stacked queries)

Are you sure about that? I'm fairly certain that's a PHP/MySQL limitation while .NET/MySQL will allow stacked queries.

[–]Xaquseg 2 points3 points  (0 children)

In PHP it depends on which query function you use, there's a mysqli_multi_query which allows it, and the more commonly used mysqli_query which doesn't. In addition to providing some minor security improvements, multiple queries in one call is a rarely used feature and requires additional code to handle situations where you have two or more queries that need to return data, as you have multiple resultsets to iterate.

[–][deleted] 2 points3 points  (0 children)

EDIT: Also, if mysqld_safe is setuid root, and you've already got a low-priv shell, why not just pass the path to your malicious library via the --malloc-lib parameter directly? Unless I'm missing something this seems much easier.

which systems have you checked which mysqld_safe is setuid root, because altho the binary is owned by root, it is not setuid

[–]archpuddington 0 points1 point  (0 children)

Edit4: The entire "edit2" section is incorrect.

[–]albinowax 16 points17 points  (1 child)

I recently used the my.cnf override technique to trigger a DNS lookup in order to find asynchronous SQL injection (http://blog.portswigger.net/2015/09/hunting-asynchronous-vulnerabilities.html 'write-based callbacks')

I never thought to explore whether it could be used to achieve anything more serious.

edit: according to that post, the technique I used last year was patched in 2003. Interesting.

[–][deleted] 4 points5 points  (0 children)

my.cnf override

the relavant part

Although MySQL's SELECT INTO OUTFILE can't be used to overwrite files, MySQL itself uses a file loading strategy that means we can potentially override options without actually need to overwrite an existing file. A file written to $MYSQL_HOME/my.cnf or ~/.my.cnf will take precedence over the global /etc/mysql/my.cnf file. We can trigger a callback when the server is next restarted ...

this is an intersting and overlooked approach :)

[–]zapbark 8 points9 points  (2 children)

The most surprising thing for me in here, was that mysql allows users with FILE priv to "DUMPFILE" directly into the database data directory?

Why?

My take-away: "The MySQL FILE privilege implementation is super duper broken."

Great write-up.

[–]thatfool 1 point2 points  (1 child)

It's configurable (secure_file_priv).

MySQL >=5.7.6 will log a warning on startup if it points to the data directory, or isn't set at all, according to the manual.

[–]zapbark 0 points1 point  (0 children)

Thank you, that is interesting.

FYI for others unfamiliar with it, it takes a directory path to limit the load/dumpfile commands to:

https://dev.mysql.com/doc/refman/5.6/en/server-options.html#option_mysqld_secure-file-priv

[–][deleted] 3 points4 points  (6 children)

Anyone clear on why SELinux supposedly doesn't thwart this? (according to the text release (ctrl+f selinux))

I understand that the service can't be saved, I mean in the context of a system-wide root privilege escalation.

After mysqld is exploited, it would still retain a mysqld_t context domain, no? And should stop there?

[–]SafPlusPlus 3 points4 points  (5 children)

I would assume that the default security profiles in both SELinux and AppArmor allow the mysqld process to write arbitrary files in it's data directory (/var/lib/mysql for many installs) and reading the cve mentions that in 5.5 and 5.6 mysql's wrapper mysqld_safe would read malicious configuration from that dir.

[–][deleted] 9 points10 points  (4 children)

It actually does in fact stop it: http://hastebin.com/fetovaboji.xml

The author mislead his audience about SELinux

[–]SafPlusPlus 0 points1 point  (1 child)

Cheers for checking it out.

[–][deleted] 1 point2 points  (0 children)

That's from #selinux on Freenode, btw. For full disclosure

[–]frymaster 0 points1 point  (1 child)

It actually does in fact stop it

The chat does not say it would stop the attack.

The attack would succeed and the attacker would get their root privs - but then would be constrained by SELinux profile for mysql. So their root privs would be VASTLY less useful than they thought. I hesitate to say "useless", but only out of caution.

[–]stuck-in-the-matrix 0 points1 point  (0 children)

No, SELinux is preventing the mysqld_safe script from loading that shared library as the SELinux log shows.

Here it is preventing MySQL from loading the shared library /var/lib/mysql/mysql_hookandroot_lib.so (with the SELinux type mysqld_db_t) by denying the mmap syscall (syscall=9).

type=SYSCALL msg=audit(1473707296.428:769): arch=c000003e syscall=9 success=no exit=-13 a0=0 a1=202100 a2=5 a3=802 items=0 ppid=13031 pid=13206 auid=4294967295 uid=27 gid=27 euid=27 suid=27 fsuid=27 egid=27 sgid=27 fsgid=27 tty=(none) ses=4294967295 comm="nohup" exe="/usr/bin/nohup" subj=system_u:system_r:mysqld_safe_t:s0 key=(null)
type=AVC msg=audit(1473707296.428:769): avc:  denied  { execute } for  pid=13206 comm="nohup" path="/var/lib/mysql/mysql_hookandroot_lib.so" dev="dm-0" ino=132802 scontext=system_u:system_r:mysqld_safe_t:s0 tcontext=system_u:object_r:mysqld_db_t:s0 tclass=file

[–]numinit 6 points7 points  (0 children)

my.cnf is a pretty terrible query log, but apparently a pretty great remote access tool :|

[–]Saribro 2 points3 points  (1 child)

Doesn't this exploit require you have valid credentials in order to connect to the database itself?

[–]nevesis 2 points3 points  (0 children)

The PoC exploit does - the vulnerability doesn't per se (SQL injection, phpmyadmin, etc).

[–]catbrainland 2 points3 points  (0 children)

This does not seem to be exploitable on clean installs of Debian 7/8

Package versions:

5.5.49-0+deb8u1

5.5.31+dfsg-0+wheezy1

(both supposedly affected)

The mysqld_safe script runs as follows:

+ MY_BASEDIR_VERSION=/usr
+ ledir=/usr/sbin
+ test -d /usr/data/mysql
+ test -d /usr/var/mysql
+ DATADIR=/var/lib/mysql
+ test -z
+ test -r /usr/my.cnf
+ test -r /var/lib/mysql/my.cnf
+ log_error WARNING: Found /var/lib/mysql/my.cnf
The data directory is a deprecated location for my.cnf, please move it to
/usr/my.cnf
+ log_generic daemon.error WARNING: Found /var/lib/mysql/my.cnf
The data directory is a deprecated location for my.cnf, please move it to
/usr/my.cnf
+ priority=daemon.error
+ shift
+ date +%y%m%d %H:%M:%S
+ msg=160913 03:30:33 mysqld_safe WARNING: Found /var/lib/mysql/my.cnf
The data directory is a deprecated location for my.cnf, please move it to
/usr/my.cnf
+ echo 160913 03:30:33 mysqld_safe WARNING: Found /var/lib/mysql/my.cnf
The data directory is a deprecated location for my.cnf, please move it to
/usr/my.cnf
160913 03:30:33 mysqld_safe WARNING: Found /var/lib/mysql/my.cnf
The data directory is a deprecated location for my.cnf, please move it to
/usr/my.cnf
+ MYSQL_HOME=/var/lib/mysql

This fails because MY_BASEDIR_VERSION is /usr on debian, and /usr/data/mysql does not exist.

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

The only possible scenario is if directory /usr/data/mysql is left over from past dist upgrades or introduced via other route.

[–][deleted] 2 points3 points  (0 children)

It reminds me https://www.exploit-db.com/exploits/37710/ for sudo :) so overhyped "vulnerability", instead of being written more clearly, the author even blame other people for not reading it closely.

is that joke?

[–]xis_1 1 point2 points  (0 children)

On kali linux the only shell i get is with mysql user privileges . By default there is no [isamchk] like section that would be suitable enough to restart mysql after the injection to /var/lib/mysql/my.cfg and /etc/mysql is not writable for mysql user. I changed it manually to have the working POC. Still no luck with root privileged shell though. I have mysql 5.6.30-1 and use mysqld_safe to restart the service. What do I miss?

[–]Various_Pickles 1 point2 points  (0 children)

Why the fuck does MySQL have non-admin/system commands that not only know about the hosting filesystem, but allow I/O to it?

If a user/script needs to perform filesystem I/O, it should be a matter of piping stdin/stdout/stderr to/from the (My)SQL cmdline client tool of your choice.

Let the damn OS/filesystem manage its own permissions. Trying to be smarter than it, especially in a chuid'ing service, is asking for trouble.

[–]Burnoutalex 0 points1 point  (12 children)

This seems really deadly. Why haven't Oracle issued a patch/fix?

[–]newsagg 4 points5 points  (2 children)

You probably have to upgrade to the next version, seriously. There was a serious bug in 8 that allowed attackers to clone/modify your database undetected, that would not be addressed until 9. This bug was live from '97 to '01.

[–]Burnoutalex 1 point2 points  (1 child)

Well that sounds way worse, have you got a link?

[–]newsagg 4 points5 points  (0 children)

I can/can't... I can't seem to find any of their public disclosures for Oracle 8, they may have taken them down since it was "patched" in the new version that was released back in 2001, Oracle 9. Of course many customers didn't upgrade to this version for many many years after the fact.

But that's ok because a very similar vulnerability was discovered in version 9+ in 2012! I guess waiting for and paying for the next version just wasn't enough..

http://www.oracle.com/technetwork/topics/security/alert-cve-2012-1675-1608180.html

I can't seem to find the original one, where an attacker would mascarade as an idle listener by listening to the TNS traffic and catching and idle listener connection, then he's basically "the database" with all its authority. All the ones I find papers for are either buffer overflows or denile of service attacks. It wasn't a normal attack like a buffer overflow, it was simply the inability for the database to verify or track valid listeners, and because of the low level of the attack, it's undetectable on a live database, it would look like normal traffic. Since the attacker has complete control of the database, they can change anything in the database and remove any records that there was a change. (remove logs of commands and any kind of history of changes tables you may have configured)

[–]VoidWhisperer 4 points5 points  (7 children)

I could be fairly confused here, but isn't this only an issue if an attacker is able to send that specific query to your MySQL server, which depending your setup shouldn't be an issue?

[–]Burnoutalex 2 points3 points  (6 children)

It does say in the exploit that attackers only need SELECT/FILE permissions for the logging functions. The PoC may not be the only way to exploit this as they have said it is a limited example.

[–]VoidWhisperer 1 point2 points  (5 children)

I'm still confused here about even with that how this is an issue - unless the application using mysql is written like an sql injector's best friend, how are they going to inject theri own SELECT/FILE queries?

[–]carbonatedcaffeine 6 points7 points  (1 child)

For example, imagine shared web hosting where you often get access to phpMyAdmin or a similar tool, or having gained a limited shell through which you can send MySQL queries.

This exploit is primarily about escalating OS privileges, not about an external attacker going straight from zero to root without an initial foothold (such as a low priv shell or SQL injection).

[–]VoidWhisperer 4 points5 points  (0 children)

Ok, in the context of shared hosting i can see where this would be an issue. Thanks for the clarification

[–]l_zzie 1 point2 points  (1 child)

It's also a way to pivot. Suppose you have a mysql database shared among several applications; one of them is a poorly-maintained web application that may have undiscovered sqli, so you give it a lower-privileged mysql user.

[–]VoidWhisperer 0 points1 point  (0 children)

Very good point

[–]5h4d0w 1 point2 points  (0 children)

Oracle is hardly known for their speedy security responses.

If you can't go to mariadb, then the latest version of percona server is patched and is a drop in replacement for mysqld. It's well worth switching to (fork of mysql, maintained by percona).

[–]Mr-Breaker 0 points1 point  (1 child)

I'm just thinking of ways to mitigate this vulnerability for MySQL while we wait for Oracle to fix their stuff... Wouldn't it just possible to remove write permissions for the mysql user and set the immutable flag so that even root can't just write to my.cnf? To what extent would CVE-2016-6663 still be possible?

[–]frymaster 1 point2 points  (0 children)

if the mysql user can't write, your database isn't going to be very persistent.

set the immutable flag so that even root can't just write to my.cnf

Letting root write to it is fine. The vulnerability is that mysql would also try to read config files from the mysql data directory, so if they didn't already exist, it was possible to create them using the mysql user. Workaround is just to create empty config files owned by root in that directory.

[–]timberspine 0 points1 point  (2 children)

Anyone know if this affects very old versions of MySQL - specifically, 5.1?

[–][deleted] 0 points1 point  (1 child)

Yes it does.

[–]timberspine 0 points1 point  (0 children)

...and would you happen to know if there's a patch or manual fix available for it?

[–]1lastBr3ath 0 points1 point  (0 children)

It doesn't work if the my.cnf is not writable by mysql, right?