SSRF攻击中Gopher协议利用:从原理到实战的完整指南
1. 项目概述当SSRF遇上Gopher一个被遗忘的“瑞士军刀”如果你在CTFHub的Web技能树里刷过SSRF服务器端请求伪造相关的题目或者在实际渗透测试中遇到过SSRF漏洞那你大概率会接触到各种过滤和限制。常见的绕过手段比如利用file://、dict://或者通过302跳转、DNS重绑定可能已经成了你的常规操作。但今天我想聊一个更“古典”、也更强大的协议——Gopher。在CTFHub的SSRF题目里它往往是通往最终Flag的那把关键钥匙而在真实的内网渗透场景中它更像是一把被遗忘在工具箱角落的“瑞士军刀”一旦用对地方威力惊人。Gopher协议诞生于1991年比HTTP还要古老设计初衷是作为一个分布式的文档检索系统。它的语法简单直接一个请求包几乎可以封装任何你想发送的TCP流量。正是这种“简单粗暴”的特性让它在SSRF攻击中具备了无与伦比的灵活性我们可以通过一个HTTP请求让存在SSRF漏洞的服务器代替我们去访问内网中任意TCP端口的任意服务并发送我们精心构造的协议数据包。无论是攻击内网的Redis未授权访问、Memcached缓存数据库还是向MySQL、FTP甚至SMTP服务发送恶意指令Gopher协议都能胜任。很多人觉得它“被遗忘”是因为现代浏览器早已不再支持日常开发中也几乎绝迹。但恰恰是这种“遗忘”使得很多SSRF防御方案会漏掉对它的严格过滤。在CTFHub的题目里出题人常常会设置诸如“仅允许访问本地HTTP/HTTPS服务”之类的限制这时Gopher就成了绕过这些限制、直击后端脆弱服务的绝佳跳板。接下来我就结合实战经验从协议原理拆解到CTFHub典型题目复现再延伸到内网攻击的实用场景带你重新认识这把“老枪”的威力。2. Gopher协议原理深度拆解为什么它能成为SSRF的“万能钥匙”要玩转Gopher不能只停留在“复制粘贴Payload”的层面必须理解其底层工作原理。只有这样你才能在各种复杂的过滤规则下灵活地构造出有效的攻击载荷。2.1 Gopher协议的基本语法与请求结构Gopher协议本质上是一个面向行的文本协议。客户端在我们的场景中就是存在SSRF漏洞的服务器向Gopher服务器即我们想要攻击的内网服务发起一个TCP连接然后发送一行请求随后服务器返回响应并关闭连接。一个最简单的Gopher URL格式如下gopher://host:port/gopher-path其中gopher-path部分最为关键它决定了发送给目标服务的具体数据。其核心语法是typeselectortab%09hosttab%09porttype一个字符表示资源类型如1表示目录列表0表示文本文件。但在SSRF攻击中我们通常使用1或直接省略因为重点是后面构造的原始数据。selector选择器可以理解为要发送的数据流。在攻击中这里就是我们精心构造的原始协议数据如HTTP请求、Redis命令等。tab制表符URL编码为%09。host和port通常可以留空或填%09因为目标主机和端口已经在URL的主机部分指定了。关键在于Gopher协议会将selector部分的内容原封不动地通过TCP连接发送到目标服务器的指定端口。这意味着我们可以在selector中嵌入任何基于TCP的协议数据包比如一个完整的HTTP POST请求、一条Redis的SET命令或者一个SMTP的EHLO握手。2.2 Gopher协议在SSRF中的核心优势协议封装与数据注入为什么Gopher在SSRF中如此强大对比其他协议就能一目了然协议功能限制在SSRF中的典型用途http:///https://只能发送HTTP/HTTPS请求。如果后端过滤了非HTTP端口或特定路径容易被拦截。探测Web服务攻击内网Web应用。file://只能读取本地文件。无法发起网络连接。读取服务器本地文件如/etc/passwd。dict://通常只用于连接字典服务端口2628功能单一且很多环境已禁用。探测端口开放情况获取有限信息。gopher://可以封装任意TCP流量。只要目标端口开放就能发送任何协议的数据包。攻击Redis、MySQL、Memcached、FTP、SMTP等几乎所有TCP服务。它的核心优势在于“协议封装”能力。SSRF漏洞的本质是“服务器代替我们发起网络请求”。大多数防御思路是限制目标URL的协议和端口。而Gopher协议就像一个“万能信封”我们可以把攻击Redis的指令“装进”这个信封里让服务器把这个“信封”送到内网的Redis服务6379端口。对于防御方来说它只看到服务器发起了一个到gopher://internal-host:6379的连接而这个连接本身可能是被允许的如果过滤不严但信封里装的东西才是真正的攻击载荷。实操心得一注意CRLF换行符在构造Gopher的selector时你必须手动控制每一行数据的结尾。大多数网络协议使用\r\nCRLF作为行结束符。在URL中\r的编码是%0d\n的编码是%0a。一个常见的错误是只用了%0a而忘了%0d导致目标服务无法正确解析命令。我的习惯是在构造任何协议数据时行尾一律使用%0d%0a这是最稳妥的。2.3 Gopher协议的数据编码与长度问题由于Gopher URL是放在HTTP请求参数中的比如?urlgopher://...所以必须进行URL编码。但这里有一个巨坑不是所有字符都需要编码而且双重编码会导致Payload失效。必须编码的字符%、#、、?、空格等URL保留字符和特殊字符必须编码。制表符\t编码为%09回车换行\r\n编码为%0d%0a。无需编码的字符协议数据本身的可打印字符如字母、数字通常不需要编码。例如Redis命令SET key value中的单词可以直接书写。警惕双重编码如果你用的工具或脚本自动对参数进行URL编码而你又在Payload里手动编码了%0d等字符那么最终可能会变成%250d%被编码成了%25这会导致Payload解析失败。最佳实践是在最终放入参数前将整个Gopher URL视为一个整体检查其中是否已经有%xx形式的编码确保它们不会被二次编码。另一个问题是长度。GET请求的URL长度有限制通常几KB而复杂的攻击载荷比如一个长的HTTP POST请求体可能会超限。这时有两个思路思路A改用POST传参。如果漏洞点支持通过POST body传递URL参数那么长度限制会宽松很多。思路B缩短Payload。对攻击载荷进行简化例如利用Redis的管道功能将多条命令合并为一条或者对HTTP请求进行最小化裁剪。3. CTFHub SSRF Gopher题实战复盘从解题到理解攻击链理论说再多不如动手一试。我们以CTFHub技能树中一道经典的SSRF题目为例通常命名为“POST请求”或类似来完整走一遍利用Gopher协议攻击内网服务的流程。这道题的典型场景是一个Web页面提供了一个输入框让你提交一个URL服务器会访问这个URL并返回内容但做了限制比如只允许访问127.0.0.1的80端口。目标是利用这个SSRF攻击本机127.0.0.1另一个端口上的服务获取Flag。3.1 环境探测与目标分析第一步永远是信息收集。假设题目页面是http://challenge-ip:port/有一个参数url。基础探测尝试urlhttp://127.0.0.1看是否返回本机Web服务的信息。再尝试urlfile:///etc/passwd看是否允许file协议。很多题目会禁用file和dict。端口扫描利用SSRF进行内网端口扫描。由于题目可能限制协议我们可以用http://127.0.0.1:端口的方式探测。常见的需要关注的端口有6379(Redis),11211(Memcached),3306(MySQL),21(FTP),25(SMTP)等。在CTF中为了降低难度后端通常运行着一个有漏洞的Redis服务。协议试探尝试urlgopher://127.0.0.1:6379/__是一个简单的选择器。如果页面返回的响应时间明显变长或者返回的报错信息与连接HTTP服务时不同例如连接被拒绝 vs 连接超时这很可能意味着6379端口是开放的并且服务器尝试去连接了。注意事项盲打技巧在实际CTF或渗透测试中SSRF可能是“盲”的即没有回显。这时需要借助DNSLog或HTTPLog平台。可以构造一个Gopher Payload让目标服务如Redis向我们的DNSLog域名发起连接。例如在Redis中执行CONFIG SET dir “http://your-dnslog.cn”这本身不是合法命令但原理是让Redis尝试连接一个域名。如果我们的DNSLog平台收到解析请求就证明Gopher协议生效且命令执行了。CTFHub题目通常有回显这简化了过程。3.2 构造攻击Redis的Gopher Payload假设我们确认了127.0.0.1:6379运行着Redis且未授权访问。我们的目标是利用Redis的config set和save功能写一个Webshell或者覆盖计划任务最终获取Flag。在CTF中常见的是写一个公钥到/root/.ssh/authorized_keys或者写一个Webshell到Web目录。下面是一个经典的、用于写入SSH公钥的Gopher Payload构造过程1. 准备原始Redis命令Redis使用纯文本协议每一条命令以\r\n分隔。我们要执行的命令序列是flushall config set dir /root/.ssh config set dbfilename authorized_keys set x \n\n你的公钥内容\n\n save为了将其放入Gopher URL需要将每个命令转换为Redis协议格式。更简单的方法是直接使用换行符分隔的原始命令但为了绝对可靠我们可以使用更底层的格式。不过在大多数情况下Redis服务能兼容简单的多行命令输入。2. 构建Gopher选择器Selector将上述命令用\r\n连接起来并进行URL编码。注意第一行前面需要加一个_或任何字符作为Gopher的类型指示后面跟一个%09tab。_%0d%0aflushall%0d%0aconfig set dir /root/.ssh%0d%0aconfig set dbfilename authorized_keys%0d%0aset x \n\nssh-rsa AAAAB3NzaC1yc2E...你的公钥...\n\n%0d%0asave%0d%0a注意上面的公钥内容里的换行符\n也需要编码为%0a。为了清晰这里用引号括起来了实际构造时需要将整个字符串正确编码。3. 组装完整的Gopher URLgopher://127.0.0.1:6379/_%0d%0aflushall%0d%0aconfig set dir /root/.ssh%0d%0aconfig set dbfilename authorized_keys%0d%0aset x \n\nssh-rsa AAAAB3NzaC1yc2E...\n\n%0d%0asave%0d%0a将这个URL作为参数提交给存在SSRF的接口http://challenge-ip:port/?urlgopher://127.0.0.1:6379/_%0d%0a...如果执行成功服务器的Redis就会将你的公钥写入/root/.ssh/authorized_keys。之后你就可以用对应的私钥直接SSH登录到127.0.0.1虽然题目环境通常不允许外部SSH但这个思路是通用的。在CTF中Flag可能直接藏在Redis的数据里或者写入Webshell后访问特定URL获得。3.3 针对HTTP POST请求的Gopher构造CTFHub另一类题目是让你利用SSRF去访问一个本地的Web应用这个应用需要POST请求才能获取Flag。这时我们需要用Gopher协议构造一个完整的HTTP/1.1 POST请求。假设我们需要向http://127.0.0.1:80/api/flag发送POST数据keysecret。1. 构造原始HTTP请求POST /api/flag HTTP/1.1 Host: 127.0.0.1 Content-Type: application/x-www-form-urlencoded Content-Length: 12 keysecret注意每行末尾必须有\r\n头部和Body之间有一个空行也是\r\n。2. 编码并嵌入Gopher将整个HTTP请求字符串包括换行符进行URL编码。然后在Gopher的选择器部分我们需要在HTTP请求前面加上一个“伪路径”。通常Gopher客户端会发送选择器后跟CRLF。为了模拟直接发送TCP数据我们可以在最前面加一个_然后直接跟上编码后的HTTP请求。gopher://127.0.0.1:80/_POST%20/api/flag%20HTTP/1.1%0d%0aHost:%20127.0.0.1%0d%0aContent-Type:%20application/x-www-form-urlencoded%0d%0aContent-Length:%2012%0d%0a%0d%0akeysecret关键点POST /api/flag HTTP/1.1这部分空格被编码为%20Host:后面的空格也被编码为%20。整个请求被压缩成一行放在Gopher URL里。实操心得二使用工具辅助生成手动构造复杂的Gopher Payload极易出错尤其是计算Content-Length。强烈推荐使用工具比如用Python的urllib.parse.quote进行编码或者使用现成的SSRF工具包如Gopherus。在实战中我通常会写一个小脚本import urllib.parse http_req POST /api/flag HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 12\r\n\r\nkeysecret gopher_payload gopher://127.0.0.1:80/_ urllib.parse.quote(http_req) print(gopher_payload)这样可以确保编码准确无误。4. 超越CTFGopher协议在内网渗透中的实战应用CTF题目是理想化的沙箱而真实的内网渗透环境要复杂得多。Gopher协议的价值在这里更加凸显因为它能攻击的服务类型远超Redis。4.1 攻击Memcached实现分布式拒绝服务DDoSMemcached默认监听11211端口并且通常没有认证。它支持一个非常简单的文本协议其中set命令用于存储数据。关键在于Memcached支持存储非常大的value。攻击者可以利用SSRF通过Gopher协议向一个开放的Memcached服务器发送set命令注入一个巨大的value比如几百KB并将该key的过期时间设置得很短。更危险的是Memcached支持UDP协议并且其UDP响应包可能远大于请求包放大攻击。虽然Gopher走TCP但通过SSRF操控内网的Memcached服务器向互联网发起UDP反射攻击是内网横向移动后一种可能的DDoS利用方式。在渗透测试中发现未授权的Memcached应立即报告因为其风险极高。构造一个Memcached set命令的Gopher Payload示例set mykey 0 60 1024\r\n[1024字节的随机数据]\r\n编码后gopher://internal-memcached:11211/_set%20mykey%200%2060%201024%0d%0a[此处是编码后的1024字节数据]%0d%0a4.2 攻击FTP协议实现文件操作与跳转如果内网存在可匿名访问或弱口令的FTP服务器Gopher协议可以发送FTP命令。这可以用于上传Webshell通过STOR命令将恶意脚本上传到Web目录。读取敏感文件通过RETR命令下载配置文件。FTP被动模式PASV滥用这更高级一些。可以尝试让FTP服务器以PASV模式连接回攻击者控制的服务器从而实现“反弹”效果绕过某些网络限制。FTP协议交互性较强需要处理欢迎消息和状态码。利用Gopher攻击FTP通常需要构造一个包含用户名、密码、操作命令的完整会话。这比Redis复杂但原理相通用\r\n分隔命令并正确处理服务器的响应在盲SSRF中这很困难。4.3 攻击MySQL、SMTP等其他TCP服务MySQL可以尝试使用Gopher发送MySQL握手包和查询语句。如果MySQL配置了弱密码如root/空密码甚至可能执行SELECT ... INTO OUTFILE来写文件。但这需要精确构造MySQL的二进制协议包难度很大通常在有工具辅助的情况下尝试。SMTP可以发送伪造的邮件。通过Gopher向内部的SMTP服务器发送EHLO、MAIL FROM、RCPT TO、DATA等命令可以冒充内部用户发送钓鱼邮件用于社会工程学攻击。注意事项协议交互与盲打攻击MySQL、FTP等需要多次交互的协议时Gopher的“单次请求-响应”模式成了短板。因为Gopher连接在发送完选择器数据后就会读取响应并关闭无法维持会话状态。对于这类协议通常只能尝试一些无需多次握手认证的漏洞比如某些特定版本的未授权漏洞或者利用协议本身的特性如MySQL的LOAD DATA LOCAL INFILE进行有限攻击。在盲SSRF环境下攻击这类服务成功率较低优先选择Redis、Memcached这种简单文本协议的服务。5. 防御视角如何有效检测与防护Gopher协议滥用作为一名安全从业者不仅要懂攻击更要懂防御。从防御者的角度看如何防止SSRF漏洞被Gopher协议利用呢5.1 黑名单过滤的局限性很多初级防御方案是采用黑名单过滤gopher://、file://等协议头。这种方法存在明显缺陷大小写绕过GOPHER://、Gopher://。URL编码绕过%67%6f%70%68%65%72://即gopher的URL编码。畸形协议格式某些库的URL解析器可能允许gopher:或gopher:后面不跟双斜杠//。利用跳转如果服务器会跟随302跳转攻击者可以先提供一个合法的HTTP URL该URL返回一个302状态码Location头指向gopher://内网地址。很多HTTP库会跟随跳转从而绕过对初始URL的协议检查。5.2 多层次综合防御策略有效的防御需要从多个层面构建1. 应用层代码层面白名单校验严格限制URL的协议、域名和端口。只允许访问业务必需的、已知安全的HTTP/HTTPS端点。这是最有效的方法。禁用URL跟随跳转在处理用户提供的URL时设置HTTP客户端禁止自动跟随3xx重定向。使用安全的URL解析库使用能够正确解析和规范化URL的库并在解析后对协议、主机名、IP地址进行严格检查。注意识别和过滤内网IP段如10.0.0.0/8172.16.0.0/12192.168.0.0/16127.0.0.0/8。对返回内容进行安全检查如果功能是获取URL内容并展示需要对返回的数据进行严格的类型检查、内容过滤和大小限制防止数据泄露和XSS等二次攻击。2. 网络层出口过滤部署服务器或容器级别的网络策略限制应用服务器向外发起连接的权限。只允许其访问特定的、业务需要的外部IP和端口。例如一个普通的Web应用服务器通常没有理由主动连接Redis的6379端口或Memcached的11211端口。微服务间认证内网服务不应默认信任来自内网的任何请求。重要的数据库、缓存服务应配置强密码认证并限制访问源IP。3. 服务配置层最小化暴露非必要的服务如Redis、Memcached不应绑定在0.0.0.0上应只监听127.0.0.1或内网特定IP。启用认证为Redis、Memcached等服务设置强密码。定期更新与扫描及时更新服务版本修复已知漏洞。定期进行内网端口扫描和安全评估发现并关闭不必要的开放端口。6. 常见问题排查与高级绕过技巧实录在实际利用Gopher进行SSRF攻击时你肯定会遇到各种“坑”。这里记录一些我踩过的坑和对应的解决方案。6.1 Payload构造失败问题排查表现象可能原因排查步骤与解决方案提交Gopher URL后页面返回“协议不支持”或“URL不合法”。1. 后端直接黑名单过滤了gopher://字符串。2. URL解析器不支持Gopher协议。1. 尝试协议名大小写变异、双写、插入特殊字符如gopher://。2. 尝试使用URL全编码%67%6f%70%68%65%72%3a%2f%2f...。3. 尝试利用302跳转绕过如果允许跳转。连接超时无任何返回。1. 目标端口未开放。2. 防火墙阻断了连接。3. Gopher Payload格式错误目标服务未响应。1. 先用http://探测目标端口是否存活如果允许。2. 检查Gopher URL中的主机和端口是否正确。3.重点检查CRLF编码确保命令末尾是%0d%0a而不是%0a或\n。用一个最简单的Payload测试如Redis的INFO命令。返回奇怪的错误如“Invalid Protocol”、“Bad Request”。1. Gopher选择器部分的数据不符合目标服务的协议规范。2. 存在双重URL编码。1. 使用网络抓包工具如nc本地模拟目标服务接收并查看Gopher客户端实际发送的数据进行调试。2. 检查最终提交的URL确保%0d、%0a等编码没有被二次编码成%250d、%250a。攻击Redis时返回-ERR unknown command。Redis命令格式错误或协议不匹配。1. 确保命令是全部大写Redis命令不区分大小写但惯例大写。2. 尝试使用更标准的Redis协议格式而不仅仅是命令参数的简单拼接。可以使用echo -e配合nc本地测试命令是否被正确解析。6.2 针对特殊过滤的绕过技巧过滤空格如果WAF或过滤代码拦截了空格可以用%09TAB、%0a换行、%0d回车或者号来替代命令参数间的空格。例如Redis的config set dir /root/.ssh可以写成config%09set%09dir%09/root/.ssh。过滤%号如果无法进行URL编码那么Gopher攻击几乎无法进行因为CRLF依赖%0d%0a。这时可以尝试其他协议或者寻找不需要换行的攻击方式极少。限制端口如果只允许访问80、443等端口可以尝试端口重定向在目标内网机器上如果存在权限可以用iptables或socat将其他端口如6379流量重定向到80端口。socat TCP-LISTEN:80,fork TCP:localhost:6379。这样访问gopher://target:80的实际流量会被转发到Redis。利用Web服务代理如果内网有一个存在SSRF的Web服务二次SSRF可以先让第一个SSRF访问这个Web服务再由这个Web服务去攻击Redis。这需要目标网络环境非常配合。DNS重绑定配合Gopher如果防御方通过检查IP地址来阻止访问内网可以使用DNS重绑定技术。提供一个域名该域名第一次解析返回一个外网合法IP通过检查第二次解析返回一个内网IP如127.0.0.1。服务器在第一次DNS解析时通过了校验但在实际发起Gopher连接时可能使用的是缓存的第二次解析结果内网IP。这需要服务器端的DNS缓存策略存在时间差漏洞。Gopher协议在SSRF攻击中展现出的灵活性和强大功能使其在CTF竞赛和高级渗透测试中始终占有一席之地。理解它不仅是为了解题得分更是为了构建更全面的网络攻防知识体系。从防御角度看它再次提醒我们安全是一个整体任何一个细微的漏洞SSRF搭配一个被遗忘的古老协议Gopher都可能成为撕开内网防线的突破口。真正的安全需要代码层、网络层、配置层协同防御缺一不可。

相关新闻