[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
该类会先进行白名单检查,然后截取文件名并重复操作,综上所述我们传入的参数只要在白名单 (source.php hint.php) 当中,并且加上 ? 就可以绕过多次的白名单检测,由于是在source.php目录下操作的,所以只需在当下目录传入file=source.php既可暂时还不知道flag在什么位置先包含hint.php文件看看有没有多余线索
source.php?file=hint.php
回显flag not here, and flag in ffffllllaaaagggg
这说明flag在ffffllllaaaagggg里面
因为我这边是直接知道flag在根目录,而且flag通常也会放在根目录,linux系统的网页路径又一般在
/var/html/www/文件名称
,所以直接包含根目录下的文件,如果不清楚的情况下只能一个一个去包含尝试
得出payload为
file=hint.php?/../../../../ffffllllaaaagggg
完整payload,以下随便一个都可以
source.php?file=source.php?/../../../../ffffllllaaaagggg
source.php?file=hint.php?/../../../../ffffllllaaaagggg
最终获取flagflag{467d5f45-294e-46b7-a15a-d021e0bceced}