小白全流程解读代码和自己的思路,欢迎大家一起学习和批评指正。
一、开局展示
开局一张图,作为一星的web题,我感觉应该不会考图片隐写,这偏离了web题的初衷了。先看看源代码、
二、源代码展示
源代码中没有直接写flag,看到中间body'中有标注source.php,想到这个php文件应该是破题的关键,也没有什么链接可以直接跳转到这个php文件,考虑是不是url里的子路径。添加试了一下,果然能访问,看一看这个代码情况。
三、代码解读
<?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中截止第一个问号之间的内容
$_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\" />";
}
?>
1.直接把核心代码贴出来,先把代码分成两块来读。上面一块是定义了一个emmm的类,下面从 if (! empty($_REQUEST['file'])是对前段接收的file的值做了个判断,然后把值传入的emmm类中的checkFile方法中。
2.先解读下面的代码,直接以注释的形式解读。
if (! empty($_REQUEST['file']) //对传入的file的值做了个判断,是否为空
&& is_string($_REQUEST['file']) //并且是否为字符串
&& emmm::checkFile($_REQUEST['file']) //传入类中的checkFile方法,看返回值是否为真
) {
include $_REQUEST['file']; //上述返回都为真后,执行include方法,考虑是文件的任意读取
exit;
} else { //为假则是输出刚才的笑脸的图
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
由此总结,首先我们需要一个file参数,?file=,传入的值不能为空,得为字符串,然后看看checkFile函数是怎么判断的。
3.解读emmm类的代码
class emmm
{
public static function checkFile(&$page) //file传入由page接受
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"]; //定义了一个白名单数组
if (! isset($page) || !is_string($page)) { //做判断,page是否为空,是否为字符串
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;//判断传入的page是否是在白名单内,是的直接返回ture,这里我们做了个测试,?file=source.php,直接返回了source.php的代码file=hint.php,则跳出flag not here, and flag in ffffllllaaaagggg,证明flag在ffffllllaaaagggg这个文件里,我要读取的就是这个文件。
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);//这是做了个file传入的值做了分割,从初始位开始,mb_strpos($page . '?', '?')中,$page . '?'是直接在fule传入的值末尾加个?,字符串的拼接,然后方法的返回值是$page中初始位到第一个出现?的地方,有几位。因为末尾有个?,所以这个函数返回有两种结果,比如file传入的值是abc.php,没有问号,拼接后就是abc.php?,返回的分割后的值就是abc.php,如果file传入的值是abc.php?source=flag,这个URL完整的应该是XXXXX?file=abc.php?source=flag,那么file的值中已经有问好了,他就会从file的初始位截去到第一个问号,也就是abc.php,赋值给$_page
if (in_array($_page, $whitelist)) {
return true;
} //再次判断$page是否在白名单内
$_page = urldecode($page); 对page进行url解码
);
$_page = mb_substr( //重复刚才的操作
$_page,
0,
mb_strpos($_page . '?', '?')
if (in_array($_page, $whitelist)) { //在进行白名单校验
return true;
}
echo "you can't see it";
return false;
}
}
四、poc尝试
首先我们需要一个值是fule,及xxxx?file=,一直file的值中到一个问号之间必须为白名单的值,所以file后至少是?file=source.php或?file=hint.php,那我们的poc就是XXXX?file=source.php。一直flag在ffffllllaaaagggg文件,一般都是向上级补录遍历该文件,也就是../../../../ffffllllaaaagggg,那么把这个加进URL呢,结合方法中对第一个问号的截断判断,poc可以写成file=hint.php?../../../../../../../../ffffllllaaaagggg,那么截断下来的_page就是hint.php,可以绕过白名单,而后传入include方法的路径就是hint.php?../../../../../../../../ffffllllaaaagggg,由于在url中在没有被&分割的情况下,会从第一个?开始解读一系列的参数名和参数值,因此在URL中添加了第二个问号及其后面的内容时,服务器会将这部分内容作为查询字符串的一部分进行处理,而不是作为路径的一部分。所以,它不会影响路径的读取或目录遍历攻击。这样就获得了flag。