ctf show web入门71
2026/6/2 22:20:49 网站建设 项目流程

这道题看起来跟前几题一样但是题目给的附件告诉我们

echo preg_replace(“/[0-9]|[a-z]/i”, “?”, $s);

这意味着: 无论你的代码执行输出了什么结果,只要结果里包含 英文字母(a-z, A-Z) 或 数字(0-9),它们在输出到屏幕前,都会被正则表达式全部替换成问号 ?。

实际上你发送的命令已经生效了,只是回显(Output)被过滤遮蔽了,导致你没办法直接读出目录和 Flag 的名字

此时我们在原payload后加上exit(0);就能成功显示出来

如果 Payload 没有加 exit(0);,原本的代码是这样跑的:

eval($c); 执行了 var_export(scandir(‘/’));,此时根目录的内容被打印出来,放到了 PHP 的输出缓冲区里,并没有立刻发送给浏览器。

$s = ob_get_contents(); ob_end_clean();
把刚才缓冲区里的目录文本拿出来存进变量 $s,然后清空缓冲区。(此时屏幕上依然什么都没有)。

echo preg_replace(“/[0-9]|[a-z]/i”, “?”, $s);
关键就在这一步,PHP 拿着包含目录名的 $s 去过正则过滤,把字母数字全换成 ?,最后才 echo 打印到屏幕上。所以你之前看到了满屏的问号。

Payload 执行流程(过滤被截断)
当你把 Payload 改为 c=var_export(scandir(‘/’));exit(0); 时,魔鬼细节发生了:

PHP
// 在 eval 中的实际执行内容:
var_export(scandir(‘/’));
exit(0);
var_export(scandir(‘/’)); 同样执行了,目录数据被送入了输出缓冲区。

exit(0);
核心来了! exit() 函数的作用是立刻终止当前整个 PHP 脚本的执行。

缓冲区的自动冲刷(Flush):
当 PHP 脚本因为 exit() 强行退出时,它会触发底层的回收机制,自动把当前输出缓冲区里缓存的所有数据,原封不动地直接冲刷(Flush)输出给浏览器。

因为 exit(0); 的存在,原本属于 index.php 后面的这三行核心过滤代码:

PHP
$s = ob_get_contents();
ob_end_clean();
echo preg_replace(“/[0-9]|[a-z]/i”, “?”, $s);
根本没有机会被执行到!用 exit(0); 抄了后门,在数据被正则替换之前,就强行让它吐回给了浏览器,从而拿到了干净的根目录列表

再执行payload:c=include(‘/flag.txt’);exit(0);就可以拿出flag.txt中的内容

按常理思考,既然“读取文件”的函数被禁用了,为什么换成 include 就能绕过呢?

这涉及到 PHP 底层设计中一个非常经典的底层概念:语言结构 vs 内置函数

核心原因:include 根本不是函数
在 PHP 的安全配置文件 php.ini 中,有一个配置项叫做 disable_functions出题人就是在这里面写上了 readfile, system, file_get_contents 等函数的名字。

然而,disable_functions 只能禁用“函数”,无法禁用“语言结构”。

  1. 什么是内置函数?
    诸如 readfile()、system()、eval() 都是 PHP 内置的函数。它们在底层是作为独立的功能模块被调用的。PHP 的安全机制允许通过名字将它们精确地拉入黑名单。

  2. 什么是语言结构(Language Constructs)?
    include、require、echo、print、die、isset 这些虽然看起来后面可以加括号,但它们在 PHP 引擎(Zend Engine)中属于关键字和基本语法成分。

它们就像是 if、for、while 一样,是支撑 PHP 这门语言运行的骨架。

PHP 官方没有提供任何方法来禁用语言结构。 如果你在 disable_functions 里写上 include,PHP 根本无法启动,甚至直接崩溃。

因此,当你调用 include(‘/flag.txt’) 时,PHP 根本不会去检查 disable_functions 列表,而是直接把 /flag.txt 当作一段代码/文本包含并读取出来。

执行的全套闭环逻辑
之所以 c=include(‘/flag.txt’);exit(0); 能通关,是以下两个机制完美配合的结果:

[你的 Payload] ──> c=include(‘/flag.txt’); exit(0);
│ │
▼ ▼
【利用语言结构绕过】 【利用强制截断】
绕过 disable_functions 在末尾正则替换执行前
成功读取文件内容并缓存 强制退出,将内容原样冲刷给浏览器
include(‘/flag.txt’); 成功绕过了函数的禁用拦截,成功读取了 flag 的内容,并将它放进了 PHP 的输出缓冲区。

exit(0);(注意:exit 同样也是一个语言结构!)在代码即将走到题目原本的 preg_replace 正则过滤之前,强行掐断了 PHP 进程。

由于进程死掉前会把缓冲区的所有内容原封不动地吐给浏览器,你就成功在屏幕上看到了干净、未被替换成问号的 Flag!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询