注:由于打开靶机的网址都会不一样,所以以下的url地址会不一样,但并不影响解题,可忽略。
做题过程
打开靶机
1、Ctrl+U查看源代码,发现有被注释掉的内容
我们访问一下这个被注释掉的文件,看看里面有什么
2、返回第一页的笑脸网页,在原来的url后面补充source.php
http://e79d31b6-19d7-40a3-a94a-4d8c8cf4a982.node4.buuoj.cn:81/source.php
这里如果我们在访问上一个源代码页面后再访问source.php后会出现下面这个界面
view-source:http://754e1f9d-22f3-4c64-8c4f-6d57f7655bfc.node5.buuoj.cn:81/source.php
[前面多了个view-source:(view-source:可查看当前页面源码)]
此时我们要查看一下这个页面内容,就把view-source:给删掉,然后就会出现下面这个页面。
我们进行代码审计
<?php
//显示当前文件的源代码
highlight_file(__FILE__);
//定义一个叫emmm的类
class emmm
{
//定义函数checkFile,checkFile通过传入的参数来检测文件是否在白名单中
public static function checkFile(&$page)
{
//白名单列表,包含可访问的文件名和对应路径
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
//isset()判断变量$page是否已设置并且不为空
//is_string()检测变量是否是字符串,如果指定变量为字符串,则返回 TRUE,否则返回 FALSE
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
//in_array() 用于检查数组中是否包含指定的值
//检测$page是否在白名单中
if (in_array($page, $whitelist)) {
return true;
}
//参数处理(解释在下面)
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
//第二次检测$page是否匹配白名单列表$whitelist
if (in_array($_page, $whitelist)) {
//在白名单中,返回 true 表示可以访问文件
return true;
}
//urldecode()解码URL字符串函数
//对 $page 进行 URL 解码
$_page = urldecode($page);
//同上
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
//第三次检测$page是否在白名单中
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
//若无法访问文件则返回false
return false;
}
}
//1、empty()检查参数是否存在且非空
//2、is_string()判断参数的值是否是一个字符串
//3、emmm::checkFile()调用了emmm类的checkFile函数,检测参数是否在白名单中存在
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\" />";
}
?>
checkFile函数进行了三次白名单检查(只要有一次为true就行),两次参数处理。
参数处理部分的解释:
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
mb_substr()返回字符串的一部分
mb_substr( $str, $start, $length, $encoding )
$str,需要截断的字符串(必选)
$start,截断开始处,起始处为0(必选)
$length,要截取的字数(可选)
$encoding,网页编码,如utf-8,GB2312,GBK(可选)
这段代码的目的是从字符串 $_page 中提取出第一个问号 ?
之前的部分。
- $page 是一个包含某个 URL 中的参数部分的字符串。假设我们传入
?file=source.php
mb_strpos($page . '?', '?')
$page . '?'
会在$page
字符串的末尾附加一个问号,得到一个新的字符串source.php?
。(为什么要加一个额外的问号呢?是为了确保即使在 $page 中没有问号时,也能找到一个位置返回。)- 然后,
mb_strpos()
函数用于在这个新的字符串中查找第一个问号的位置。
- 找到了第一个问号在新字符串中的位置后,
mb_substr()
函数就会从原始字符串$page
中提取出从位置0
到这个位置(不包括这个位置)的子字符串。- 这个子字符串就是原始字符串中第一个问号 之前的部分,也就是
$page
中的参数部分(不包括问号本身)。
因此,$_page
变量将包含$page
字符串中第一个问号之前的内容。
$_page
的值将是source.php
。
- 这个子字符串就是原始字符串中第一个问号 之前的部分,也就是
hint.php是白名单中的文件,我们也访问一下
我们整理一下已有线索:
(线索1):hint.php文件告诉我们flag在ffffllllaaaagggg中。我们猜测ffffllllaaaagggg是一个文件名,但是ffffllllaaaagggg并不在白名单中,所以我们要想办法绕过白名单访问到ffffllllaaaagggg。
上面的php代码告诉了我们:
(线索2):我们想要访问ffffllllaaaagggg,就必须让最后一段的if语句的三条检测都为true。
(线索3)checkFile函数会检查用户传入的参数($page
)是否匹配白名单列表$whitelist。所以我们在构造这类查询语句时要包含白名单中的文件(source.php或hint.php)。并且checkFile函数是通过传入的参数 $page 来进行的文件检查,而不是直接从 URL 中获取参数。所以我们需要以参数形式传递文件名【?file=source.php】,将参数传给file,而不是直接访问文件路径【/source.php】。
- 这里的参数名为什么是file?因为在最后的if语句中,if (! empty($_REQUEST[‘file’])…中的 ‘file’ 就是参数名。所以此处我们的参数名也只能是file。
(线索4):代码会截取第一个问号前的字符串并检查其是否在白名单中。
那我们让第一个问号?
前的内容在白名单中,在问号?
后面加上我们想访问的ffffllllaaaagggg。这样不就可以让ffffllllaaaagggg绕过白名单检测了嘛
我们在?file=source.php
后加上?
,然后加上ffffllllaaaagggg。即?file=source.php?ffffllllaaaagggg
根据已有线索,构造payload(下面source换成hint也行):
?file=source.php?ffffllllaaaagggg
没查到,可能文件不在当前目录中。
我们继续一层一层往上查,每次多添加一层../
直到查到flag(../
代表上一层目录)
(只写了后边部分)
?file=source.php?../../../../../ffffllllaaaagggg
为啥是往上查?
原因
[因为我们当前的source.php一般是在html目录下,往上是www,var,然后到根目录,flag一般就放在根目录下面,这里还有一个hint.php?/或者source.php?/,因此需要返回四层才能到根目录。]