XCTF-warmup详细题解(含思考过程)
一、题目来源
XCTF-Web-warmup
二、信息搜集
进入场景后,映入眼帘的是一张滑稽图

先ctrl+u
看一下源码,查看是否存在“敏感信息泄露”的现象
果然存在,访问一下该文件:
http://ip:端口/source.php
可以看到出现了一段后端代码(用php
写的)
<?phphighlight_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\" />";}
?>
三、代码审计
既然有了源码信息,我们就可以对代码进行分析
我们先不管上面的类,看到代码的主体部分,即:
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\" />";
}
一眼就可以看到关键函数include
,并且里面的参数是用户可控的(通过REQUEST传值)
因此,存在文件包含漏洞
但是要够着这个函数,需要满足三个判断条件,分别是
- file参数不为空
- file接受的参数需要是字符串
emmm
类的checkFile
方法其返回值需要为1
如果不满足,就会显示滑稽图,这也就是为什么我们一开始进入会出现滑稽的根本原因(没有传值给file)
好,现在我们就可以回过头去看emmm
类了
该类中有且仅有一个方法,就是checkFile()
,且该方法的参数值就是我们通过REQUEST传值给file的值
它首先列出来白名单列表:
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
到这其实就可以大致猜测出来该方法的用途,就是检查我们输入的文件名是否合法
那么,它列举出来的官方文件有且仅有:
source.php
hint.php
第一个文件我们看过了,直接看第二个文件
http://ip:端口/hint.php
或者直接:
http://ip:端口/?file=hint.php
可以看到一段提示:
flag not here, and flag in ffffllllaaaagggg
说明我们最终要找的flag存储在ffffllllaaaagggg
文件中
大家不要因“无后缀”的问题而感到疑惑,在Linux操作系统中是不根据文件后缀名来判断文件类型的
因此,没有后缀名是常有的事情
但是,很明显,该文件是不属于白名单里面的
因此,我们继续分析后面的代码
if (! isset($page) || !is_string($page)) {echo "you can't see it";return false;
}
他会检测我们是否输入信息,如果没有或者不为字符串,就会直接退出且返回false
这点只要我们正常输入文件就不会符合该判断,接着看后面
if (in_array($page, $whitelist)) {return true;
}
会判断我们的输入信息是否在白名单内,如果在就返回true,否则继续
按照正常的逻辑来说,此次判断结束后会直接跟上一个:
return false;
直接发挥白名单真正的作用,但是它并没有这么做
而是会再经过几个判断(且该判断又返回true的希望)最后再到达false逻辑

这就变相给了攻击者很多的机会
假设,我们现在输入就是ffffllllaaaagggg
,当前这个判断是不满足的,会继续后面的逻辑,还有机会,我们接着看
在下一个判断之前,会多一个步骤:
$_page = mb_substr($page,0,mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {return true;
}
首先,我们来了解一下mb_substr
和mb_strpos
两个函数
mb_strpos(string $haystack,string $needle,int $offset = 0,?string $encoding = null
): int|false
该函数会查找$needle
在$haystack
中第一次出现的位置(下标位置)
比如mb_strpos("abc","c");
的返回值就是2
mb_substr($str,$start,$length);
该函数会输出从$str
的$start
下标开始的长度为$length
的字符串
比如:
<?php
echo mb_substr("abc", 0, 2);
// 输出ab
?>
我们将我们的假想输入“ffffllllaaaagggg
”带进去看一看
//根据输入改写代码成:
$_page = mb_substr("ffffllllaaaagggg",0,mb_strpos('ffffllllaaaagggg?', '?')
);
最后的输出就是:ffffllllaaaagggg
很明显,经过转换后还是不能满足下述判断:
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;
}
与上一个判断不同的是,这里多出来了一个urldecode
其他的逻辑基本不变,无非是那两个函数的对象变成了"$_page"而不是原本的"$_page"
很明显,如果我们不改变我们的假想输入,那么结果还是一样
四、Payload构造
知道了检查文件的逻辑,我们就可以开始构造payload了
非常明确的几个细节就是:
- payload中得有关键文件
ffffllllaaaagggg
- 得拿“?”做文章
我们观察到,每次使用函数mb_strpos
它都会手动在末尾帮我们拼接(“.”)上一个“?”
换言之,只要我们输入的内容本身就携带"?"就会截断该问号前面的内容,保留后面的内容
而根据我们之前的分析,他们的判断都是针对"?"前面的内容进行判断的,但是include()
的参数却是整体的内容
那么我们的payload可不可以是这样的形式:
payload = "白名单内容?xxxxx"
我们只要让其满足:
- 前面用于白名单判断
- 整体作为一个正确的文件路径,且能导向到文件
ffffllllaaaagggg
因此,我们的payload由此而生:
payload = "source.php?/../../../../ffffllllaaaagggg"
这样的payload,虽然绕不过第一个判断,但是第二、第三个判断都是可以符合的
因为,根据判断逻辑,他只会截取第一个问号前面的数据,即source.php
因而,函数返回为true,使得该payload成功进入文件包含逻辑
而"source.php?
"这个整体会被当成一个文件名作为文件路径的一部分
这里可能会有疑问
当我们输入:
http://ip:端口/?file=source.php?/../../../../ffffllllaaaagggg
的时候
浏览器会自动帮我们进行URL编码,但是在此处,编码后的结果还是其本身
在后端被file接受的时候,file接收到的还是“source.php?/../../../../ffffllllaaaagggg
”
这里大家也可以手搓测试代码验证一下(测试代码如下)<?php$file = $_GET['file'];echo $file; ?>
结果:
为什么“?”也被作为我们输入内容的一部分呢?
这就是因为“?”在URL中的作用是:分隔路径与查询参数
因此这里的"?“很明显会被误以为source.php
还有传输参数的必要,因此作为正常语法逻辑被传输进来了
但是后续我们通过在”?"后加上一个“/”把“source.php?
”完整地作为了一个文件名而存在了
后面的“…/”就是回到上级目录,多个“…/”就可以让目录直接返回到根目录的位置,实现目录穿越的目的
因此我们只要构造URL为:
http://ip:端口/?file=source.php?/../../../../ffffllllaaaagggg
即可成功得到flag!