题目
做法
启动靶机,点进去
没啥信息,点开F12康康源码
发现有被注释的
在URL后面加进去被注释的东西,访问一下
分析一波
1. 代码起始和高亮显示
php
<?php
highlight_file(__FILE__);
<?php
:PHP 代码的起始标记。highlight_file(__FILE__)
:highlight_file()
是 PHP 内置函数,用于高亮显示文件的源代码(以 HTML 格式展示)。__FILE__
是 PHP 的魔术常量,表示当前文件的完整路径。- 这行代码会把整个 PHP 文件的源代码显示在浏览器中(通常用于调试)。
2. 定义类和检查函数
php
class emmm
{
public static function checkFile(&$page)
{
class emmm
:定义一个名为emmm
的类(类似 Java/C++ 中的类)。public static function checkFile(&$page)
:public
:函数可以被外部访问。static
:函数属于类本身,无需创建对象即可调用(如emmm::checkFile()
)。- 静态方法
checkFile
用于检查文件 &$page
:引用传递参数,直接修改原始变量的值(这里未实际修改)。
3. 白名单设置
php
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
- 创建一个关联数组(类似 Python 的字典),白名单中只允许两个文件:
source.php
hint.php
4. 检查参数是否合法
php
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
isset($page)
:检查变量$page
是否存在且不为NULL
。is_string($page)
:检查变量$page
是否为字符串类型。- 如果
$page
不存在或不是字符串,输出错误信息并终止检查。
5. 第一次白名单检查
php
if (in_array($page, $whitelist)) {
return true;
}
in_array($needle, $haystack)
:检查$needle
是否在$haystack
数组中。- 如果
$page
直接等于source.php
或hint.php
,允许访问。
6. 截取问号前的内容再检查
php
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
mb_strpos($page . '?', '?')
:mb_strpos()
查找字符串中第一次出现?
的位置。$page . '?'
在字符串末尾添加?
,防止找不到?
时返回NULL
。
mb_substr($page, 0, ...)
:截取$page
从开头到?
之前的部分。- 例如:
$page = "source.php?xxx"
→$_page = "source.php"
。 - 如果截取后的内容在白名单中,允许访问。
7. URL 解码后再次检查
php
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
urldecode($page)
:对 URL 编码的字符串进行解码(例如%20
→ 空格)。- 再次截取解码后的字符串中
?
之前的部分,检查是否在白名单中。 - 例如:
$page = "source.php%3Fxxx"
(%3F
是?
的 URL 编码)→ 解码后$_page = "source.php"
。
8. 检查失败处理
php
echo "you can't see it";
return false;
}
}
- 如果所有检查都不通过,输出错误信息并返回
false
。
9. 主程序:接收用户输入并包含文件
php
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\" />";
}
?>
$_REQUEST['file']
:获取用户通过 URL 参数(如?file=xxx
)或 POST 表单提交的file
参数。empty()
:检查变量是否为空(值为NULL
、''
、0
等)。- is_string( ):检查变量是否为字符串类型。
emmm::checkFile($_REQUEST['file'])
:调用之前定义的检查函数。include $_REQUEST['file']
:include
是 PHP 的文件包含函数,将指定文件的内容嵌入到当前脚本中执行
即先暂停主脚本的运行,先运行子脚本
(1)若内容是php的话,直接运行此php文件,完成后再跳出来继续运行主脚本(假设include里的脚本为子脚本)
(2)若里面含有echo等输出函数、被引入文件包含 HTML 或非 PHP 内容(eg.纯文本内容)、return、被引入文件包含错误或警告信息等,则会直接读取并输出相应内容- 风险:如果检查函数存在漏洞,攻击者可能通过构造特殊的
file
参数读取任意文件。
exit
:终止脚本执行,防止后续代码被执行。- 如果条件不满足,显示一张默认图片。
综上,我们为了绕过上面的保护,只需要在URL后写入上面提到hint.php(自行尝试,source.php那个我试了不行,没有提示我们要的flag在哪)
得出,我们要的flag就在ffffllllaaaagggg这里
至此,我们编写payload
source.php?file=hint.php?/../../../../ffffllllaaaagggg
source.php?file=source.php?/../../../../ffffllllaaaagggg
解释:
1、为什么php后要加“ ?”呢:
(1)为了应付上面的检查,不然它就会在ffffllllaaaagggg后面加一个?我们就无法通过检测
(2)为了应付include函数,如下
2、include函数解释(以下通过上面给的第一个payload加以解释)
第一步:绕过白名单
传进 file=
的是:
hint.php?/../../../../ffffllllaaaagggg
PHP 会提取 ?
前的部分(hint.php
),发现它在白名单中,就放行了。
第二步:PHP include 怎么处理这个路径?
include('hint.php?/../../../../ffffllllaaaagggg');
这是 PHP 的语法。
PHP 遇到
include("xxx?yyy")
的时候,只会 includexxx
这个文件,不会理会?yyy
。
所以,PHP 实际执行的是:
include("hint.php");
到这里你是读取 hint.php 并执行它了!
第三步:hint.php 的作用
虽然你没看到 hint.php 的源码,但你可以合理推测它会这样写:
<include($_SERVER['QUERY_STRING']);// 它把 ? 后面的东西当成路径去 include>
你不知道你要找的flag在哪个文件里,一定会在这俩白名单文件里吗?谁也说不准,最简单的方法就是回到根目录进行查找了
你传的这个:
source.php?file=hint.php?/../../../../ffffllllaaaagggg
会让 hint.php 里的 $_SERVER['QUERY_STRING']
变成:
/../../../../ffffllllaaaagggg
于是 hint.php 执行了:
include("/../../../../ffffllllaaaagggg"); // 最终解析为 /ffffllllaaaagggg
第二次 include()
是在 hint.php 中执行的,相对路径会从 hint.php 所在的目录 /var/www/html
开始解析,层层 ../
向上,最终到达根目录 /
,再拼上目标文件名,形成有效路径。
详细一点:
这里的include是这样工作的——假如hint.php的绝对路径是/var/www/html/hint.php,然后…/就会变成/var/www/html,再…/就会继续返回上一级/var/www,以此类推,最终返回根目录,即是/ffffllllaaaagggg了,就像你直接在你的c盘找一个文件一样,你不知道它放在哪,因此直接返回根目录一搜,即可搜到,因此,其实上面的payload有多少个…/都没什么问题,但是一定不能少,根目录再上一级还是停留在根目录上,不会再改变
那还有一个问题,就是payload里的…/最前面的/还有必要加吗?
我这边尝试了一下,发现去掉这个/还是可以得出flag,大概是无所谓的
但这里建议还是带上为好,因为带 /
更安全更规范,防止被解析成查询参数而不是路径字符串,从而导致我们得不到flag而去怀疑自己思路,导致更进一步的错误
如果没有那个 /
会怎样?(URL上需要加/)
如果你传:
hint.php?../../../../ffffllllaaaagggg
PHP 会以为你传的是个 GET 参数名叫 ../../../../ffffllllaaaagggg
,值为空:
在不同 PHP/服务器组合中,有的解析器只有以 /
开头才会处理为路径
因此,这里强烈建议加上/,这里指的是URL上的
类型 | 意义 |
---|---|
路径 | 控制服务器访问哪个资源 |
参数 | 把值传给正在运行的脚本 |
要是识别成参数,就变成是写进去东西了,而不是访问/找某个文件,跟我们的目的相悖了
但在include函数上呢,则恰恰相反了,是不建议加/的
它截取的是payload的?后面的,刚好把/截取到
一般来说,有/的是绝对路径,没有/的是相对路径
假设你的文件在这里:
/var/www/html/hint.php
你想 include:
/flag
那你可以这样:
include("../../../../flag"); // 从 hint.php 的目录出发 // 即:/var/www/html/../../../../flag = /flag
你也可以:
include("/flag"); // 绝对路径
都没问题。
但是你如果写:(可以,但没必要)
include("/../../../../flag"); // 从根目录开始,还向上跳❓
PHP 实际等价于:
/../../../../flag => /flag
虽然结果一样,但这写法:
- 没有逻辑意义:根目录不能再往上跳
- 容易引起误解或报错
- 在不同系统/配置上行为不一致(特别是 open_basedir 或安全策略存在时)
那还有一个问题,就是既然上面说的
source.php?file=hint.php?/../../../../ffffllllaaaagggg
和
source.php?file=hint.php?../../../../ffffllllaaaagggg
都可以得出flag,那我直接
source.php?file=hint.php?/ffffllllaaaagggg
行不行呢,上面说了嘛,既然直接从根目录出发这样写都可以,那我何必还要写这么多…/呢
直接/,一步到位,岂不美哉?
但答案是不行的,至少在该题中找不到flag
❌ include("/flag")
:
大概是路径解析过程中触发了安全限制/被限制了,因此行不通
✅ include("/../../flag")
:
一般是失败的,跟上面一样
(/…/…/flag其实跟/flag一样的,"/“继续往上还是”/"因此后面的…/其实没什么作用)
但如果路径被错误解析,则可能绕过限制(就像最前面没加/的情况一般)
因此,这种情况行不通的情况下,我们就得去掉最前面的/了
一旦路径以 /
开头,就告诉 PHP:
“我给的是绝对路径,不用管当前文件在哪。”
some/path/to/file
→ 相对路径(从当前工作目录出发)/some/path/to/file
→ 绝对路径(从根/
出发)
没办法了,找了半天,最合理的解释也只能是这样了【单纯指的include(“/flag”)和include(“/…/…/flag”)】,平时做题/比赛的时候不行就按照上面的思路一个一个慢慢试吧
得出flag
复制,回去提交
更新
于2025.06.16