[鹤城杯 2021]EasyP wp
参考博客:
源码分析
首先进入题目,看到代码:
<?php
include 'utils.php';
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if ($guess === $secret) {
$message = 'Congratulations! The flag is: ' . $flag;
} else {
$message = 'Wrong. Try Again';
}
}
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}
if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
?>
其中第一段代码是让我们传入一个 GET 参数 guess 。这个参数值先会经过转义然后跟 $secret 强比较。我没有办法绕过,也猜不到 $secret 的值是多少,所以这段代码的作用相当于没有。
第二段代码对 $_SERVER['PHP_SELF']
进行了正则匹配:
if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("hacker :)");
}
$_SERVER['PHP_SELF']
$_SERVER
是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。
PHP_SELF
获取当前执行脚本的文件名,与 document root 有关。
例如,在地址为 http://example.com/foo/bar.php 的脚本中使用 $_SERVER['PHP_SELF']
将得到 /foo/bar.php。FILE 常量包含当前(例如包含)文件的完整路径和文件名。 如果 PHP 以命令行模式运行,这个变量将包含脚本名。
正则解读
/utils\.php\/*$/i
/utils\.php\/*$/i
: 正则表达式的开始和结束都有斜杠(‘/’)表示正则表达式的开始和结束。utils\.php
: 该部分用于匹配字符串 ‘utils.php’。‘.’ 表示匹配点字符,点字符在正则表达式中具有特殊意义,因此需要转义。\/*$
: 该部分用于匹配可选的斜杠(‘/’)。‘/*’ 表示匹配零个或多个斜杠,‘$’ 表示匹配字符串的结尾。i
: 该部分表示在匹配时忽略大小写。
综上所述:就是匹配以 utils.php+0个或多个 / 结尾
的字符串。
第三段代码对 $_SERVER['REQUEST_URI']
做了一个正则匹配:
if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
exit("hacker :)");
}
REQUEST_URI
URI 用来指定要访问的页面。例如访问地址:http://www.baidu.com/index.html?a=1&b=1 。
那么 $_SERVER['REQUEST_URI']
获取到的值就是 /index.html?a=1&b=1
此外 $_SERVER['REQUEST_URI']
在获取 URL 编码字符时不会进行 URL 解码。利用这一点可以进行绕过。
这里可以参考博客:
PHP中$_SERVER[“QUERY_STRING”]函数
正则解读
这段正则就是输入的 URL 链接中不能有 “show_source” 字符串。、
最后一段代码:
if (isset($_GET['show_source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}else{
show_source(__FILE__);
}
获取一个 GET 参数 show_source ,将 $_SERVER['PHP_SELF']
的值经过 basename 函数处理后显示出来。
basename() 函数
basename() 函数返回路径中的文件名部分。
语法
basename(path,suffix)
参数 | 描述 |
---|---|
path | 必需。规定要检查的路径。 |
suffix | 可选。规定文件扩展名。如果文件有 suffix,则不会输出这个扩展名。 |
举例
<?php
$path = "/testweb/home.php";
//显示带有文件扩展名的文件名
echo basename($path);
//显示不带有文件扩展名的文件名
echo basename($path,".php");
?>
输出:
home.php
home
basename() 函数绕过
basename 函数有这样一个特性:在使用默认语言环境设置时,basename() 会删除文件名开头的非 ASCII 字符。
比如:
$_SERVER['PHP_SELF'] 获取到的值 | basename() 函数处理后的结果 |
---|---|
/dir/index.php | index.php |
/dir/%ffindex.php | index.php |
/dir/index.php/%ff | index.php |
/dir/index.php/%2b | + |
上面的 %ff 就是一个非 ASCII 字符。而当路径的最后为 ASCII 字符时,basename 函数就会返回该字符。
具体可以去看:basename()绕过小结
绕过
经过上面的分析,GET 传入的参数 show_source 会被匹配到,因此对 “show_source” 做一个 URL 编码后再传入。
此外,在页面请求时传入 /index.php/utils.php/大
:
服务器会认为请求的页面是 index.php ,因此可以正常返回页面;
$_SERVER['PHP_SELF']
获取到的值是 /index.php/utils.php/大
,因为不是以 utils.php/ 结尾,所以可以绕过正则;
因为中文是非 ASCII 字符,所以经过 basename 函数处理后得到的结果就是 utils.php ;
这样就可以读到 utils.php 文件的内容。
测试发现:直接传入 /utils.php/大
的话没有回显。
payload
http://node4.anna.nssctf.cn:28806/index.php/utils.php/大?%73%68%6f%77%5f%73%6f%75%72%63%65=1
返回结果:
拿到 flag 。