[HCTF 2018]WarmUp
[HCTF 2018]WarmUp
解题步骤
开启靶场页面上只有一个大滑稽脸,没有什么特别有用的信息,直接查看源码
看到提示,网址上拼接url访问/source.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\" />"; // 输出滑稽脸
}
?>
public static function checkFile(&$page) // 静态方法
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; // 白名单
先是定义了一个名为 checkFile 公共的静态方法,主要用于对传入的 $page 变量进行初步的合法性检查,定义了一个白名单数组,包含两个键值对,为允许访问的文件白名单
if (! isset($page) || !is_string($page)) { // 参数检查
echo "you can't see it"; // 提示信息
return false; // 退出
}
这段代码是文件访问控制的 “前置检查”,先验证 $page 的基本合法性(是否存在、是否为字符串),如不存在或不是字符串则输出提示并返回false
if (in_array($page, $whitelist)) { // 白名单检查
return true; // 允许访问
}
检查传入的 $page 是否在白名单当中,是则返回true
$_page = mb_substr( // 去除参数
$page,
0,
mb_strpos($page . '?', '?') // 找到问号位置
);
1. 核心函数解析
-
mb_strpos($str, $needle):多字节安全的字符串查找函数,返回$needle(目标字符)在$str(字符串)中首次出现的位置(索引值,从 0 开始)。若未找到,返回false。
-
mb_substr($str, $start, $length):多字节安全的子字符串截取函数,从$str的$start位置(默认 0)开始,截取长度为$length的子字符串。
2. 代码执行逻辑
$page . '?'表示在 $page 的末尾拼接一个问号?。
这样做的目的是:即使原始$page中没有?,也能确保拼接后的字符串一定包含?,避免mb_strpos返回false(确保后续截取长度有效)。
mb_strpos($page . '?', '?')查找拼接后字符串中第一个?出现的位置
(如$page = "hint.php"),则返回拼接后 ? 的位置(即$page的长度,例如"hint.php"长度为 8,拼接后?在索引 8 的位置)
mb_substr($page, 0, 上述位置)从 $page 的开头(0 位置)开始,截取到第一个?出现的位置(不包含 ? 本身),结果赋值给 $_page
这段代码通常用于截取文件名,去除可能包含的查询参数(?及之后的内容),只保留基础文件名。结合前面的白名单检查逻辑,这样可以避免因$page带有参数(如source.php?x=1)而导致白名单验证失败,确保只验证文件名本身是否合法
后面就是在检查一次在不在白名单里,继续重复文件名截取再检查白名单,只不过多了一个url解码,影响不大
$_page = urldecode($page);
接着解释最后一段
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\" />"; // 输出滑稽脸
}
! empty($_REQUEST['file']):检查请求中是否存在名为file的参数,且该参数不为空。
is_string($_REQUEST['file']):确保file参数的值是字符串类型。这是基础的类型校验,防止非字符串类型的输入(如数组)导致后续处理异常
emmm::checkFile($_REQUEST['file']):调用emmm类的静态方法checkFile,对file参数的值进行进一步安全验证。只有checkFile返回true(验证通过),这个条件才成立。
include $_REQUEST['file']:包含并执行file参数指定的文件。include是 PHP 的文件包含语句,会将目标文件的内容当作 PHP 代码执行。
构造payload
理解完上述代码后,可以明确得出这是一个文件包含漏洞,文件包含分别有本地包含和远程包含本地文件包含漏洞(LFI):指包含本地服务器中的文件
?file=../../../xxxx
远程文件包含漏洞(RFI):指包含远程服务器中的文件
?file=http://xxxx.xxx/xxxx
因为我们目前还不知道要包含的文件是不是直接叫flag,所以我们先尝试包含一下hint.php文件有没有多余提示,回显flag not here, and flag in ffffllllaaaagggg,很好直接得到flag所在的文件名
source.php?file=hint.php
接下来只需要绕过主要的三个判断就可以完美的构造payload了
1.判断我们传入的file值是否为空且是否为字符串
2.白名单判断传入的值是否在白名单 (source.php hint.php) 当中
3.将我们传入的值截取后再次进行白名单检测
得知我们传入的值必须在白名单当中,且需要绕过文件名截取,只需将file值的前面主动拼接一个souce.php?或者hint.php?,截取后的值依旧是source.php或hint.php,此时checkFile返回true就可以绕过检测
因为我这边是直接知道flag在根目录,通常flag也会放在根目录,linux系统的网页路径又一般为
/var/www/html/文件名称,所以直接包含根目录下的文件,如果不清楚的情况下可以一个个去包含尝试
得出payload为
file=hint.php?/../../../../ffffllllaaaagggg
完整payload,以下随便一个都可以
source.php?file=source.php?/../../../../ffffllllaaaagggg
source.php?file=hint.php?/../../../../ffffllllaaaagggg

最终获取flagflag{467d5f45-294e-46b7-a15a-d021e0bceced}

浙公网安备 33010602011771号