我来到博客园的第一篇记录
题目复现链接:https://www.nssctf.cn/problem/463
题目源码:
<?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__);
}
?>
审计代码第一眼可以看到只需要POST传参guess
,使其值为secret
即可获取到flag
但四处寻找无果,只能转换思路,尝试从utils.php
文件中入手
代码中存在highlight_file
函数,可以读取文件,这里便是题目的突破点
知识点
$_SERVER超全局变量
查阅官方文档,$_SERVER
是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等信息的 array。这个数组中的条目由 Web 服务器创建,所以不能保证每个 Web 服务器都提供全部条目;服务器可能会忽略一些,或者提供此处没有列举出来的其它内容。
简单来说,这是一个包含了各种信息的数组,而在题目中:
$_SERVER['PHP_SELF']
用于获取当前正在执行的脚本文件的路径和名称
$_SERVER['REQUEST_URI']
用于获取当前请求的URI(统一资源标识符),包括路径和查询参数部分。
他们在仅访问某脚本时返回的值完全一样:
但当传递参数则会有不同的返回:
basename()函数
该函数返回路径中的文件名部分,可以近似地认为是返回最后一个/
之后的内容
该函数有一个利用的点在于当传入的参数为非ASCII码、乱码、中文等特殊字符时会将其抛弃
PHP参数解析特性
PHP会自动将参数转换为有效的变量名:
1.删除空白符
2.将特殊字符[+ .
转化成下划线_
正则表达式/utils.php/*$/i
utils\.php
:匹配字符串中的"utils.php"部分,因为"."在正则表达式中是一个元字符,需要使用转义符""来匹配实际的点号。
\/
:匹配斜杠字符"/"。
*
:表示前面的斜杠可以出现0次或多次,即"/"可以出现0个或多个。
$
:表示匹配字符串结尾。
i
:表示忽略大小写。
Payload
知识点已经罗列完毕,现在回到题目中
思路已经明确了:修改URI,绕过waf,利用代码中的文件读取函数去进行文件读取
最终payloado:
/index.php/utils.php/%ff?show[source=1
其中%ff
用来绕过第一个匹配,[
用来绕过第二个匹配,payload经过PHP_SELF
和basename()
的作用后只剩下utils.php
,达到读取文件的结果