PHP RCE 多层绕过实战:关键字过滤、空格替换与 php://filter 伪协议 | 02

目录

[RCE 受限环境绕过技术复盘](#RCE 受限环境绕过技术复盘)

阅读说明

从函数替换开始建立基线

关键词内部的字符绕过

[从伪协议认识 PHP 过滤器](#从伪协议认识 PHP 过滤器)

[用 Base64 过滤器写入文件](#用 Base64 过滤器写入文件)

[权限、运行用户与 php-fpm 沙箱](#权限、运行用户与 php-fpm 沙箱)

组合过滤器与文件读取

数组函数链解决动态文件名

十七字符限制下的参数拆分

[追到 C 源码寻找追加标志](#追到 C 源码寻找追加标志)

从后门连接回到实战方法

复盘结论

排错检查表

证据链复盘


RCE 受限环境绕过技术复盘

本文依据授课转录稿改写为对外技术复盘,全文采用第三人称转述,保留原讲解顺序、案例推进、调试过程、工具使用和讲师观点。

阅读说明

文章从函数替换开始,依次经过字符过滤、空格替代、PHP 伪协议、Base64 写入、Linux 权限与 php-fpm 沙箱、过滤器组合、数组函数、十七字符限制、C 源码检索和 webshell 连接。图片按原稿首次出现顺序插入,重复出现的截图也按原位置保留。

【小白通俗注解】RCE 可以理解为让应用代替请求者执行系统命令。复盘关注的是限制出现在哪里、怎样验证假设,以及为什么某个替代方案能工作。

从函数替换开始建立基线

复盘先从 PHP 中常见的命令执行函数开始。讲师没有把它们当作需要死记的 API,而是先比较返回值和回显方式:有的函数会自动把结果写回响应,有的函数只负责执行,结果还需要额外输出。这个差异会直接影响验证动作,因此每次测试都要同时记录"命令是否执行"和"结果是否可见"两个事实。

图 1:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

第一轮题目只对少数关键词做限制,随后逐渐把黑名单扩大到 flagsystemPHP。原先以 system 为中心的写法因此失效,但执行能力并没有消失。讲解转而寻找功能等价的函数,例如 passthru,并把函数名、参数和输出分别拆开检查。

图 2:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这种替换不是为了炫技,而是为了说明黑名单的边界:它拦截的是字符串,不是"命令执行"这一能力本身。只要另一个函数在当前运行环境中能完成相同动作,就可以继续验证。课程把这一点作为后续所有绕过的思维起点。

图 3:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

在知道目标文件名之前,先用 ls 确认当前目录是一个必要步骤;目录里可能没有名为 flag 的文件,也可能存在后缀或前缀变化。确认目录内容后,再用更短的读取命令替代探索命令,能减少输入长度和被拦截字符。

图 4:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

当命令被放入 eval 时,命令字符串实际上会被拼接成一段新的 PHP 代码。讲师特别指出,表达式末尾的分号不是装饰,而是语法边界。缺少分号时,前面的函数即便写对,也会因为生成的 PHP 代码不完整而直接报语法错误。

图 5:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

随后用一个最小例子验证:把同一段字符串分别以"有分号"和"无分号"的形式交给 eval,前者能够执行,后者在解析阶段失败。这个结果说明,测试 RCE 时不能只看过滤规则,还要把 payload 放回目标语言的语法结构中检查。

图 6:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的经验判断是:不要把 system 当成唯一答案。更可靠的能力是识别"目标需要什么效果",再寻找未被限制、行为等价的实现。

图 7:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

在基线阶段还需要区分"函数被拦截"和"函数执行但没有回显"两种情况。课程先保留最短的参数,只改变函数名,再观察 HTTP 响应是否变化;如果响应为空,不能立刻判定命令没有执行,因为某些函数本来就不会自动输出。只有把命令换成带明显结果的目录查询,或在代码中显式打印返回值,才能确认执行路径已经打通。

图 8:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

基线测试还专门比较了命令函数的输出习惯。passthru 更接近直接把系统命令结果输出到响应中,其他函数可能只返回状态或需要 echo。因此同一个命令在不同函数下看起来可能有不同结果,排查记录必须把函数、参数、响应和终端输出放在一起对照,不能只记录"页面有没有内容"。

图 9:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

当过滤规则同时覆盖函数名和目标文件名时,绕过动作被拆成两处:函数名用未被列入黑名单的等价调用替换,文件名则通过通配、引号或反斜杠改变正则看到的字符序列。两处都验证通过后,才说明规则确实被绕开;如果只替换函数而保留原始 flag,请求仍会在前置正则处失败。

图 10:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

在最初的可用函数清单中,课程还区分了"执行后自动回显"和"只返回结果"两种实现。测试时先用简单命令确认执行通道,再根据回显特征决定是否补 echo,这样可以避免把输出缺失误判成函数被禁用。这个基线为后面所有过滤器测试提供了参照。

图 11:从函数替换开始建立基线中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

正则黑名单的局限在案例中反复出现:它只能看到提交到这一层的字符串,无法理解后续 shell 会怎样拼接、转义或展开。只要输入在进入下一层前被拆成多个片段,规则就可能失去完整单词匹配能力。因此每次绕过都要说明是哪一层看到了什么内容。

此外,基线阶段还确认了不同命令在当前 shell 中的兼容性。读取、列目录和输出状态分别承担不同验证职责,不能用一个命令的结果推断全部能力。

若函数自动回显,响应内容可以直接作为证据;若函数不回显,则要通过文件变化、状态码或额外输出确认执行。两种函数的验证方式不同,但都必须留下可复核结果。

关键词内部的字符绕过

第二轮限制针对关键词本身。课程先尝试在 flag 中插入无关字符,使正则无法再匹配完整单词,同时保持命令解释器仍能识别目标文件。终端截图展示了类似 cat fl'a'g 的写法,成对引号把额外字符包住,文件名仍可被拼接出来。

图 12:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

反斜杠、单引号和双引号分别验证了不同的解析路径。单引号必须成对出现,否则会破坏字符串边界;双引号在相同位置可以承担分隔作用。这里的重点不是某一种固定符号,而是先判断字符会被哪一层解释,再利用解释顺序避开正则检查。

图 13:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

题目再次升级后,限制列表包含 catshell、点号、空格和单引号。课程先做减法:如果当前文件名不需要点号和 PHP 后缀,就不必主动引入这些受限字符;读取工具也可在 cattac 之间切换,避免把不必要的关键字带入请求。

图 14:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这样处理后,真正的瓶颈只剩空格。直接提交空格会命中过滤,提交 %20 也没有帮助,因为浏览器在请求进入程序前会先解码,服务端最终看到的仍然是空格。这个失败结果把问题定位到了"编码发生在哪一层"。

图 15:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

接下来尝试 shell 中常见的 $IFS。在 Linux 命令行里,$IFS 可以充当参数分隔符,$IFS$1 也常被用来避免显式空格。课程先在终端确认变量行为,再将其放回 HTTP 参数中验证,结果发现浏览器、PHP 字符串和 shell 三层的处理并不完全一致。

图 16:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

为了排除编码因素,讲解依次测试 %24%09 以及换行、回车、非断行空格等编码。$IFS 在本地终端有效,并不代表同一写法在目标程序中必然有效;每一次请求都要对照源码确认过滤发生在 URL 解码前还是解码后。

图 17:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

前几种替代没有反馈时,课程没有立即下结论,而是继续枚举历史题目中常见的空白字符。最终 %09 在该环境中真正替代了空格,命令恢复执行。这个结果也说明,过滤绕过高度依赖实际案例积累,只有见过足够多的解析差异,排查时才不会被单一工具的表现限制。

图 18:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的学习建议是:记录每个失败尝试同样重要。失败能说明过滤点、解码顺序或解释器边界,下一步的假设应当建立在这些证据上,而不是重复提交同一种 payload。

图 19:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

针对关键词的尝试始终围绕解析顺序展开。正则表达式先看到请求字符串,shell 随后再解释引号和反斜杠;因此一个字符在前一层看起来破坏了单词,在后一层却可能被拼接或忽略。课程还提醒,单引号、双引号和反引号的作用不能混为一谈,必须结合当前字符串是否处在 PHP 引号、shell 参数还是 URL 参数中判断。

图 20:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

$IFS 的失败尤其说明了跨层解析的风险。在交互式 shell 中,变量展开发生在命令执行之前;在 URL 中,美元符号、数字和编码又可能先被浏览器或 PHP 处理。课程先在 Linux 终端确认 $IFS$1 能分隔参数,再分别测试原文、URL 编码和双引号包裹的版本,最终以目标页面的真实反馈为准。

图 21:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

空白字符枚举并不是无止境试错。换行、回车、水平制表符和非断行空格虽然都可能被某些解析器当成分隔符,但是否能穿过当前正则取决于过滤顺序。%09 成功后,课程停止继续堆叠变体,并把这个结果记录为当前环境的可复用条件,避免把一次偶然成功误当成所有环境都成立。

图 22:关键词内部的字符绕过中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

空格替代的尝试也提醒了请求工具的影响。浏览器地址栏、抓包工具和服务端框架可能各自执行一次解码,某个编码在第一层被还原后,后续正则就会重新看到空格。课程通过逐个修改请求而非批量替换,保留了每个环节的可观察结果。

%09 最终生效时,课程仍然检查了命令是否完整执行、目标文件是否被读取以及返回内容是否稳定。一次成功响应只能证明当前输入通过了规则,不能证明所有命令、所有后缀或所有服务器版本都接受同一写法,这也是复盘中反复强调验证边界的原因。

每次字符替换都要保留原请求与改写请求的差异。只有这样,才知道是函数名、文件名、分隔符还是编码导致响应变化。

字符绕过不是把输入随意打散,而是让下一层解析器重新组合出原意。引号闭合、反斜杠转义和通配符展开各有前提,脱离上下文照抄很容易把语法破坏。

从伪协议认识 PHP 过滤器

题目给出的另一条思路使用 php://filter。讲解先解释"伪协议"这个名字:它不是浏览器访问的网络协议,而是 PHP 在处理流时提供的一组特殊包装器。包装器能改变文件读写的方式,因此经常出现在 CTF 和过滤器绕过题中。

图 23:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 24:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程列举了几个常见包装器作背景对照:httphttps 用于网络请求,file 用于文件读取,FTP 面向文件传输,phar 与归档处理有关,proc 则暴露进程视图。当前案例只需要 filter,其余内容用于建立识别范围,不在本题上展开。

图 25:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

filter 的核心能力是让数据流经过一个或多个过滤器。过滤器大致可分为字符串处理、转换、字符编码、压缩和加密几类。课程重点放在转换过滤器,因为 Base64 编解码既能改变输入形态,又能在后续步骤恢复原始内容。

图 26:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

字符串类过滤器可以统一大小写或剥离 PHP 标签,但当前题目的限制并不要求上传完整标签,所以这些选项暂时用不上。字符集转换用于兼容不同编码,现实项目里常见于旧系统迁移;在本次 RCE 题中,它们只是候选能力,不是最短路径。

图 27:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

转换类过滤器提供 convert.base64-encodeconvert.base64-decode。前者把内容变成只包含 Base64 字符集的文本,后者再把文本还原。利用这两个方向,可以把尖括号、问号、空格等敏感字符从第一步输入中移走。

图 28:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

为了让概念落地,讲解引入一个写文件示例:代码通过 file_put_contents 接收文件名和内容。只要这两个参数来自请求,调用者就有机会决定写入哪个路径以及写入什么内容。这里的风险判断依据是"用户是否能控制参数",而不是函数名称听起来是否危险。

图 29:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程进一步说明,PHP 标签过滤器若同时删除了前置拦截代码和后门自身的标签,结果仍然无法执行。因此需要设计两阶段处理:先移除阻断代码留下可解析文本,再用第二个过滤器把编码内容还原成 PHP。

图 30:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

【小白通俗注解】可以把过滤器链想成流水线。第一站只负责清掉会触发规则的字符,第二站负责解码;每站只做一件事,最终写入的内容才既能通过检查又能被 PHP 解释。

图 31:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

空格排查中还验证了点号和后缀限制是否真的影响当前文件。若题目要求读取的目标就是无后缀名称,点号限制只是背景噪声;若目标是 flag.php,则点号和 php 会同时成为输入问题。先把题目给出的文件结构确认清楚,再决定是否需要处理这些字符,能避免在并不存在的约束上浪费尝试次数。

图 32:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

过滤器类别的介绍还帮助区分"转换"和"清洗"。大小写转换会改变字符形态,但不一定删除危险标签;标签过滤器会丢弃特定片段,却可能连后门自身一起删除;Base64 编解码则改变字符集合,通常需要成对使用。只有明确每个过滤器的输入和输出,才能安排正确的先后顺序。

图 33:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

在写入示例中,课程先问两个参数是否可控,再讨论如何拼接伪协议。文件名可控意味着资源位置可变,内容可控意味着写入字节可变;两者缺一不可。这个审计顺序也解释了为什么同样调用 file_put_contents,固定文件名和固定内容的代码未必具备相同风险。

图 34:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

伪协议的枚举还包括 php://input。它可以让 PHP 从请求体读取原始数据,但当前题目需要的是文件流过滤,因此最终没有沿这条路继续。课程把它与 php://filter 并列展示,是为了让读者看到同一命名空间中不同包装器的职责差异。

图 35:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

转换过滤器的实际价值在于改变检测时机。编码后的字符串不含 PHP 标签,先经过标签清理不会影响主体;之后再解码,标签只在过滤器链的后端出现。这个顺序让"清理规则"和"执行语法"不必在同一时刻发生冲突。

图 36:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

伪协议的选择也取决于读写方向。读取时关注资源内容如何返回,写入时关注过滤器怎样处理即将落盘的字节,两条链路不能混用。

图 37:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

包装器列表中很多能力与当前题目无关,课程刻意把它们标记为背景知识。对外复盘保留这种取舍,读者可以知道为什么继续深入 filter,而不是平均解释所有协议。

图 38:从伪协议认识 PHP 过滤器中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

用 Base64 过滤器写入文件

写文件案例的第一步是确认调用参数。file_put_contents 至少需要文件名和写入内容,第三个参数还可以决定覆盖还是追加。题目中这几个值都由请求控制,因而具备形成文件写入漏洞的条件。讲师强调,审计时要沿着数据流追踪用户输入,不能只凭函数名做结论。

图 39:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

原始设想是把一句话 webshell 直接写入 shell.php,再用客户端连接。但代码前面存在 exitdie,PHP 执行到这里就会终止,后面的后门代码根本没有机会运行。所谓"可写文件"因此只是表面条件,执行顺序才决定利用是否成立。

图 40:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

绕过前置代码有两条方向。其一是让前置标签完全消失,使 PHP 把剩余内容当作普通文本跳过;其二是只清理拦截代码的标签,同时保留后门标签。直接使用标签过滤器会把两者一并删除,所以必须配合编码把后门标签保护起来。

图 41:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

Base64 规则成为关键。它以四个字符为一组,输入长度不足时用 = 补位;有效字符主要是大小写字母、数字、加号、斜杠和等号。尖括号、问号、空格等都不属于有效字符,过滤器解码时会丢弃它们。

图 42:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程用一个带前缀的字符串做实验:非法字符被删除后,剩余内容长度可能从八位变成七位,直接解码会因为分组不完整而报错。再补一个合法字符凑成八位后,解码可以完成,即使前面产生乱码,也不会影响后续 PHP 代码的解析。

图 43:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

写入方案因此改为 php://filter/write=convert.base64-decode/resource=...。注意这里是 write 而不是 read:过滤器作用在即将写入文件的内容上,resource 指向实际文件。写入内容先放入 Base64 字符串,再由过滤器在落盘前还原。

图 44:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

测试时先抓取 POST 请求,再只替换文件名与内容参数。截图中的操作顺序体现了一个重要习惯:先确认参数名和过滤器方向,再提交最小载荷,避免因为拼写错误把语法问题误判成环境问题。

图 45:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

第一次写入没有出现目标文件,课程随即转入排错。参数名、Base64 内容和过滤器拼接都核对无误后,仍需验证目录权限。把目标改成 /tmp 并写入简单的 123,可以把"payload 错误"和"目录不可写"区分开。

图 46:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

【小白通俗注解】readwrite 表示过滤器位于数据流的哪一侧:读文件时处理读出的内容,写文件时处理即将写入的内容。方向写反,即使过滤器本身正确,也不会得到预期结果。

图 47:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的判断是:面对复杂 payload,先做一个能证明链路通不通的最小测试,再逐步换回真实内容。这样每次失败都能对应一个明确假设。

图 48:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

伪协议部分还对 fdproc 等概念作了边界说明。Linux 把很多资源都呈现为文件,进程信息也可以从 /proc 看到,文件描述符则对应进程打开的输入输出句柄。它们能帮助理解系统,但与当前过滤器写文件的核心路径不同,因此课程只用截图确认概念,不把排查带到另一个方向。

图 49:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

Base64 的四位分组规则贯穿整个写入过程。编码串中出现的等号只是补位符,不是每次都必须保留;解码器关注的是分组是否完整以及字符是否属于允许集合。课程通过加入一个合法字符让长度回到八位,观察到前缀可能产生乱码,但后续 PHP 片段仍能被解释,这个实验结果成为继续写入的依据。

图 50:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

抓包改写时,课程反复核对 writeconvert.base64-decoderesource 三个部分。write 决定过滤器作用于写入流,resource 决定实际文件,编码串则放在内容参数中。任意一处写错,都会出现"请求看似发送成功、目录却没有目标文件"的现象,因此截图中的每一步都对应一个具体检查点。

图 51:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

前置 exit/die 的分析还说明了 PHP 的执行顺序。代码从上到下运行,遇到终止函数后不会继续处理后续语句;因此后门即使已经写进文件,也可能因为前置逻辑而无法触发。复盘将"文件落盘"和"代码执行"分成两个独立验证目标。

图 52:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

Base64 解码产生乱码时,课程没有直接把乱码当作失败,而是读取落盘文件确认后半段代码是否完整。只要无关字节不会破坏 PHP 解析,前缀噪声就不一定影响执行;但如果乱码落在标签或字符串中间,结果就不同,必须以实际文件内容为准。

图 53:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

后门写入前后的文件内容应分别保存证据。编码文本、解码结果和最终 PHP 代码是三个不同状态,截图和日志可以帮助定位状态转换在哪一步发生。

图 54:用 Base64 过滤器写入文件中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

过滤器链的每一段都应记录输入和输出。只有把中间状态保存下来,才能解释非法字符何时被删除、Base64 何时被解码,以及乱码为什么没有阻断后续代码。

权限、运行用户与 php-fpm 沙箱

目录权限排查从 ls -l 开始。表面上目录给了读写执行权限,并不代表处理请求的进程拥有同样权限;nginx 的 master、worker 与 php-fpm 可能使用不同用户。案例中真正执行 PHP 的 worker 属于 www-data,而不是查看目录时看到的 root。

图 55:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程把请求链路拆开说明:nginx 接收 HTTP 请求后,将 PHP 相关内容交给 php-fpm;php-fpm 再把请求整理后交给 PHP 后端执行,结果沿相反方向返回前端。写文件动作发生在后端进程里,所以必须核对 php-fpm 的运行身份和目录访问权。

图 56:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

将目标写入 /tmp 成功后,说明 Base64 过滤器和参数拼接没有根本错误;写回网站目录失败,才有理由继续怀疑目录权限。随后逐层检查目标目录、父目录和 nginx 目录,尝试调整属主属组与 755、777 等权限模式。

图 57:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

即使把子目录改成最高权限仍然失败,说明问题不一定在 Unix mode bits。父目录的访问权、进程用户的一致性以及服务启动配置都可能造成阻断。课程没有停在"再 chmod 一次",而是把每个改动后的结果重新验证。

图 58:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

确认普通文件可以写入后,排查范围转向 php-fpm 的 service 配置。AI 辅助定位到沙箱选项:某些发行版会在服务单元中启用受保护的文件系统,使进程看到的网站目录只读。此时即使目录显示为 777,进程级策略仍然可以拒绝写入。

图 59:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

解决方式是在 php-fpm 的服务配置中加入允许站点目录读写的路径,然后 reload 或重启服务。修改后先写入一个测试值,再写入 Base64 载荷,最后清理测试文件。这样可以证明修复作用来自沙箱配置,而不是偶然的权限变化。

图 60:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这个过程也说明运维知识与渗透排错是连在一起的。Apache、nginx、DNS、DHCP 等服务配置决定了应用是否稳定运行;当网站目录突然不可写时,安全人员需要同时理解文件权限、服务用户和系统保护策略。

图 61:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的经验判断是:权限给到最大仍不能解释现象时,就应停止重复 chmod,转而检查进程级安全策略。AI 可以帮助定位陌生配置,但前提是把现象、已排除项和测试结果描述清楚。

图 62:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

修复完成后,重新提交过滤器写入请求,乱码前缀被 Base64 解码器丢弃,后门代码正常出现在文件中。这个阶段的结论是:前置阻断可以通过编码过滤器绕过,写入失败则由 php-fpm 沙箱而非语法造成。

图 63:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

Base64 写入时,编码前的内容必须与解码后的目标严格对应。课程先把 PHP 代码编码,再观察编码串中是否出现过滤器允许的字符;如果在编码串前面加一个合法字符,解码后可能产生乱码,但不会改变后面的 PHP 片段。这个结果需要通过实际读取文件确认,不能只凭编码表推测。

图 64:权限、运行用户与 php-fpm 沙箱中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

权限排查还经历了从 755 到 777 的逐级验证。最初目录由 root 所有,others 只有读取和执行权限;后来虽然放宽了子目录,php-fpm worker 仍受父目录与 service 策略影响。课程通过逐层修改并重新提交请求,确认单纯提高 mode bits 无法解释失败,这一步避免了把问题错误归因于文件权限。

沙箱修复后,测试流程包括重载服务、写入普通字符串、写入 Base64 内容、读取结果和删除测试文件。先写 123 是为了证明普通写入链路恢复,再写真实载荷是为了证明过滤器逻辑也恢复。两类测试都通过后,才能把修复结论归因于 php-fpm 的受保护路径配置。

父目录继承问题的排查过程还涉及服务重启时机。目录属主改变后,已经运行的 worker 可能仍保持旧权限或旧沙箱状态,必须 reload/restart 后再测试。课程记录了重启前后的写入结果,避免把服务未重新加载误认为配置无效。

沙箱配置解决后,讲师没有继续扩大权限,而是把目录恢复到足够工作的模式。这个动作保留了排错结论,也避免把临时的 777 当成部署建议。案例最终留下的经验是:修复应针对真正的控制点,权限只给完成任务所需的最小范围。

服务配置变更后要做回归测试,至少包括普通文件、目标目录和原始载荷三类写入。这样才能避免修复只对某一次请求偶然有效。

权限调整属于诊断动作,不应直接成为生产修复。案例在确认沙箱后恢复合理权限,说明安全测试既要找到原因,也要避免留下过大的写入面。

组合过滤器与文件读取

单个 Base64 解码过滤器能工作,但落盘内容前面可能带有无关字符。为了得到更干净的结果,课程把过滤器组合成两段:先使用标签过滤器去掉非法 PHP/HTML 标签,再使用 Base64 解码器恢复真正代码。编码后的后门不会在第一段被误删,第二段才负责还原。

图 65:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

组合过滤器的写法体现了数据流顺序。第一段的输出直接成为第二段的输入,不能把两个过滤器写成互不相干的参数;只要顺序反过来,解码结果就可能再次包含会被清理的标签,导致后门残缺。

图 66:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

验证时先观察文件是否生成,再检查文件内容是否仍带有前缀。截图显示组合方案写入的是完整 PHP,而不是带乱码的混合文本。这个改进并没有改变漏洞成因,只是让过滤链的副作用更小。

图 67:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

同一包装器也可以用于读文件:将 readconvert.base64-encode 放在资源前面,读取结果会以 Base64 形式返回。课程用通配符尝试读取 flag,但第一轮没有得到目标内容,说明"能读流"与"找到正确路径"仍是两件事。

图 68:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

随后核对三种读取方式:直接用 php://filter、把过滤器结果交给变量以及通过 include 加载。直接把伪协议字符串传入 eval 不会自动触发包装器,因为 eval 只解析 PHP 语法,不负责把任意流包装器当作文件打开。

图 69:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

要让伪协议真正参与执行,需要让它出现在文件读取或包含操作的参数位置。这个判断把问题从"过滤器是否有效"转成"是否存在正确的执行环境",也是后续比较不同解法时的依据。

图 70:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

【小白通俗注解】eval 负责解析字符串,include/文件读取函数负责打开资源。把资源地址交给错误的函数,就像把文件路径当成普通文字打印,不会自动触发读取流程。

图 71:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

权限排查还涉及目录属主、属组和 others 三组权限的区别。即使目标目录显示为 755,只要执行 PHP 的用户不是属主,或者父目录缺少执行权限,写入仍会失败。把目录权限临时调高只是诊断手段,验证结束后应恢复合理权限;案例最终定位到 service 沙箱,说明 Unix 权限不是唯一控制面。

图 72:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

读取分支中,课程先把目标写成 php://filter/read=convert.base64-encode/resource=...,再确认结果是否为 Base64。没有得到 flag 时,排查重点转到资源路径和执行位置,而不是马上更换编码方式。目录扫描、通配符和包含操作分别解决"文件名未知""文件名有变化""伪协议需要被打开"三个不同问题。

图 73:组合过滤器与文件读取中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

方法比较的结论是,直接把包装器文本传给 eval 不够,因为 eval 不会替调用者打开流。要触发过滤器,包装器必须进入文件读取、包含或写入函数的资源参数。这个边界在代码审计中同样重要:字符串里出现 php://filter,不等于过滤器已经执行。

读取文件的分支还验证了通配符的影响。通配符能减少对完整文件名的依赖,但也可能匹配到多个文件,读取函数返回的顺序和内容就需要重新判断。课程先打印目录列表,再把过滤器资源限定到可确认的目标,避免把多个结果混在一起。

方法三使用 include 的思路,本质上是让文件打开动作发生在正确的函数里。eval 只负责解析已经得到的字符串,include 才会根据资源路径读取并执行文件;把两者职责分清,才能理解为什么某些看似合理的拼接没有任何反应。

读取分支还应关注错误回显。资源不存在、过滤器语法错误和权限不足可能产生不同提示,提示本身就是判断下一步的证据。

伪协议读取失败时,资源路径和执行函数是两个独立变量。先固定其中一个,再替换另一个,才能避免同时修改多个因素导致定位困难。

数组函数链解决动态文件名

当文件名未知且过滤规则复杂时,课程转向 PHP 数组函数。第一步用 scandir('.') 枚举当前目录,把目录项放入数组;点号表示当前目录。这样不必猜测 flag 的确切命名,也能确认目标文件在列表中的相对位置。

图 74:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 75:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

数组打印结果显示了多个文件名。题目原本假设 flag 位于倒数第二个位置,因此示例使用 array_reverse 把倒数第二个元素移到靠前位置,再配合 currentnext 取得目标。当前环境中文件更多,反转后位置发生变化,原方案因此未直接成功。

图 76:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 77:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这里必须区分数组的键和值。键通常是从 0 开始的索引,值才是文件名;array_rand 返回的是随机键,不是文件名本身。若直接把随机键交给读取函数,得到的只是数字索引,无法打开目标文件。

图 78:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 79:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

为此课程引入键值交换,把文件名变成键,再随机选择一个键。array_flip 完成交换,array_rand 负责随机命中,最后由读取函数处理随机得到的文件名。每次刷新都可能得到不同结果,命中 flag.php 时再进入下一步读取。

图 80:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 81:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这条路径的价值不在于随机性本身,而在于把多个基础函数串成一条可验证链路:扫描目录解决未知文件名,反转或交换解决位置问题,随机选择解决过滤条件无法表达的动态索引。每个函数单独看都简单,组合后才形成绕过。

图 82:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 83:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程也复盘了为什么只使用 current 或只使用 next 会失败。current 只能取数组当前指针的值,next 只能向后移动一个位置;目标位于中间时,需要先改变数组结构,不能靠盲目叠加 next 解决。

图 84:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 85:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

在函数选择上,讲师建议先看参数与返回值,再看名字。array_unique、切片、计数、替换等函数虽然都出现在文档里,但不一定改变目标文件的位置。只有能影响键值关系或指针位置的函数,才值得加入当前链路。

图 86:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 87:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

最终的组合是:扫描目录、必要时反转、交换键和值,再随机取键并读取。验证输出包含预期文件名后,读取过滤器负责把 PHP 文件内容编码返回。这个过程展示了 PHP 函数数量多、组合方式灵活,也正是防御难以穷举的原因。

图 88:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 89:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的观点是:分析函数时,英语理解和程序基础都会直接影响效率。先搞清楚函数返回键还是值,再判断它是否满足下一步输入要求,比反复试 payload 更可靠。

图 90:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 91:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

【小白通俗注解】数组可以看成一张"编号---文件名"清单。交换键和值,就是把编号列和文件名列对调;随机取键后,取到的就可能直接是目标文件名。

图 92:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 93:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

组合过滤器的验证包含两个层次:一是确认过滤器链被 PHP 识别,二是确认链的输出被写入或执行。课程先用普通文本测试读写,再换成 Base64 内容;这样即使后门内容没有出现,也能知道是资源路径、过滤器语法还是内容编码导致失败。这个顺序也是处理任何流包装器问题时的通用排错方法。

图 94:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 95:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

数组方案的每一个中间结果都被打印出来核对。先看 scandir('.') 的完整列表,再判断目标文件位于首位、末位还是中间;反转数组只是改变顺序,不能保证当前环境仍然把 flag 放在可读位置。随后用 array_flip 交换键值,验证 array_rand 返回的是文件名还是索引,最后才把结果交给读取函数。

图 96:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 97:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程还比较了多种看似相关的函数:去重、切片、计数、替换和随机选择。只有会改变数组结构、指针位置或键值类型的函数,才可能影响最终读取。这个筛选过程把官方文档从"函数清单"变成"按返回值和副作用选择工具",也解释了为什么不能只凭函数名猜答案。

图 98:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 99:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

数组位置变化还带来一个实践问题:题目环境和本地环境的文件数量可能不同。示例中 flag 处于倒数第二位时,反转加 next 可以命中;本地目录中插入了更多测试文件后,flag 位于中间,原索引逻辑就失效。复盘因此强调先观察当前数组,再选择函数。

图 100:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 101:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

键值交换后使用随机选择,并不保证一次命中。课程通过刷新页面多次观察随机结果,命中目标文件名后才进入读取步骤。随机函数返回键而非值的细节,是这条链路中最容易被忽略、也最容易造成误判的地方。

图 102:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

数组链路的可读性来自中间结果。把每个函数拆开打印,虽然比最终的一行表达式更长,却能让失败位置一目了然。

图 103:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

数组函数叠加后,表达式虽然变短,但理解成本变高。课程通过先写成多行代码、逐步打印,再考虑压缩,保证每个函数的作用都能被解释。

图 104:数组函数链解决动态文件名中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

十七字符限制下的参数拆分

另一道题把用户可控参数限制为 17 个字符,同时过滤 evalassert 等危险函数名。完整的一句话 webshell 远超长度上限,直接提交既会被长度检查拒绝,也无法完成写文件动作。解决方向是把短逻辑留在受限参数,把长内容放到另一个请求参数。

图 105:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 106:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

第一种方案使用 eval 读取外部参数,再让反引号表达式执行该参数中的系统命令。受限参数只需要包含很短的取值和执行结构,真正的命令内容通过 GET 参数传入。先用 ls -al 验证执行,再替换成长的写文件命令。

图 107:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

图 108:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

写入后门时,回显并不是必要条件。原始示例中的 echo 主要用于观察结果,但文件写入场景可以省略回显,把字符预算留给文件名、内容和追加逻辑。这个调整体现了"按目标选择最小功能"的原则。

图 109:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

测试结果显示,短参数执行成功,长参数没有受到 17 字符限制。随后把命令改成写入 webshell 文件,再用客户端验证连接。只要短参数负责中转,外部参数的长度就不再由前置正则直接控制。

图 110:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程把这条路径评价为实现难度较低、环境依赖较高。思路来自前面 GET 传参的案例,因此做过类似题目的人容易想到;但如果目标环境没有可用的参数读取或反引号执行能力,方案就不能照搬。

图 111:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这里还验证了最基础的一句话后门能否连接。讲师提醒,实验环境里能连接不代表真实环境允许直接上传,安全设备通常会对常见后门特征、文件扩展名和请求行为进行拦截。

图 112:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的经验判断是:把长动作拆到外部参数并不是"绕过长度"的魔法,而是改变了检查对象。理解检查发生在哪个参数上,才知道哪些内容可以移动。

图 113:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

数组函数链中,目录扫描结果的顺序取决于实际环境,不能照抄题目示例中的索引。课程在本地输出完整数组后再决定是否反转,随后打印交换后的键值,最后才加入随机选择。每个中间结果都可见,能够解释为什么某次刷新读到的是普通文件、为什么下一次才可能命中目标。

图 114:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

短参数中转方案的关键是分离检查与执行。前置正则只检查接收中转代码的字段,长命令在另一个字段中传递,服务端取值后才组合。课程先用目录列表命令测试回显,再换成写文件动作;如果一开始就提交后门,失败时无法判断是长度超限、命令未执行还是文件权限不足。

图 115:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

写文件时,回显函数可以去掉,原因不是它危险,而是它不再承担验证职责。文件是否生成、内容是否正确和客户端能否连接,才是这一阶段的三个结果指标。省下的字符预算用于短文件名、参数读取和追加标志,体现了受限输入下的精确取舍。

图 116:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

十七字符限制下的长参数移动,还要求确认后端是否对最终拼接字符串做二次过滤。案例环境只在前置字段检查长度,因此外部参数可以承载较长命令;若后端在组合后再次执行黑名单或长度检查,就需要重新设计分段方式。

图 117:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

第一种方案的验证顺序是先读目录、再写文件、最后连接客户端。每一阶段都使用不同的成功指标,避免把"命令有回显"直接等同于"后门已写入"。这种分阶段检查也为后续排查权限和函数禁用留下了清晰界线。

图 118:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

短参数方案的边界也需要记录。中转参数只在目标程序允许跨参数取值时成立,换成只读取单一字段的实现后,思路可能完全失效。

图 119:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

长度限制题还说明了传输层与执行层的区别。请求字段可以很短,服务端组合后的代码可以很长;审计时必须分别检查两层是否有二次限制。

图 120:十七字符限制下的参数拆分中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

追到 C 源码寻找追加标志

第二种长度受限方案围绕 file_put_contents 展开。文件名被压缩成单个字符,写入内容则按字符逐次提交。逐次提交意味着后一次不能覆盖前一次,否则前面已经写入的 PHP 片段会被清掉,因此第三个参数必须开启追加模式。

图 121:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

官方文档给出的追加常量名称较长,直接放入 17 字符参数会超限。课程先确认第三个参数确实控制覆盖/追加,再提出疑问:是否存在更短、数值相同的写法。这个问题无法只靠函数签名回答,需要进一步查实现。

图 122:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

PHP 的底层实现由 C 语言编写,文件写入标志最终会映射为 C 层的数值。讲解借助 AI 检索相关源码,让工具先列出保留逻辑,再定位追加模式对应的常量值,避免人工通读数千行底层代码。

图 123:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

源码结果显示,追加模式的数值为 8,覆盖模式使用另一组值。于是原本十个字符左右的追加参数可以压缩成单个字符 8。正常开发仍应使用可读的常量名,单字符只服务于长度极端受限的测试。

图 124:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

写入内容开头的 P 也有来历:它是 PHP 起始标签经过 Base64 编码后的首字符。直接提交 <? 等标签字符会被过滤或触发解析错误,因此先把整段 PHP 代码编码,再逐字符追加到目标文件。

图 125:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

追加完成后,文件中先出现的是 Base64 文本,而不是可执行 PHP。课程再利用 includephp://filter,把文件内容交给解码过滤器还原。包含动作负责触发读取,过滤器负责转换,最终 eval 执行还原后的代码。

图 126:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

第一次看不到新文件并不说明转换失败。题目要求通过 POST 参数传入触发值,如果只完成文件拼接却没有提交这个参数,包含代码不会执行。补上触发参数后,转换结果出现,连接测试才有意义。

图 127:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这条解法把三个限制逐个拆开:GET/POST 参数先减少固定结构长度,源码常量把追加标志压缩到一个字符,Base64 则把非法标签从输入阶段移走。每一步都针对一个具体失败原因,缺少任何一步都会导致最终链路断开。

图 128:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

【小白通俗注解】这里的 8 不是凭空记忆的暗号,而是底层实现中代表"追加"的数值。可读性与长度是两种不同目标,正常代码优先可读性,受限题目才会考虑数值短写法。

图 129:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的评价是:当常规函数用法无法解释现象时,向底层实现追问往往能找到替代值;AI 可以降低检索成本,但仍需要人来提出正确的问题并验证结果。

图 130:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

十七字符案例还验证了限制究竟施加在 POST 参数、GET 参数还是最终执行字符串上。短的中转代码放在被检查字段中,长命令放在另一个字段,服务端取值后才组合执行;这不是简单删除字符,而是改变了长度检查与执行发生的先后。若后端在组合后再次检查总长度,第一种方案就会失效。

图 131:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

追踪追加标志的过程还验证了常量可读性与长度之间的取舍。正常代码使用 FILE_APPEND 更清晰,也更容易被维护;题目把参数限制到十七个字符时,才有必要查底层数值。AI 返回源码片段后,课程继续对照 file_put_contents 的参数映射,并通过实际追加多个字符确认 8 没有被误读成覆盖模式。

图 132:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

追加 Base64 字符串后,include 只是触发执行,真正的还原由 php://filter 完成。若没有 POST 参数,包含代码不会进入预期分支,所以页面看不到新文件或连接结果。补上触发值后,解码、执行和客户端连接才连续发生,这也是"没有输出"不能直接等于"没有执行"的又一个例子。

图 133:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

源码检索还解释了为什么追加参数必须放在正确位置。file_put_contents 的第三个参数控制写入模式,缺少它时默认覆盖;把 8 放到文件名或内容位置不会改变模式。课程通过参数位置和底层常量两次核对,确保短写法没有被误用。

图 134:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

Base64 逐字符追加完成后,文件内容仍是编码文本,只有触发包含并进入过滤器链才会恢复。课程把"追加成功""解码成功""执行成功"分别记录,任何一个阶段缺失都不会贸然进入客户端连接测试。

图 135:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

底层常量的短写法必须绑定版本验证。不同实现或编译选项可能改变细节,源码结论仍需在实际 PHP 环境中用追加测试确认。

图 136:追到 C 源码寻找追加标志中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

底层源码检索的价值在于确认语义,而不是追求"越底层越高级"。如果上层文档已经给出可读、可靠的写法,应优先使用文档;只有长度或兼容性成为瓶颈时,才有必要继续向下追。

从后门连接回到实战方法

文件写入与执行验证完成后,课程使用冰蝎一类的 webshell 管理工具测试连接。配置时需要填写实际文件路径和约定密码,客户端发出的请求必须与后门代码接收的参数一致。连接成功后,工具可以浏览文件、打开虚拟终端并执行命令。

图 137:从后门连接回到实战方法中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

管理工具提供便利,但进程权限通常很低。命令能执行到什么程度取决于 PHP worker 的用户、目录权限以及系统安全策略;如果 disable_functions 禁用了命令执行函数,即使后门代码存在,常规命令通道也可能不可用。

图 138:从后门连接回到实战方法中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

课程把 disable_functions 作为下一层防御示例,并提到绕过限制需要单独研究。这里的重点不是给出万能绕过,而是提醒读者:连接成功只代表入口成立,后续权限、函数白名单和 WAF 仍然会改变可操作范围。

图 139:从后门连接回到实战方法中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师随后展示本地工具中积累的多个站点记录,用来说明真实环境通常存在云厂商 WAF。第一次遇到陌生云防护时可能被拦截,了解拦截策略后再调整请求形态,才能把靶场中的技巧迁移到真实测试。

图 140:从后门连接回到实战方法中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

讲师的学习建议是:靶场用于建立知识点,但理想环境没有完整防御。真正的安全能力还包括识别限制、设计绕过、记录排错过程和解释为什么某次尝试失败。

图 141:从后门连接回到实战方法中的代码、请求参数或终端输出截图,用于核对对应步骤的操作与结果。

这种方法同样适用于面试。被问到 SQL 注入、应急响应或某个漏洞时,先讲清现象、步骤和结果,再补充绕过与防御思路。完整的案例叙述比只报出漏洞名称更能体现实际经验。

讲师还建议利用简历主动设定讨论范围。简历写清做过的项目、处理过的防御和擅长的方向,面试官通常会围绕这些内容追问;如果简历过于空泛,问题可能落到不熟悉的领域,回答质量就难以控制。

最终回看整场案例,核心方法可以归纳为三点:先定位限制层,再用最小测试验证假设,最后寻找行为等价但表达更短的实现。函数替换、空格编码、过滤器链、权限排查、数组组合和源码检索都遵循同一套节奏。

【小白通俗注解】"绕过"不是绕开所有安全措施,而是识别某条规则只检查了哪一部分,再在合法的解析顺序中移动或转换输入。每一次尝试都必须有边界、有记录,并只在授权环境中验证。

追踪 C 源码时,课程没有把 AI 返回的结论直接当作答案。先要求工具列出 file_put_contents 对应的底层调用,再让它解释第三个参数如何映射到打开模式,最后用实际追加测试验证 8 的效果。源码检索提供候选值,实验结果负责确认候选值确实适用于当前 PHP 版本。

工具连接验证完成后,课程把注意力重新放回防御。站点数量增加时,手工记忆文件路径、密码和权限状态容易出错,管理工具可以集中维护这些记录;但工具本身不会提高 PHP worker 的权限,也不会绕过所有函数禁用。每个站点仍需单独确认 WAF、目录权限和 disable_functions

面试建议部分延续了同一套复盘结构。回答 SQL 注入或应急响应问题时,先交代遇到的现象,再讲排查顺序、采取的动作和最终修复;如果还有绕过或防御方面的理解,可以主动补充。简历写得具体,面试官更容易沿着做过的项目提问,候选人也更能围绕熟悉的案例展开。

真实环境中的 WAF 会改变请求可见性。实验中能够回显的命令,在云防护下可能被拦截或被替换响应;因此面试或复盘时要说明测试环境、请求是否经过 WAF 以及权限属于哪个用户,不能把靶场结果包装成普遍结论。

面试表达的核心不是把所有术语堆出来,而是展现定位问题的过程。讲师建议先围绕简历中的项目讲完整案例,说明遇到的防御、排查动作和修复结果,再根据面试官追问补充 SQL 注入绕过、应急响应等相关知识。

客户端连接只是最后一个验证点。此前的文件、权限、执行和回显证据都应保留,才能在连接失败时快速判断是后门内容还是工具配置出了问题。

连接工具的最后一步应回到授权边界。所有截图和命令只用于课程靶场或明确授权的测试环境,复盘重点是方法和证据链,而不是对未知目标实施操作。

复盘结论

这场复盘展示的是一条连续的工程化排错链:先从黑名单中找出可替代函数,再确认 URL 解码和 shell 解析的边界;遇到文件写入问题后,依次排除参数、语法、目录权限、运行用户和沙箱;当文件名与长度继续受限时,再借助 PHP 数组函数和底层 C 源码寻找等价表达。

讲师保留的核心观点包括:用户输入统一按不可信数据处理;函数黑名单不等于能力消失;失败尝试应转化为下一次假设;AI 适合帮助检索陌生配置与源码,但判断和验证仍由操作者完成;靶场经验必须结合 WAF、权限和函数禁用等真实防御;面试表达要用完整案例和简历主动引导讨论。

排错检查表

为了让复盘可以被复用,整场操作还可以整理成一份顺序检查表。第一,确认函数是否执行以及结果是否回显;第二,列出黑名单中的函数、关键字和分隔符;第三,判断 URL 解码、PHP 解析和 shell 展开发生的先后;第四,用最短命令确认当前目录和目标文件;第五,分别验证引号、反斜杠、通配符和空白字符;第六,区分 readwriteresource 的流向;第七,用普通字符串验证文件写入,再换成编码内容;第八,对照进程用户、父目录权限和 php-fpm service;第九,必要时检查沙箱与函数禁用;第十,保留每个中间输出,最后才用管理工具连接。

这份检查表并没有新增解法,而是把原讲解中反复出现的判断顺序显式化。它提醒读者:任何一次"成功"都应说明成功发生在哪一层,任何一次"失败"都应对应一个下一步假设。

证据链复盘

从第一张函数截图到最后的客户端连接,课程始终把命令、请求、源码、目录权限和服务配置当作同一条证据链。函数替换验证的是黑名单边界;%09 验证的是空白字符在当前解析顺序中的位置;过滤器写入验证的是数据如何在落盘前变形;/tmp 测试验证的是 payload 与目录权限可以分开判断;php-fpm service 修复验证的是进程沙箱能够覆盖传统文件权限;数组打印、源码常量和连接结果则分别验证了动态路径、长度限制和最终执行状态。

按这种方式记录,任何读者都能从某一步回溯到对应的输入、观察结果和推理,而不必把成功归结为一段不可解释的字符串。课程中使用 AI 检索配置和 C 源码,也是同样的原则:先明确未知点,再索取可验证的候选解释,最后回到环境里做最小实验。只有把工具输出接回证据链,AI 才是排错助手,而不是替代判断的黑箱。

对外发布时,完整保留失败过程尤其有价值。它展示了为什么 %20$IFS、目录 chmod 或直接 eval 伪协议没有立即解决问题,也展示了如何据此缩小排查范围。相比只展示最终 payload,这种复盘更接近真实工程工作:结论来自连续验证,而不是事后拼凑。

补充来看,这些案例之间并不是互相孤立的技巧清单。前一阶段对解析层的观察,会直接影响后一阶段对过滤器顺序的安排;前一阶段记录的权限用户,会决定写入测试应放在哪个目录;前一阶段确认的数组返回值,又会决定下一步读取函数接收键还是值。把这些依赖关系保留下来,读者才能按原顺序复现推理,而不是只得到若干看似神奇的字符串。

为了让技术复盘具备可操作性,文中还保留了每次"先做最小验证、再扩大载荷"的节奏。这样做既能减少无效请求,也能在发生异常时快速定位责任层。对读者而言,最重要的不是记住某个固定字符串,而是知道如何从反馈反推过滤规则、执行顺序和权限边界。

相关推荐
Jay Kay35 分钟前
深入理解 RDMA 内存管理:海思 HNS RoCE 架构中的 HEM 表与 MTR 表有什么区别?
服务器·网络·架构
木心术143 分钟前
卫星通信技术全景:从低轨星座组网、相控阵 EIRP 工程计算到 NTN 标准演进(2026 深度梳理)
网络
我星期八休息1 小时前
软件测试—从认识到BUG
考研·安全·bug
你怎么知道我是队长1 小时前
计算机网络中的 DNS 与 DHCP 详解
网络·计算机网络
智者知已应修善业1 小时前
C#用一个循环[等效]取出字符串中的数字
网络·c#·web·string·语言
金立基胶粘1 小时前
纸袋热封胶是什么?它的特点与应用有哪些?
大数据·安全
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】Gemini模型接入知识体系
java·开发语言·网络·人工智能·网络协议·学习·http
Jay Kay1 小时前
RDMA中CQ为什么用轮询?——从CQ、Doorbell到GID的深度解析
运维·服务器·网络
SendTomo1 小时前
SendTomo 与 send.wang 全景对比评测与实战指南
网络·网络协议·tcp/ip·webrtc·p2p