资讯动态

命令注入漏洞详解:从原理分析到CTF实战拿下flag

发布时间:2026/10/17 14:30:15 来源:尧图企业网站定制
最近在攻防世界刷Web题又碰到了那道经典的 Command execution。这道题几乎每个入门Web安全的人都会遇到题如其名考的就是命令执行漏洞的利用。网上关于它的 wpwriteup解题报告不少但多数是“大佬版”——丢出一个payload几行字解决战斗。这次我打算写个“胎教版”意思就是保姆级还不够得细致到像胎教一样从“填什么”到“为什么填这个”从“flag在哪”到“怎么把flag读出来”每一步都拆开揉碎保证完全没接触过命令注入的新手照着敲也能把题解出来。而且这篇不只是解题。命令注入是 Web 安全里非常重要的一类漏洞把这道理搞懂等于掌握了这一类漏洞的完整利用链从原理到防御都能有个整体认知。适合谁看刚入门CTF的Web方向新手、准备网安竞赛的学生、想搞明白命令注入到底怎么回事的前后端开发人员都可以参考。1. 题目初探先把输入输出摸清楚1.1 环境长什么样攻防世界的题目一般会提供一个链接点开之后就是一个独立的靶机页面。这道题打开后特别朴素一个标题一个输入框一个提交按钮旁边提示让你填写 IP 地址。整个页面没有多余的功能也没有任何需要点开的二级菜单。这时候新手最容易犯的错是不知道点什么、不知道填什么甚至怀疑环境是不是坏了。我的建议是先做一次正常的功能测试不要一上来就注入。在输入框填127.0.0.1然后点提交。正常情况下会看到类似这样的回显PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq1 ttl64 time0.037 ms 64 bytes from 127.0.0.1: icmp_seq2 ttl64 time0.028 ms如果你看到的是 ping 的输出说明两件事一是靶机正常工作二是你填的内容确实被当作一个系统命令执行了。这不是一个“模拟的假界面”而是后端真的把输入拼进了命令里。这个观察是整个解题的起点。有的题目需要先看源代码可以右键查看网页源代码或者找一下页面上的 view_source 按钮。但这道题在攻防世界环境里一般直接就是可用的输入页面。页面越简单越说明漏洞藏在请求参数里。1.2 输入框背后发生了什么搞清楚靶机做了什么才知道从哪下手。这道题的 PHP 源码核心逻辑大致长这样?php $res FALSE; if (isset($_REQUEST[ip]) $_REQUEST[ip] ! ) { $ip $_REQUEST[ip]; $res shell_exec(ping -c 2 . $ip); } if ($res) { echo $res; } ?这一段代码信息量很大。逐行看isset($_REQUEST[ip])检查有没有通过 GET 或 POST 传一个叫ip的参数。这意味着你不一定非要在页面上点提交直接修改 URL 的查询参数也能传值。shell_exec(ping -c 2 . $ip)这是关键。shell_exec是 PHP 里用于执行系统命令的函数双引号内是命令字符串. $ip就是把用户输入直接追加在命令的尾巴上。echo $res把命令执行的结果原样输出到页面。所以如果你提交的是127.0.0.1后端实际执行的是ping -c 2 127.0.0.1如果你在输入框里提交的是127.0.0.1; ls后端就会执行ping -c 2 127.0.0.1; ls问题一下就清楚了程序想让你填 IP但它没有检查你填的到底是不是 IP。任何你输入的内容都会被当作命令的一部分交给系统去执行。1.3 这算不算一个“真实”的漏洞有人可能会想这会不会是题目故意造出来哄新手的现实里没人这么写恰恰相反这就是真实的漏洞类型——命令注入Command Injection英文就是标题里的 Command execution。虽然现在成熟一点的项目不会直接这么裸写但在老旧系统、嵌入式设备、运维脚本、各种自研系统的后台里用shell_exec、system、exec、passthru这类函数直接拼接用户输入的情况并不少见。OWASP开放式Web应用程序安全项目每年发布的 Web 安全风险清单里注入类漏洞一直排在前列。命令注入一旦被利用成功攻击者等于获得了执行任意系统命令的能力可能读取服务器上任何文件、修改数据甚至拿到整台服务器的控制权。所以这道题虽然只是入门难度背后连着的却是一条完整的真实攻击链路。这也是我反复建议初学者不要“拿完flag就走人”的原因——多花十分钟想明白原理比闷头刷一百道题都有用。2. 原理拆解命令注入是怎么发生的2.1 一条命令的“缝合”很多新手第一次看到127.0.0.1; ls能执行觉得不可思议我只填了个IP地址后面怎么还能跟着跑一条 ls这要从命令行解析机制说起。Linux 的 Shellbash、sh 等在解析一条命令时会按照一定的语法规则拆分整串字符。空格是参数分隔符而;、|、、||、这类符号是 Shell 预先定义好的命令分隔符或连接符。拿ping -c 2 127.0.0.1; ls来说Shell 会把它拆成两条命令ping -c 2 127.0.0.1—— 先执行ls—— 紧接着执行分号的意思就是前面的执行完不管成功还是失败都继续执行后面的。这个特性本来是方便用户一次性敲多条命令的语法糖结果被命令注入当成了突破口。因此命令注入的本质可以概括成一句话应用程序把用户的输入当作数据而 Shell 在底层却把它当作可执行语法来解析。程序以为自己塞进命令里的是一段不会变质的“馅料”Shell 却连同分隔符一起把它“蒸熟”了。2.2 各种分隔符的“脾气”既然提到分隔符就得把常用符号的行为差异讲清楚因为它们不只是长得不一样执行逻辑完全不同用错了可能连注入点都测不出来。符号含义执行逻辑典型用途;分号不管前一条成功与否都执行后一条无需关联回显时最稳妥|管道把左边命令的输出当作右边命令的输入让右边命令处理左边结果||或运算只有左边执行失败才执行右边当前面报错时触发备用命令与运算只有左边执行成功才执行右边当前面成功时接着做下一步后台符左边命令放入后台右边立即执行绕过阻塞或开启并行反引号先执行引号内命令再把输出替换进命令把命令结果当作参数$()命令替换与反引号类似内部命令先执行可嵌套同反引号但兼容性更好在CTF题里最常用的是;和|其次是。我个人的习惯是优先试;因为语义最简单前面执行前面的后面执行后面的两边都是独立命令回显也最容易判断。举个例子如果输入127.0.0.1 | ls因为|是管道符Shell 会执行ping -c 2 127.0.0.1然后把 ping 的输出当作输入传给ls。而ls正常情况下不会去读标准输入直接列出当前目录内容。最终页面上显示的可能只有ls的结果。这看起来也像注入成功但如果输入127.0.0.1; ls页面上会同时出现 ping 输出和 ls 输出更容易确认注入点是否成立。2.3 用一个生活化的类比我经常给朋友打一个比方。想象一个快递驿站驿站工作人员有一条工作流程凡是收到写着“请帮我跑腿”的纸条就拿去给外卖员照着念一遍。正常你写的是“帮我去 A 超市买一瓶酱油。”外卖员就去买了酱油。有一天你写的是“帮我去 A 超市买一瓶酱油顺便把 B 银行柜台里的现金搬走一半。”理论上你应该只被当作一个“要买酱油的顾客”但如果驿站工作人员把你纸条上的话一字不差全部念给外卖员那跑腿就变成了抢劫。程序里的情况一模一样一个 IP 输入框本来只想请你提供“ping 的目标地址”后台却把你输入的所有字符都无脑拼进命令里执行。分号、管道符这类字符就是你在纸条上悄悄塞进去的“附加指令”。这个类比虽然夸张但核心逻辑是准的漏洞的根源在于输入内容的边界没有被守住。3. 实操全流程从探测到拿到flag3.1 第一步确认注入点打开题目在输入框填正常 IP比如127.0.0.1看到 ping 的回显。这一步做完可以基本确定参数ip被拼进了系统命令。接下来验证注入点。第一发最常用的 payload 是127.0.0.1; ls提交后如果页面同时出现 ping 的回显和当前目录的文件列表就说明分号注入成功了。这里有个新手容易忽略的细节要区分“命令执行了”和“命令输出被显示了”是两回事。第4部分专门讲没有回显时怎么排查这里先不展开。如果;之后没有反应可以换|试试或者加上 URL 编码再试。最核心的思路是先找到一个能让附加命令输出出现的分隔符后续一切才有基础。3.2 第二步查看敏感文件缩小flag范围靶机环境里flag 文件到底放在哪里攻防世界的靶机一般会有一个或多个 flag 文件常见命名是flag、flag.txt位置可能在根目录、当前目录或某个固定路径。不要靠猜直接用命令找。我第一次做这道题时先执行ls看了当前目录然后直接试了全局搜索127.0.0.1; find / -name flag*这个命令在题目环境里往往是最快的。有一点要提醒find /会遍历整个文件系统输出可能比较大部分目录会因为权限不足报错。如果页面回显被截断或输出太长可以先限定几个常见目录搜索。比如127.0.0.1; find / -name flag* 2/dev/null把错误输出重定向到/dev/null只保留有效结果页面会干净很多。这是我实际解题时比较偏爱的姿势。通常在输出里会看到类似/flag.txt或者/var/www/html/flag.txt这样的路径。看到路径之后下一步就顺理成章了。3.3 第三步读取flag文件拿到路径后用cat读取文件内容127.0.0.1; cat /flag.txt如果 flag 在别的目录把路径替换成实际的即可。例如127.0.0.1; cat /var/www/html/flag.txt到这里 flag 就到手了。如果只是为了提交答案把页面上的 flag 字符串复制过去就行。有的新手会问为什么不能直接cat /flag.txt非要先find两步走如果你一开始就猜对了路径当然可以。但实战中你并不知道 flag 放在哪所以“先 find 找路径再 cat 读取”是更稳妥的通用套路。另外有的题目 flag 藏在隐藏文件里比如.flag这时find / -name flag*未必能覆盖到需要加ls -la或换用find / -name .*flag*来排查隐藏文件。3.4 第四步完整的利用链梳理这道题的完整利用链路可以归纳成四步功能探测输入127.0.0.1确认这是一个真实执行 ping 命令的功能点。注入验证输入127.0.0.1; ls确认分隔符被接受、附加命令能执行。信息收集用find / -name flag*全局搜索 flag 文件位置。文件读取用cat读取 flag 内容并提交。这是命令注入类 CTF 题最标准的“四步走”。后面做到更复杂的题目时这四步里的每一步都可能被加障碍过滤了符号、过滤了关键字、无回显、需要编码绕过等但整体思路骨架是不变的。3.5 一次真实的操作复盘为了看着更接近实战我复盘一下第一次实际做这道题的完整过程。我打开题目页面先正常填127.0.0.1页面返回了 ping 结果。这时候我关注了一下 URL发现请求变得类似?ip127.0.0.1参数名就是ip。接着注入127.0.0.1; ls页面列出了当前目录看到了index.php等文件。然后用127.0.0.1; find / -name flag* 2/dev/null输出里出现了/flag.txt。最后执行127.0.0.1; cat /flag.txtflag 直接躺在页面上。全程没遇到过滤是一次非常顺畅的入门体验。如果遇到带过滤的变种题目那就进入下一部分的绕过排查环节了。4. 常见被卡住的点与排查思路4.1 现象一命令执行了页面却没有回显有同学做完; ls之后页面只有 ping 的输出ls 的结果消失得无影无踪。这是最常见的困惑。可能原因有几个。第一;本身被过滤了相当于“分隔符”根本没生效。第二命令实际执行了但输出没被回显函数捕获。比如后端用的是system()而不是shell_exec()回显行为会有差异。第三你用的是|管道符的语义导致左边命令的输出被“吃掉”而不是并列显示。排查办法换一个分隔符;不行就换再不行换|。用sleep类命令做时间盲注验证。比如提交127.0.0.1; sleep 3如果页面明显卡了3秒才返回说明命令确实执行了只是回显有问题。在 payload 里加一个唯一标记比如127.0.0.1; echo abcdefg看页面上有没有abcdefg出现。这个标记法是我最常用的“探测回显”手段比看目录列表更直白。4.2 现象二分号、管道符全被过滤了有的题目会做一点过滤比如把;、|、直接替换成空。怎么判断是不是被过滤了提交带这些符号的 payload页面返回内容里符号消失或者命令报错信息能看出来。绕过思路有几种用 URL 编码。部分后端过滤的是“攻击特征字符串”但没有处理 URL 解码后的变体。把分隔符编码后再提交比如%0a代表换行%3B代表分号。有些环境里换行符也能起到命令分隔作用。用$()或反引号执行内联命令。比如127.0.0.1$(ls)在某些过滤了显式分隔符的场景下命令替换仍可能被执行。用${IFS}代替空格。空格是高频过滤对象而${IFS}是 Shell 内置的字段分隔符变量默认就是空白字符很多题目不会专门过滤它。典型 payload 长这样127.0.0.1;cat${IFS}/flag.txt。绕过关键字过滤。比如cat被过滤可以试c\at、cat、反引号包裹或者用less、more、head、tail等替代命令。需要说明的是攻防世界这道基础题通常没有这么多花活过滤很简单。但这些绕过思路是通用的后面遇到同类题目能直接复用。4.3 现象三flag文件找到了但cat不出来有时候文件路径找到了比如就是/flag.txt但执行cat /flag.txt报权限不足或者页面直接报错。这种情况在偶发环境里会出现常见原因是 Web 服务运行用户对文件没有读权限或者 flag 文件名里带了随机字符串搜索出来的结果不全。解决思路用ls -la查看文件权限确认是否真的没权限。换读取工具head、tail、more、less都能读文件内容。如果权限不足尝试搜索其他可读的 flag 副本有些靶机会放多份 flag 文件。用通配符匹配文件名比如知道是/fl*可以执行cat /fl*碰碰运气。4.4 现象四空格被过滤这个在 4.2 里提前提过。如果是空格被过滤没法正常写cat /flag.txt怎么办首先要理解命令本身需要参数分隔。空格没了但 Shell 还认${IFS}。所以可以写成127.0.0.1;cat${IFS}/flag.txt这个 payload 就是把空格换成了${IFS}。有时候${IFS}也会被过滤可以试$IFS$9。$9是 Shell 的第九个位置参数默认为空与$IFS拼在一起后既能绕过对${IFS}字面量的过滤又能保留分隔符效果。这属于CTF里比较经典的“空格绕过”知识点。4.5 一个省力的排查顺序综合这些现象我给新手一个相对通用的排查顺序先确认靶机环境正常填正常 IP看看是否有 ping 回显。再确认分隔符可用提交127.0.0.1; echo test。出现test说明分号通了。分号不通依次试127.0.0.1|echo test、127.0.0.1echo test。如果符号全被过滤尝试 URL 编码变体、$()、反引号、${IFS}等姿势。确认命令可执行后先pwd看当前目录再ls看有什么文件最后find找 flag。读取时注意权限、隐藏文件、文件类型灵活更换工具。这个顺序不一定每次最优但能帮你在卡住的时候快速缩小排查范围避免原地打转。5. 拿到flag之后从攻击端切到防御端5.1 回头看这段代码犯了哪些错把攻击思路理完再回头审视漏洞代码会更清晰。问题代码的核心就两个点$ip $_REQUEST[ip]; $res shell_exec(ping -c 2 . $ip);这两个地方暴露了漏洞的核心矛盾输入没有校验。用户传了什么程序就信什么。程序假设了“用户一定会填IP地址”但现实里不该有这种假设。拼接没有转义。即使输入做了某种程度的校验只要“拼接字符串进入系统命令”这个动作存在安全就是不可控的。所以修复的本质不是为了“过滤掉几个危险字符”而是要从根上改变“把用户输入放进命令里”这种设计。黑名单永远堵不完。5.2 给开发的修复清单我把自己平时给开发同学提的修复建议整理一下层面做法说明首选方案不用系统命令能用内置库实现的功能坚决不调 shell 命令参数白名单校验输入格式IP 就按 IP 的正则校验不匹配直接拒绝安全转义escapeshellarg()把输入当参数而非命令片段处理PHP 内置函数禁用危险函数disable_functions部署层面禁用shell_exec、exec、system等最小权限限制运行用户Web 服务运行用户不授予敏感文件读写权限常规防护WAF 与日志至少做到攻击能发现、能追溯如果业务中一定要执行命令最安全的姿势是把用户输入严格限制在特定命令的可选参数范围内并采用白名单方式过滤。凡是拿不准的直接拒绝。安全宁可少一点便利也不能留一个任意命令执行的洞。5.3 对新手来说这道题的意义不止是flag我见过不少新人做完题目拿到 flag 就划走下一题。如果只是复制了一个 payload其实是损失了绝大部分价值。做完这道题后最好能自己动手改一改代码做几个小实验。比如在后端加一层str_replace过滤试试自己能不能绕过。把命令执行函数换成system和shell_exec感受一下回显差异。试着写一个安全版本比如用正则只允许 IP 地址再测一测还有没有注入点。这样一套练习下来对命令注入的理解会远超那些只会“抄 payload 拿 flag”的人。我一直觉得安全入门阶段比刷题数量更重要的是把一个漏洞吃透。这道 Command execution 就是一个很好的起点——简单但五脏俱全。最后分享一点个人习惯每做一道 CTF 题我都会顺手把能复用、可复现的 payload 模板记下来。命令注入的初探三板斧——; ls、 whoami、| cat /flag.txt看起来简单可到比赛手忙脚乱的时候这些肌肉记忆能帮你快速确认题目类型。如果你也是刚接触这块建议把攻防世界上这类 Web 基础题集中起来刷它们之间很多思路是相通的。做错一道不要紧重要的是总结卡在哪了、为什么卡。这道胎教版 wp 就到这里希望你看完能自己动手把 flag 跑出来——只有亲手拿到过 flag才算真正入门了命令注入。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑