<?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\" />";
}
?>
提示 flag 在 ffffllllaaaagggg 文件中。
$_REQUEST['file']
既可以接收 file 传参也可以接收 POST 传参;
emmm::checkFile
表示调用 emmm 类的方法 checkFile ,::
在 PHP 中可以用来调用类的静态方法或静态属性,而不需要实例化对象;
mb_strpos
函数:查找字符串在另一个字符串中首次出现的位置
mb_strpos(
string $haystack,
string $needle,
int $offset = 0,
?string $encoding = null
): int|false
返回 string 的 haystack
中 needle
首次出现位置的数值。 如果没有找到 needle
,它将返回 false
。
比如本题中的:
mb_strpos($page . '?', '?')
先将 $page
的末尾连接一个问号,再查找问号在这整个字符串中第一次出现的位置。
mb_substr
函数:
mb_substr() 函数返回字符串的一部分,之前我们学过 substr() 函数,它只针对英文字符,如果要分割的中文文字则需要使用 mb_substr()。
用法示例:
<?php
echo mb_substr("菜鸟教程", 0, 2);
// 输出:菜鸟
?>
比如本题中的:
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
在 mb_strpos
函数返回了问号第一次出现的位置之后,再通过 mb_substr
函数将 $page
截断到第一个问号之前,结果返回给 $_page
传入的参数 file 会经过 emmm::checkFile
的检查,我们的目的是让它返回 true 。查看 checkFile 方法会发现有三个地方能返回 true 。
第一个地方白名单检查,过不去,我决定让它在第二个地方返回 true 。这里要提到一个知识点:目录穿越。
当我传入 payload :
source.php?../../../../../ffffllllaaaagggg
include $_REQUEST['file'];
变为:
include("source.php?../../../../../ffffllllaaaagggg")
source.php?..
整体会被看做是一个目录,而不会去检查它的正确性,在此目录基础上不断返回上级目录,并最终访问 flag 文件。
此 payload 可以通过第二次 return true;
的检查。
返回结果:
依此道理,可以在第三处返回 true ,需要 URL 编码一下,payload 为:
source.php%253F../../../../../ffffllllaaaagggg
对 ? 进行两次 URL 编码,在传参时,默认解码一次,得到 ? 的一次 URL 编码值,在经过第二处判断时,由于没有 ? ,返回的是完整字符串,故不通过,在经过第三次判断之前进行了一次 URL 解码,? 出现,故通过第三次判断,原理与第二次相同。