[HCTF 2018]WarmUp1
php
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
第一步:确定最终目标(我们要去哪里?)
审计代码前,先看提示:hint.php页面的内容是flag not here, and flag in ffffllllaaaagggg。
这告诉我们,我们的最终目标是利用 include 包含 ffffllllaaaagggg 这个文件。
但是,这个文件不在当前目录,根据经验(以及你最终的成功),它通常在系统的根目录 / 下。
所以,我们最终需要让 include 执行类似这样的语句:
include '../../../../ffffllllaaaagggg' (利用../回退到根目录)。
第二步:分析约束条件(什么阻碍了我们?)
我们要包含目标,必须经过主逻辑的检验:
php
if (! empty($_REQUEST['file']) && is_string($_REQUEST['file']) && emmm::checkFile($_REQUEST['file'])) {
include $_REQUEST['file'];
}
这意味着,我们传入的 file 参数,必须让 checkFile 函数返回 true。
第三步:寻找突破口(如何让 checkFile 返回 true?)
进入 checkFile 函数内部,我们看看如何通过它的检测:
-
防线1 & 防线2(严格匹配) :
前两道防线要求我们传入的值完全是
source.php或hint.php。如果我们传
hint.php,确实能返回true,但include只会包含hint.php的内容,拿不到 flag。我们需要附加穿越路径../,一旦附加,就不等于hint.php了,这两道防线必然失败。 -
防线3(截取匹配 - 突破口出现!) :
看这段核心代码:
php$_page = mb_substr($page, 0, mb_strpos($page . '?', '?')); if (in_array($_page, $whitelist)) { return true; }这段代码的意思是:找到字符串中第一个
?出现的位置,并截取它前面的所有内容,然后判断截取后的内容是否在白名单里。灵光一闪的时刻 :如果我在白名单文件后面加一个
?,然后再加上目录穿越的代码呢?假设我传入:
hint.php+?+../穿越代码- 函数执行截取:找到第一个
?,截取出前面的hint.php。 - 函数执行判断:
hint.php在白名单里! - 函数返回
true!我们绕过了检测!
- 函数执行截取:找到第一个
第四步:构建 Payload(组装武器)
现在我们知道要用 ? 作为分隔符,接下来解决目录穿越的问题。
-
前半部分(用来骗过 checkFile) :
hint.php? -
后半部分(用来让 include 找到 flag):我们需要回退目录。
如果我们直接拼凑:
hint.php?../../../../ffffllllaaaagggg交给
include执行时,系统会怎么解析?系统会试图寻找一个名叫
hint.php?的目录 ,然后进入它的上一级../。但
hint.php?这个目录存在吗?不存在!在 Linux/Windows 中,遇到不存在的目录进行
../回退会怎样?系统会忽略不存在的层级,直接在当前真实目录进行回退!所以,
hint.php?被当成了一个无用的空目录名,../依然发挥回退上级目录的作用。 -
完善穿越路径 :
我们不知道当前脚本执行的具体深度,通常 Web 目录大概是
/var/www/html/(3级)或者更深。为了保险,我们多回退几级,比如 4 级。回退到顶(根目录
/)后,再回退依然在根目录,不会报错。路径补充为:
../../../../ffffllllaaaagggg -
最终拼装 :
前半部分 + 后半部分
hint.php?+../../../../ffffllllaaaagggg=
hint.php?../../../../ffffllllaaaagggg
第五步:微调细节(让路径更规范)
在第四步我们得到了 hint.php?../../../../ffffllllaaaagggg。
这个 payload 已经可以用了。但为了更符合文件路径的规范逻辑,我们可以在 ? 后面加一个斜杠 /,明确表示 hint.php? 是一个目录,后面的是路径。
- 加入
/后:hint.php?/../../../../ffffllllaaaagggg
让我们验证一下这个最终的 payload:
- 传入参数 :
file=hint.php?/../../../../ffffllllaaaagggg - 防线3截取 :找到第一个
?,截取得到hint.php-> 白名单校验通过! - include 执行 :
include 'hint.php?/../../../../ffffllllaaaagggg'- 文件系统找目录
hint.php?/(不存在,当空气) - 执行
../../../../回退到根目录 - 找到
ffffllllaaaagggg - 包含成功!拿到 Flag!
- 文件系统找目录
总结推导思路
- 目标 :包含
../../../../ffffllllaaaagggg。 - 阻碍 :必须包含白名单关键词
hint.php。 - 矛盾:白名单关键词和穿越路径不能同时存在,除非有一种方法让代码只看到关键词。
- 破局 :发现
mb_strpos($page . '?', '?')截取逻辑,利用?作为视觉盲区,前面骗过代码,后面执行穿越。 - 成型 :
白名单关键词?目录穿越符目标文件->hint.php?/../../../../ffffllllaaaagggg。