知识点:
目录穿越_文件包含
static静态方法
参数传递引用
mb_strpos函数 mb_substr函数
正文:
页面有一张 滑稽 的表情包,查看一下页面源代码,发现提示
那就访问/source.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\" />";
}
?>
可以看到有一个hint.php 访问一下
提示flag not here, and flag in ffffllllaaaagggg 得到了flag文件名
代码审计:
前面都是类中的代码,先看最后的代码
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 可以接收 GET POST COOKIE 传入的参数
需要存在file参数是字符串类型,并且经过emmm类中的checkFile方法返回true,才能执行include函数进行文件包含
这里没有创建emmm类的实例就可以引用其中的方法,是因为类中static表示是一个静态方法,可以通过类名直接调用,而不需要创建类的实例
目标明确之后开始看emmm类中的代码,类中也只有一个checkFile 方法,所以很简单,顺着往下看
给出了白名单,是source.php 和 hint.php 然后对参数page设置了条件
参数page就是传入的file数据,这里用了&$page,表示可以直接修改传入的变量,而不需要返回值来更新该变量
最终的目的是include $_REQUEST['file'] 包含其中有flag的文件,也就是ffffllllaaaagggg
目标就是让参数file也就是类中参数page包含ffffllllaaaagggg的同时满足checkFile方法返回true
首先page参数需要存在并且是字符串类型,然后if(in_array($page, $whitelist)) 这个判断是无法满足的,因为得有flag文件名,但是没影响,只要不返回false就行,接着往下看
php
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
php
mb_strpos函数: 查找字符串在另一个字符串中首次出现的位置
mb_strpos ($haystack , $needle , $offset )
$haystack:被搜索的字符串
$needle: 要查找的字符串
$offset: 可选参数,指定从哪个字符开始搜索
//如果为正数,则从字符串的开头开始计算;如果是负数,则从字符串的末尾开始计算
//没有的话默认是0,即从开头开始搜索
mb_substr函数: 从一个字符串中提取子字符串
mb_substr ($str , $start , $length )
//substr() 函数只针对英文字符,而mb_substr()对于中文也适用
$str: 原始字符串
$start: 起始位置,如果为正数,则从字符串的开头开始计算;如果是负数,则从字符串的末尾开始计算
$length: 可选参数,表示要提取的子字符串的长度,如果没赋值,则提取从开始到字符串结束的所有字符
'.' 是 PHP 中的字符串连接运算符,它用于将两个字符串连接在一起,形成一个更长的字符串
在这里,它将 $page 变量的值和一个问号字符 '?' 连接在一起,形成一个新的字符串,在这个新的字符串中查找问号是否存在,那么很明显能找到
首先使用mb_strpos函数找到page中第一个问号的位置,然后使用mb_substr函数将问号之前的部分赋值作为_page进行处理
经过mb_strpos和mb_substr函数得到的$_page如果在白名单中,返回true
后面的代码对page 进行url解密之后赋值给_page,再次进行mb_substr函数得到新的$_page,再次判断是否在白名单中,不过如果传入的是正常的字符串,url解码之后还是本身,就没意义了
如果这一步进行完$page还不在白名单中,就会返回false
结合起来发现,只需要传入一个在白名单内的文件名(source.php或者hint.php)并在后面加上问号?
就可以保证在第二次if(in_array($_page, $whitelist))匹配查找的内容在白名单,返回true,退出checkfile方法,后面的urldecode代码都是用不到的
不在文件名后面加?问号的话代码自动加的?会添加在参数末尾,这样截取的内容就不满足白名单
关于include函数
这样的话,参数的格式就是source.php? + 文件名
include()函数可以传入文件的绝对路径,也可以是相对路径
而参数是作为include()函数的参数执行文件包含的,绝对路径我们不知道,只能用相对路径
如果直接传入source.php?ffffllllaaaagggg,那么肯定会报错,因为没有这么一个文件名
实践出真知, 使用 include($_GET['file']); 这个简单的代码来简单说明一下
下图中的phpinfo.php是代码文件目录下面的一个php文件,abc是我随便输入的,没有这个目录,如果正常输入abc/phpinfo.php 是不行的,即使这个目录是存在的,也是会报错,因为格式不对
但是只要用../ 退回一级,就可以正常包含phpinfo.php
但是如果把abc 换成 abc? ,只要出现了? 那么就会报错
因为在windows文件名不能包含?
但是这里题目的环境是debian 不是windows系统 所以可以
那么这里搞清楚了,剩下的就是查找flag文件的具体路径了
从source.php?/ffffllllaaaagggg 开始,逐级用../进行目录穿越 经过四次目录穿越发现成功执行了命令,得到了flag
其实发现文件名也提示了我们要使用四层目录,文件名是4个f l a g
构造
php
http://node4.anna.nssctf.cn:28698/source.php?file=source.php?/../../../../ffffllllaaaagggg
即可得到flag