CTFshow Web55这道题,算是我印象里“无字母数字 RCE”系列里非常有代表性的一关。很多新手第一次看到preg_match("/[a-z0-9]/i", $c)的时候,第一反应都是“完了,命令全被过滤了”,但实际这道题恰恰是给你打开了一扇窗:它过滤的是字母和数字,而不是过滤shell 的通配符语义。点号.在这个关卡里,就是那把被大多数人忽略的钥匙。这篇文章我会从题目本身出发,把“为什么点号能绕过”的原理讲透,再把可直接落地的 payload 一步步拆给你看。无论你是刚开始接触 CTF Web 方向,还是已经在命令执行类题目里打转了一段时间,这篇内容都能帮你把“无字母数字命令执行”这一类题目的底层逻辑理清楚。1. 先把题目看清楚:无字母数字限制到底卡住了什么CTFshow Web55 这类题目的核心代码通常长得非常克制,就几行:<?php highlight_file(__FILE__); if (isset($_GET['c'])) { $c = $_GET['c']; if (!preg_match('/[a-z0-9]/i', $c)) { system($c); } } else { highlight_file(__FILE__); }也就是说,你传进去的c参数,只要包含任意一个字母或数字,就会被拦下来。常规的cat /flag、ls /、whoami这种命令,一上来就被a-z0-9正则打回原形,根本活不到system($c)执行的那一步。但注意一个关键点:这个正则过滤的对象只有原始传入的字符串,它不会去检查 shell 在执行命令时通配符展开出来的结果。这就是这类题目的突破口。很多入门选手会陷入一个误区,以为system()收到的字符串里不能出现字母,那么最终执行的命令里也不能出现字母。这是错的。system()会把字符串交给系统的 shell(一般是/bin/sh或/bin/bash),shell 在执行前会先做通配符展开(glob)、变量展开、命令替换等一系列操作。也就是说,我们完全可以构造一串“没有一个字母数字”的原始字符串,让它经过 shell 展开之后,变成一条实打实的cat /flag命令。举个最简单的例子,/bin/cat的路径一共是 8 个字符:/+bin+/+cat。如果我把其中的字母全部换成通配符?,就得到:/???/???请你在终端里执行一下:ls /???/???大概率你会看到/bin/cat也在这个匹配结果里。因为/???/???正好能匹配到/bin/cat这个路径。而?既不是字母也不是数字,所以它能够顺利通过preg_match的过滤。这里就出现了一个很自然的思路:既然system()会把?展开成真实的路径,那我不就可以用一堆?把命令名和参数名都“拼”出来吗?比如:/???/??? /????shell 展开之后可能就变成了:/bin/cat /flag这是整道题的核心思想,也是理解“点号绕过”之前必须先吃透的基础。2. 点号为什么能绕过:bash 里的.远比你想的能干很多人看到标题里写“用点号绕过”,第一反应是“点号就是个路径开头而已”。其实点号.在 Linux shell 里有好几种身份,而这些身份在这道题里都可能被利用:第一,.是“当前目录”的简写。在路径解析层面,./xxx表示“当前目录下的 xxx”。当我们用system()执行命令时,工作目录往往是 Web 服务启动时的目录。如果这个目录恰好是/,那么./bin/cat和/bin/cat就是同一个东西。第二,.是 Bash 的内建命令,等价于source。单独敲一个.,后面跟一个文件名,它会把那个文件当作 shell 脚本加载并执行。如果文件内容里写着命令,那这些命令就会在当前 shell 里跑起来。第三,.本身是可以参与 glob 匹配的字符。在 shell 的通配符规则里,.只会在一种情况下特殊:当你用*去匹配隐藏文件时,默认不匹配以.开头的文件名。但如果你把.写死在模式里,比如./???,那它就是一个普通字符,表示“当前目录下名字为 3 个字符的文件”。CTFshow Web55 这道题最常用的点号 payload 是这两种形态:. /???/??? /????和./???/??? /????两者只差一个空格,但含义完全不同。第一种是让.作为source内建命令去加载文件;第二种是把.作为路径前缀去执行一个相对路径命令。在实际解题中,很多人一上来就用./???/??? /????直接打通,就是因为 Web 服务的当前工作目录正好是根目录或者命令可执行。但这个点号真正厉害的地方,在于它出现在通配符模式里也不会触发正则过滤。正则只查字母数字,而.、?、/、空格都在白名单里。于是你可以放心大胆地把.加进 payload,让它参与路径拼接或者作为 source 命令。为了让你更直观地理解,我建议本地做一个最小实验。在终端里执行:cd / ./???/??? /????如果你机器上有/flag文件,这条命令会执行./bin/cat /flag。如果没有/flag,你也可以改成:./???/??? /etc/passwd同样能把/etc/passwd的内容打出来。这里你可以看到,整条命令里没有任何一个字母数字,完全靠.、?、/、空格把/bin/cat和/etc/passwd给“拼”了出来。所以点号在这道题里的本质作用是:它让路径可以从“当前目录”开始解析,同时又满足正则过滤条件,从而把真正的命令名和参数名都交给 shell 的通配符展开机制去完成。3. Web55 完整实操:三段式构造一条可用 payload理论说完了,下面进入可以直接抄作业的实操环节。CTFshow Web55 的目标一般就是读根目录下的 flag 文件,文件名可能是/flag,也可能是/flag.txt,甚至可能是/fllllllag这种变体。所以构造 payload 时要给文件名长度留点余量。3.1 第一步:确认当前工作目录虽然很多 writeup 里一上来就甩 payload,但实际做题时我强烈建议你先确认一下当前工作目录。方法很简单,找一个不含字母数字的命令来执行pwd:/???/????注意/???/????的长度是 1 + 3 + 1 + 4 = 9 个字符,正好能匹配/bin/pwd(/bin/pwd也是 9 个字符)。在靶机上请求:/?c=/???/????如果你看到类似/var/www/html的输出,说明当前目录不是根目录,那么./???/???这种相对路径可能不好使,你得优先考虑绝对路径通配/?/???这种写法。如果你看到输出是/,那就太好了,直接上相对路径 payload 也能通。3.2 第二步:先用系统命令摸清 flag 文件位置在没有字母数字的情况下,怎么列出根目录内容?我们可以让cat先靠边,先尝试执行ls。/bin/ls的路径长度是 7 个字符:/+bin+/+ls,也就是:/???/??在靶机上请求:/?c=/???/??如果/bin/ls是唯一匹配项,你应该能看到根目录下的文件列表。如果?匹配到了太多文件导致命令出错,可以试着把路径写得更精确一点,比如把bin用b??来匹配:/???/?? // 可能嫌太宽泛 /b??/??但b是字母,会被正则过滤。所以这类题的常规做法不是追求 100% 精确匹配,而是让匹配结果恰好落在你想执行的命令上。CTF 靶机的文件系统通常比较干净,/bin/ls和/bin/cat往往是 7 或 8 字符路径里最合适的候选。3.3 第三步:构造读取 flag 的最终 payload假设你已经确认 flag 文件就在根目录,名字是flag,长度 4 个字符。那最常见的 Web55 点号绕过 payload 就是:/?c=. /???/??? /????注意这里.和后面的/之间有一个空格。在 URL 里,空格要编码成%20或+。所以实际请求一般是:/?c=.+ /???/???+/????等一下,我上面说了.加空格是source语义,这里为什么能读 flag?因为在很多 CTF 环境里,/???/???匹配到的可能是一个可以加载的 shell 脚本,或者靶机环境本身被刻意配置成了“source 也能执行关键逻辑”。更常见的实际请求其实是下面这种不带空格的版本:/?c=./???/??? /????这个请求会先让 shell 看到./???/???,把它解析成一个可执行路径,然后传入参数/????。如果当前工作目录是/,等价于执行:./bin/cat /flag也就是:/bin/cat /flag如果你不确定 flag 文件的长度,可以多试几个参数模式:参数模式可能匹配的文件/????/flag、/tmp、/dev等 4 字符路径/?????/flag.txt、/hello等 5 字符路径/????????常见带下划线或数字的 flag 文件名实际做题时,我遇到过好几次 /flag 文件名被改成长名字的情况,比如/f1aggg、/flag_123。所以别只盯着/????一个模式,灵活调整?的个数非常关键。3.4 一个比较稳的备选 payload如果你发现./???/???因为当前目录的原因执行不了,或者 glob 匹配到了多个结果导致 shell 报错,可以用绝对路径通配来绕:/?/??? /????这里/?表示根目录下所有单字符目录,在大多数 Linux 容器里只有/?能匹配/bin吗?不一定,但通常单字符目录非常少,所以/???几乎必然落在/bin上,后面/???也能大概率命中cat。我自己在 CTF 靶机上测试时,这个 payload 的成功率比点号版本还要高,因为它不依赖当前工作目录。不过既然题目专门强调点号,那考试或练习时最好还是把点号的原理和写法都掌握,别只背一个绝对路径版本。4. 常见问题与排查技巧实录无字母数字 RCE 这类题,最让人头疼的往往不是“不会构造 payload”,而是“payload 看起来没问题但就是不出 flag”。这里我把实际踩过的坑整理成一份排查清单,你照着检查一遍,基本能解决 90% 的问题。4.1 glob 匹配到多个文件导致命令执行失败这是最常见的问题。比如/???/???在有些环境下不只是匹配/bin/cat,还可能匹配到/bin/cp、/bin/df、/bin/mv等等。当system()把字符串交给 shell,shell 会把所有匹配到的路径都展开成一个个独立的词,这就等于执行了/bin/cat /bin/cp /bin/df ... /flag,结果自然不是你想要的。排查方式很简单:先把 payload 里的执行动作改成ls类的命令,看你匹配到的文件到底有哪些。比如:/?c=/???/??如果这条命令能列出/bin/ls本身,说明 glob 的命中是可控的。如果???模式下命中的文件太多,就手动加长或缩短?的个数,让模式更贴近真实路径长度。另外,你可以在通配符里加字符集合来缩小范围,比如:/???/[!a-f]??但注意a-f里有字母,会被 PHP 正则过滤。所以这种方法在 Web55 里不适用,只能在理解原理时用。4.2 空格被 Web 服务器吃掉或被 URL 编码搞乱system()执行的是完整的命令行,命令和参数之间必须有空格。但 URL 里直接写空格是不规范的,容易被解析成%20或+。很多新手在 payload 里写:?c=. /???/??? /????实际请求发出去的时候,空格变成了%20,但某些中间件会把%20解码成空格,没问题。可有些平台会比较严格,需要你用+代替空格:?c=.+/???/???+/????这里有个很容易被忽略的细节:如果你的 payload 是./???/??? /????,中间那个空格同样要编码。很多 writeup 省略了这个细节,导致你直接复制粘贴时总感觉命令不对。我的习惯是:构造完 payload 后,先请求一遍不带执行结果的 URL,打开浏览器的开发者工具,看请求的真实 URL 是什么。如果空格变成了+,那服务器收到的就是合法空格;如果+变成了字面加号,那就要改用%20。4.3 点号本身被匹配成隐藏文件还有一种情况:你在当前目录用./?去匹配文件时,会匹配到隐藏文件吗?不会。在 shell 里,如果模式开头不是显式的.,?和*默认不匹配以.开头的文件名。但如果你写的是./???,前面的.是显式的路径前缀,后面的???就可以匹配到隐藏文件了。比如当前目录有.bashrc这种文件,./?????可能匹配到它。这在 Web55 里倒不是大问题,但如果你的 payload 变成了执行一个不想执行的文件,就可能产生意外输出。遇到这种情况,先用?数量去对齐目标文件,别盲目加?。4.4 system 与 shell 的环境差异很多 CTF 靶机为了减小体积,用的是 Alpine Linux,默认 shell 是 BusyBox 的 ash。BusyBox 的 glob 规则和 Bash 基本一致,但有些内置命令的路径和标准发行版不完全一样。比如/bin/cat在 Alpine 里可能是一个指向/bin/busybox的符号链接,但符号链接不影响 glob 匹配,/???/???照样可以命中。但如果你在题里发现/???/???匹配不到任何内容,那就要考虑靶机是不是把命令放在/usr/bin而不是/bin。这时候可以尝试匹配/usr/bin下的路径,比如:/???/???/???这是 1 + 3 + 1 + 3 + 1 + 3 = 12 个字符,能匹配/usr/bin/cat吗?不一定,具体要看/usr/bin里哪个命令是 3 个字符的。所以还是要先列出目录内容,摸清环境再动手。4.5 文件内容不是纯文本,命令执行了但看不到 flag如果你用了cat,但看到的是乱码或者二进制内容,说明你 cat 错了文件。比如/???/???匹配到了一个二进制命令本身,而不是 flag 文件。这时候你应该检查参数部分用的 glob 是不是太宽泛,匹配到了/dev或/tmp这样的目录。建议先用长度最短的/????去试探,如果还是不行,就用/???/??执行ls来确认根目录下到底有哪些文件。记住,先侦察,后读取,这两步别混在一起。5. 同类型题目的扩展思考:点号之外,还有哪些“无字母数字”套路Web55 只是无字母数字 RCE 的一个入口。理解了点号和通配符之后,你能顺着这条线继续摸到更多变种,我在这里帮你把这些套路放一起对比一下。5.1 通配符直接执行这是最基础的一种,也是 Web55 的核心。利用?、*去匹配命令路径,核心原则就是让正则无法拦截“展开前”的字符串:/???/??? /????它不依赖点号,但点号可以帮你把路径改成相对路径,增加一层灵活性。5.2 取反构造字符串PHP 里有一种非常经典的技巧,用按位取反运算符~配合非字母数字字符,在运行时构造出_GET、system这些字符串。比如:?c=$_=~%98%BB%8D%9A%88;$_();这种技巧用于eval类的场景,和system类场景是不同的赛道。Web55 如果换成eval($c),你就得走这条路,而不是走纯 shell 通配符路线。5.3 启动 shell 再喂命令在 Bash 里,$0会保存当前 shell 的名字。利用这个特性,可以通过重定向把后面的字符串输入给新启动的 shell 执行。常见的思路是:$0<<<...当然,...部分如果包含字母数字,还是要经过编码或构造。这种方法的优点是灵活,缺点是需要对 Bash 的 here-string 很熟。5.4 利用已有文件当脚本这就是点号source语义的延展。如果靶机的 Web 目录下有某个写着命令的脚本文件,你可以用.把它加载执行。但真实的 CTF 环境很少这么慷慨,所以这个方法更多是在“当前目录有可控 PHP 文件”或“上传点”的场景里使用。我个人的经验是:无字母数字 RCE 题,第一件事永远是搞清楚代码是system还是eval,第二件事是搞清楚 shell 环境,第三件事才是背 payload。这三步的顺序反了,你会陷入“背了一堆 payload 但全都不通”的困境。6. 最后再分享一个小技巧很多人在 Web55 里卡住,是因为以为“无字母数字”就是不能出现任何字母数字。其实你只要记住一句话:正则只能过滤你输入的原字符串,它管不到 shell 展开后的结果。所以构造 payload 的时候,你要做的就是让原字符串里全是符号,同时让这些符号经过?的展开,变成一个合法的命令。我个人在实际解题时,会先在本地用一个极简的 PHP 环境复现这道题。具体做法是起一个容器,放一个带system的 PHP 文件,然后手动请求./???/??? /???这种 payload,一步步观察 shell 的展开结果。这种方法能帮你快速理解哪些?长度是稳定的,哪些命令路径跟你想的不一样。最后提醒一句:CTF 和授权靶场的命令执行练习,目的是训练思路,请一定在合法合规的环境里进行。不要把这类绕过技巧用在未经授权的系统上,安全研究的前提永远是授权和边界意识。
CTFshow Web55这道题,算是我印象里“无字母数字 RCE”系列里非常有代表性的一关。很多新手第一次看到preg_match("/[a-z0-9]/i", $c)的时候,第一反应都是“完了,命令全被过滤了”,但实际这道题恰恰是给你打开了一扇窗:它过滤的是字母和数字,而不是过滤shell 的通配符语义。点号.在这个关卡里,就是那把被大多数人忽略的钥匙。这篇文章我会从题目本身出发,把“为什么点号能绕过”的原理讲透,再把可直接落地的 payload 一步步拆给你看。无论你是刚开始接触 CTF Web 方向,还是已经在命令执行类题目里打转了一段时间,这篇内容都能帮你把“无字母数字命令执行”这一类题目的底层逻辑理清楚。1. 先把题目看清楚:无字母数字限制到底卡住了什么CTFshow Web55 这类题目的核心代码通常长得非常克制,就几行:<?php highlight_file(__FILE__); if (isset($_GET['c'])) { $c = $_GET['c']; if (!preg_match('/[a-z0-9]/i', $c)) { system($c); } } else { highlight_file(__FILE__); }也就是说,你传进去的c参数,只要包含任意一个字母或数字,就会被拦下来。常规的cat /flag、ls /、whoami这种命令,一上来就被a-z0-9正则打回原形,根本活不到system($c)执行的那一步。但注意一个关键点:这个正则过滤的对象只有原始传入的字符串,它不会去检查 shell 在执行命令时通配符展开出来的结果。这就是这类题目的突破口。很多入门选手会陷入一个误区,以为system()收到的字符串里不能出现字母,那么最终执行的命令里也不能出现字母。这是错的。system()会把字符串交给系统的 shell(一般是/bin/sh或/bin/bash),shell 在执行前会先做通配符展开(glob)、变量展开、命令替换等一系列操作。也就是说,我们完全可以构造一串“没有一个字母数字”的原始字符串,让它经过 shell 展开之后,变成一条实打实的cat /flag命令。举个最简单的例子,/bin/cat的路径一共是 8 个字符:/+bin+/+cat。如果我把其中的字母全部换成通配符?,就得到:/???/???请你在终端里执行一下:ls /???/???大概率你会看到/bin/cat也在这个匹配结果里。因为/???/???正好能匹配到/bin/cat这个路径。而?既不是字母也不是数字,所以它能够顺利通过preg_match的过滤。这里就出现了一个很自然的思路:既然system()会把?展开成真实的路径,那我不就可以用一堆?把命令名和参数名都“拼”出来吗?比如:/???/??? /????shell 展开之后可能就变成了:/bin/cat /flag这是整道题的核心思想,也是理解“点号绕过”之前必须先吃透的基础。2. 点号为什么能绕过:bash 里的.远比你想的能干很多人看到标题里写“用点号绕过”,第一反应是“点号就是个路径开头而已”。其实点号.在 Linux shell 里有好几种身份,而这些身份在这道题里都可能被利用:第一,.是“当前目录”的简写。在路径解析层面,./xxx表示“当前目录下的 xxx”。当我们用system()执行命令时,工作目录往往是 Web 服务启动时的目录。如果这个目录恰好是/,那么./bin/cat和/bin/cat就是同一个东西。第二,.是 Bash 的内建命令,等价于source。单独敲一个.,后面跟一个文件名,它会把那个文件当作 shell 脚本加载并执行。如果文件内容里写着命令,那这些命令就会在当前 shell 里跑起来。第三,.本身是可以参与 glob 匹配的字符。在 shell 的通配符规则里,.只会在一种情况下特殊:当你用*去匹配隐藏文件时,默认不匹配以.开头的文件名。但如果你把.写死在模式里,比如./???,那它就是一个普通字符,表示“当前目录下名字为 3 个字符的文件”。CTFshow Web55 这道题最常用的点号 payload 是这两种形态:. /???/??? /????和./???/??? /????两者只差一个空格,但含义完全不同。第一种是让.作为source内建命令去加载文件;第二种是把.作为路径前缀去执行一个相对路径命令。在实际解题中,很多人一上来就用./???/??? /????直接打通,就是因为 Web 服务的当前工作目录正好是根目录或者命令可执行。但这个点号真正厉害的地方,在于它出现在通配符模式里也不会触发正则过滤。正则只查字母数字,而.、?、/、空格都在白名单里。于是你可以放心大胆地把.加进 payload,让它参与路径拼接或者作为 source 命令。为了让你更直观地理解,我建议本地做一个最小实验。在终端里执行:cd / ./???/??? /????如果你机器上有/flag文件,这条命令会执行./bin/cat /flag。如果没有/flag,你也可以改成:./???/??? /etc/passwd同样能把/etc/passwd的内容打出来。这里你可以看到,整条命令里没有任何一个字母数字,完全靠.、?、/、空格把/bin/cat和/etc/passwd给“拼”了出来。所以点号在这道题里的本质作用是:它让路径可以从“当前目录”开始解析,同时又满足正则过滤条件,从而把真正的命令名和参数名都交给 shell 的通配符展开机制去完成。3. Web55 完整实操:三段式构造一条可用 payload理论说完了,下面进入可以直接抄作业的实操环节。CTFshow Web55 的目标一般就是读根目录下的 flag 文件,文件名可能是/flag,也可能是/flag.txt,甚至可能是/fllllllag这种变体。所以构造 payload 时要给文件名长度留点余量。3.1 第一步:确认当前工作目录虽然很多 writeup 里一上来就甩 payload,但实际做题时我强烈建议你先确认一下当前工作目录。方法很简单,找一个不含字母数字的命令来执行pwd:/???/????注意/???/????的长度是 1 + 3 + 1 + 4 = 9 个字符,正好能匹配/bin/pwd(/bin/pwd也是 9 个字符)。在靶机上请求:/?c=/???/????如果你看到类似/var/www/html的输出,说明当前目录不是根目录,那么./???/???这种相对路径可能不好使,你得优先考虑绝对路径通配/?/???这种写法。如果你看到输出是/,那就太好了,直接上相对路径 payload 也能通。3.2 第二步:先用系统命令摸清 flag 文件位置在没有字母数字的情况下,怎么列出根目录内容?我们可以让cat先靠边,先尝试执行ls。/bin/ls的路径长度是 7 个字符:/+bin+/+ls,也就是:/???/??在靶机上请求:/?c=/???/??如果/bin/ls是唯一匹配项,你应该能看到根目录下的文件列表。如果?匹配到了太多文件导致命令出错,可以试着把路径写得更精确一点,比如把bin用b??来匹配:/???/?? // 可能嫌太宽泛 /b??/??但b是字母,会被正则过滤。所以这类题的常规做法不是追求 100% 精确匹配,而是让匹配结果恰好落在你想执行的命令上。CTF 靶机的文件系统通常比较干净,/bin/ls和/bin/cat往往是 7 或 8 字符路径里最合适的候选。3.3 第三步:构造读取 flag 的最终 payload假设你已经确认 flag 文件就在根目录,名字是flag,长度 4 个字符。那最常见的 Web55 点号绕过 payload 就是:/?c=. /???/??? /????注意这里.和后面的/之间有一个空格。在 URL 里,空格要编码成%20或+。所以实际请求一般是:/?c=.+ /???/???+/????等一下,我上面说了.加空格是source语义,这里为什么能读 flag?因为在很多 CTF 环境里,/???/???匹配到的可能是一个可以加载的 shell 脚本,或者靶机环境本身被刻意配置成了“source 也能执行关键逻辑”。更常见的实际请求其实是下面这种不带空格的版本:/?c=./???/??? /????这个请求会先让 shell 看到./???/???,把它解析成一个可执行路径,然后传入参数/????。如果当前工作目录是/,等价于执行:./bin/cat /flag也就是:/bin/cat /flag如果你不确定 flag 文件的长度,可以多试几个参数模式:参数模式可能匹配的文件/????/flag、/tmp、/dev等 4 字符路径/?????/flag.txt、/hello等 5 字符路径/????????常见带下划线或数字的 flag 文件名实际做题时,我遇到过好几次 /flag 文件名被改成长名字的情况,比如/f1aggg、/flag_123。所以别只盯着/????一个模式,灵活调整?的个数非常关键。3.4 一个比较稳的备选 payload如果你发现./???/???因为当前目录的原因执行不了,或者 glob 匹配到了多个结果导致 shell 报错,可以用绝对路径通配来绕:/?/??? /????这里/?表示根目录下所有单字符目录,在大多数 Linux 容器里只有/?能匹配/bin吗?不一定,但通常单字符目录非常少,所以/???几乎必然落在/bin上,后面/???也能大概率命中cat。我自己在 CTF 靶机上测试时,这个 payload 的成功率比点号版本还要高,因为它不依赖当前工作目录。不过既然题目专门强调点号,那考试或练习时最好还是把点号的原理和写法都掌握,别只背一个绝对路径版本。4. 常见问题与排查技巧实录无字母数字 RCE 这类题,最让人头疼的往往不是“不会构造 payload”,而是“payload 看起来没问题但就是不出 flag”。这里我把实际踩过的坑整理成一份排查清单,你照着检查一遍,基本能解决 90% 的问题。4.1 glob 匹配到多个文件导致命令执行失败这是最常见的问题。比如/???/???在有些环境下不只是匹配/bin/cat,还可能匹配到/bin/cp、/bin/df、/bin/mv等等。当system()把字符串交给 shell,shell 会把所有匹配到的路径都展开成一个个独立的词,这就等于执行了/bin/cat /bin/cp /bin/df ... /flag,结果自然不是你想要的。排查方式很简单:先把 payload 里的执行动作改成ls类的命令,看你匹配到的文件到底有哪些。比如:/?c=/???/??如果这条命令能列出/bin/ls本身,说明 glob 的命中是可控的。如果???模式下命中的文件太多,就手动加长或缩短?的个数,让模式更贴近真实路径长度。另外,你可以在通配符里加字符集合来缩小范围,比如:/???/[!a-f]??但注意a-f里有字母,会被 PHP 正则过滤。所以这种方法在 Web55 里不适用,只能在理解原理时用。4.2 空格被 Web 服务器吃掉或被 URL 编码搞乱system()执行的是完整的命令行,命令和参数之间必须有空格。但 URL 里直接写空格是不规范的,容易被解析成%20或+。很多新手在 payload 里写:?c=. /???/??? /????实际请求发出去的时候,空格变成了%20,但某些中间件会把%20解码成空格,没问题。可有些平台会比较严格,需要你用+代替空格:?c=.+/???/???+/????这里有个很容易被忽略的细节:如果你的 payload 是./???/??? /????,中间那个空格同样要编码。很多 writeup 省略了这个细节,导致你直接复制粘贴时总感觉命令不对。我的习惯是:构造完 payload 后,先请求一遍不带执行结果的 URL,打开浏览器的开发者工具,看请求的真实 URL 是什么。如果空格变成了+,那服务器收到的就是合法空格;如果+变成了字面加号,那就要改用%20。4.3 点号本身被匹配成隐藏文件还有一种情况:你在当前目录用./?去匹配文件时,会匹配到隐藏文件吗?不会。在 shell 里,如果模式开头不是显式的.,?和*默认不匹配以.开头的文件名。但如果你写的是./???,前面的.是显式的路径前缀,后面的???就可以匹配到隐藏文件了。比如当前目录有.bashrc这种文件,./?????可能匹配到它。这在 Web55 里倒不是大问题,但如果你的 payload 变成了执行一个不想执行的文件,就可能产生意外输出。遇到这种情况,先用?数量去对齐目标文件,别盲目加?。4.4 system 与 shell 的环境差异很多 CTF 靶机为了减小体积,用的是 Alpine Linux,默认 shell 是 BusyBox 的 ash。BusyBox 的 glob 规则和 Bash 基本一致,但有些内置命令的路径和标准发行版不完全一样。比如/bin/cat在 Alpine 里可能是一个指向/bin/busybox的符号链接,但符号链接不影响 glob 匹配,/???/???照样可以命中。但如果你在题里发现/???/???匹配不到任何内容,那就要考虑靶机是不是把命令放在/usr/bin而不是/bin。这时候可以尝试匹配/usr/bin下的路径,比如:/???/???/???这是 1 + 3 + 1 + 3 + 1 + 3 = 12 个字符,能匹配/usr/bin/cat吗?不一定,具体要看/usr/bin里哪个命令是 3 个字符的。所以还是要先列出目录内容,摸清环境再动手。4.5 文件内容不是纯文本,命令执行了但看不到 flag如果你用了cat,但看到的是乱码或者二进制内容,说明你 cat 错了文件。比如/???/???匹配到了一个二进制命令本身,而不是 flag 文件。这时候你应该检查参数部分用的 glob 是不是太宽泛,匹配到了/dev或/tmp这样的目录。建议先用长度最短的/????去试探,如果还是不行,就用/???/??执行ls来确认根目录下到底有哪些文件。记住,先侦察,后读取,这两步别混在一起。5. 同类型题目的扩展思考:点号之外,还有哪些“无字母数字”套路Web55 只是无字母数字 RCE 的一个入口。理解了点号和通配符之后,你能顺着这条线继续摸到更多变种,我在这里帮你把这些套路放一起对比一下。5.1 通配符直接执行这是最基础的一种,也是 Web55 的核心。利用?、*去匹配命令路径,核心原则就是让正则无法拦截“展开前”的字符串:/???/??? /????它不依赖点号,但点号可以帮你把路径改成相对路径,增加一层灵活性。5.2 取反构造字符串PHP 里有一种非常经典的技巧,用按位取反运算符~配合非字母数字字符,在运行时构造出_GET、system这些字符串。比如:?c=$_=~%98%BB%8D%9A%88;$_();这种技巧用于eval类的场景,和system类场景是不同的赛道。Web55 如果换成eval($c),你就得走这条路,而不是走纯 shell 通配符路线。5.3 启动 shell 再喂命令在 Bash 里,$0会保存当前 shell 的名字。利用这个特性,可以通过重定向把后面的字符串输入给新启动的 shell 执行。常见的思路是:$0<<<...当然,...部分如果包含字母数字,还是要经过编码或构造。这种方法的优点是灵活,缺点是需要对 Bash 的 here-string 很熟。5.4 利用已有文件当脚本这就是点号source语义的延展。如果靶机的 Web 目录下有某个写着命令的脚本文件,你可以用.把它加载执行。但真实的 CTF 环境很少这么慷慨,所以这个方法更多是在“当前目录有可控 PHP 文件”或“上传点”的场景里使用。我个人的经验是:无字母数字 RCE 题,第一件事永远是搞清楚代码是system还是eval,第二件事是搞清楚 shell 环境,第三件事才是背 payload。这三步的顺序反了,你会陷入“背了一堆 payload 但全都不通”的困境。6. 最后再分享一个小技巧很多人在 Web55 里卡住,是因为以为“无字母数字”就是不能出现任何字母数字。其实你只要记住一句话:正则只能过滤你输入的原字符串,它管不到 shell 展开后的结果。所以构造 payload 的时候,你要做的就是让原字符串里全是符号,同时让这些符号经过?的展开,变成一个合法的命令。我个人在实际解题时,会先在本地用一个极简的 PHP 环境复现这道题。具体做法是起一个容器,放一个带system的 PHP 文件,然后手动请求./???/??? /???这种 payload,一步步观察 shell 的展开结果。这种方法能帮你快速理解哪些?长度是稳定的,哪些命令路径跟你想的不一样。最后提醒一句:CTF 和授权靶场的命令执行练习,目的是训练思路,请一定在合法合规的环境里进行。不要把这类绕过技巧用在未经授权的系统上,安全研究的前提永远是授权和边界意识。