首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Gopher协议在SSRF中的深度利用:从任意TCP请求构造到安全防御
📅 2026/10/21 23:56:23
✍️ 爱科研究院
👁 阅读 3,247
1. 从一道CTF题窥见的协议盲区先说个有意思的事。CTFHUB上有一道经典的SSRF题目常规解法无非是探测内网、读取文件、打Redis未授权大家玩得挺熟。但某次我卡在最后一关内网端口扫出来了服务也知道是什么可常规的dict://、file://、http://全都用不上差点就放弃。后来群里有人提了一嘴“试试Gopher协议”我才意识到自己对这个老协议的理解一直停留在“能用但没用过”的层面。Gopher协议说白了一个上古时代的互联网信息访问协议诞生得比HTTP还早现在基本没什么人在用。正因为它“古老”、“冷门”很多安全从业者对它的了解仅限于“能发GET请求”或者“能打内网Redis”。但真正深挖下去你会发现Gopher之于SSRF就像一把万能钥匙——你可以用它构造任意TCP数据包跟内网任意服务完成一次完整的交互。这个能力在常规协议里几乎是独一份的。这篇文章就从一个实际的CTF场景出发把Gopher协议为什么能在SSRF里如鱼得水、怎么构造payload、构造过程中有哪些坑、以及它背后暴露出来的防御盲区原原本本讲清楚。不管你是刚入门CTF的新手还是做攻防演练的老手这篇文章应该都能给你一些新的思路。2. 从一个“老古董”协议聊起Gopher为什么这么特殊2.1 一个被时代遗弃但保留完整能力的协议Gopher诞生于1991年比HTTP早了整整五年。它设计之初就是为了在互联网上做分层菜单式的信息检索用户通过Gopher客户端进入一个菜单界面逐级点击就能浏览文本内容。后来HTTP凭借更丰富的表现力迅速取代了它Gopher逐渐退出了历史舞台。但协议退出了能力并没有消失。Gopher协议的核心定义极其简单一个TCP连接、一个选择器字符串、加一个可选的请求参数。它没有HTTP那么复杂的请求头解析没有TLS握手没有Cookie甚至没有请求体。它本质上就是“往TCP连接里塞一段原始数据”这段数据是什么格式完全由你说了算。这个特性在SSRF里被无限放大。因为你所控制的Gopher URL最终会被目标服务器解析成一次真正的TCP连接并发出去而TCP连接里的内容就是你URL里编码好的任意字节。2.2 从协议格式看“任意请求构造”的原理Gopher协议的URL标准格式大致如下gopher://host:port/gophertypeselector分解一下gopher://协议标识host目标主机地址port目标端口gophertype一个字符表示资源类型比如1表示目录、0表示文本文件selector实际发送到服务端的数据关键点在这个selector。它会被客户端按原始字节发送到目标TCP端口中间不会做任何二次解析。这就意味着如果你把一段精心构造的HTTP请求或者Redis命令塞进selector部分再用URL编码转义掉特殊字符目标服务器收到的就是一份完全“合法”的原始请求。举个最直观的例子正常情况下你要打一个未授权的Redis服务要么直接连上去发命令要么通过SSRF的dict协议逐字下发非常费劲。但如果用Gopher你可以把一整串Redis命令拼好一次性塞进TCP连接服务端按流式解析你相当于拥有了一个“内网TCP会话操控器”。2.3 为什么偏偏是Gopher而不是其他协议我们常用SSRF能调用的协议就那么几种file://读文件、dict://发简单命令、http://发HTTP请求、ftp://列目录/上传下载。逐一对比你就知道Gopher的位置在哪协议能控制的内容限制适用于file://无只能读文件本地文件读取dict://单行命令无法自定义完整请求探测端口、基础命令http://标准HTTP请求格式受限无法自定义请求体Web服务交互ftp://用户名、密码、目录很难构造任意数据文件操作gopher://任意字节流几乎没有各类内网服务注意看最后一行“任意字节流”是其他所有协议都不具备的。HTTP协议被浏览器和组件固化得死死的你很难控制它发出畸形请求dict协议虽然是文本协议但它只允许你传单行长度的内容毫无灵活性可言。只有Gopher因为设计极简反而保留了最大的自由度。在我看来Gopher的强大可以用一个生活化的类比来解释其他协议像是有轨列车只能沿着固定的轨道跑Gopher则像一块可以随意捏的橡皮泥你想捏成什么样它就是什么样。3. 拿下SSRF关键点用Gopher构造任意HTTP请求3.1 构造一个标准HTTP GET请求的完整过程先从最简单、也最常见的场景讲起如何通过Gopher发一个标准的HTTP GET请求。假设内网有一个Web服务地址是http://127.0.0.1:8080/admin你希望SSRF的目标服务器去访问这个内网地址并把这个请求发送给内网服务。常规的http://协议直接就能做到但如果目标Web应用对URL做了严格过滤禁止直接使用http://只放行gopher://你就得手动把HTTP请求“翻译”成Gopher格式。一个标准的HTTP GET请求长这样GET /admin HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 Accept: */* Connection: close注意这里有两个换行HTTP协议规定请求和响应之间必须有一个空行来分隔头部和消息体。转换成Gopher URL你需要把这个请求内容逐字节编码。第一步把请求头和请求尾的换行符都转成%0d%0a也就是\r\nHTTP规范里的标准换行消息体结束时再额外加一个%0d%0a表示空行。这样整段请求就变成了GET /admin HTTP/1.1%0d%0aHost: 127.0.0.1:8080%0d%0aUser-Agent: Mozilla/5.0%0d%0aAccept: */*%0d%0aConnection: close%0d%0a%0d%0a第二步在Host请求头后面拼接目标IP和端口然后在开头拼上gopher://gopher://127.0.0.1:8080/_GET /admin HTTP/1.1%0d%0aHost: 127.0.0.1:8080%0d%0aUser-Agent: Mozilla/5.0%0d%0aAccept: */*%0d%0aConnection: close%0d%0a%0d%0a这里有一个非常容易被忽略的细节_字符。Gopher协议规范里selector字段的第一个字符会被解释为gophertype也就是资源类型标识。而当我们想发送任意数据时这个_是约定俗成的占位符因为它不会被服务器特殊处理相当于“默认类型”。如果你漏掉了这个下划线整个请求可能就无法正确发出去。第三步把整个URL交给SSRF漏洞点发送。目标服务器会去连接127.0.0.1:8080然后逐字节发送后面这一长串编码数据。内网的HTTP服务收到后解包看到的就是一个完美的、没有任何异常的GET请求。这就是Gopher构造HTTP请求的基本原理。3.2 进阶用法发送POST请求与自定义Body上面讲了GET但实际渗透中更常见的是POST请求。比如内网有一个登录接口你想通过SSRF去提交一个表单光靠GET是做不到的。用Gopher就能解决。POST请求和GET的区别在于多了Content-Type和Content-Length两个头以及一个消息体。构造时你不仅要编码请求头还得把body内容一起编码进去。body和头部之间用空行隔开空行同样要编码成%0d%0a。假设要提交一个登录表单POST /login HTTP/1.1 Host: 127.0.0.1:8080 User-Agent: Mozilla/5.0 Content-Type: application/x-www-form-urlencoded Content-Length: 29 Connection: close usernameadminpasswordadmin注意Content-Length必须和body的字节数完全一致。如果你写少了服务端会一直等待后续数据直到超时写多了服务端会把下一个请求的头误判为body内容直接报错。计算usernameadminpasswordadmin的字节数可以用工具也可以直接数username是9个字符admin是5个加上password是10个再加上admin5个总共29个就是这个值。然后整段转换成Gophergopher://127.0.0.1:8080/_POST /login HTTP/1.1%0d%0aHost: 127.0.0.1:8080%0d%0aUser-Agent: Mozilla/5.0%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aContent-Length: 29%0d%0aConnection: close%0d%0a%0d%0ausernameadminpasswordadmin这里可能有人会问body里的符号不需要URL编码吗在Gopher协议里selector部分只有特殊字符需要转义比如空格、#、?等。在URL中一般是参数分隔符但在Gopher的selector上下文里它不需要被转义直接按原样发送。不过如果你不确定最稳妥的做法是把所有非ASCII字符或可疑字符都做一次URL编码多编几个字符不影响服务端解析。我就不再赘述直接说结论在构造Gopher payload时凡是可能被URL解析器“吃掉”的字符一律编码凡是不会造成歧义的可见字符保留原样。3.3 一个必踩的坑请求行的换行符与URL解析器的二次解码我在实操中遇到过很多次payload构造完全正确但请求就是发不出去的情况。排到最后发现问题几乎都出在换行符上。Gopher协议的URL在传递过程中不同的语言和框架会对它做不同次数的URL解码。比如目标应用用的curl命令发起HTTP请求时它收到原始URL会先做一次解码如果应用在这个环节之前又额外调用了一次urldecode函数你的payload就会被解码两次。第一次解码后%0d%0a变成了真正的\r\n第二次解码时如果\r\n本身不含有%号就不会再被识别为转义序列问题不大——但前提是你在构造时已经把字符编码对了。真正的坑在于如果你一次性编码了两层也就是说你把%25作为%字的转义那么最终发送到Gopher服务器的数据就会多一层编码服务端收到的就不是HTTP请求了而是带%号的乱码。解决方法是先搞清楚目标应用到底做几次URL解码多试几次找到一个合适的编码层数。一个比较实用的经验是在本地先搭一个监听端口做测试用NetCat或者其他工具监听8080端口然后把构造好的Gopher payload直接发给本机看NetCat收到的原始数据是不是你想要的HTTP请求。如果收到的数据里还有%符号就说明编码多了一层如果直接是原始请求那就没问题。这个验证思路特别适合实际渗透时快速调试省得反复打内网服务试错。4. 向内网延伸Gopher的“隐形触手”有多远4.1 不止HTTP探测内网任意TCP服务Gopher能构造任意TCP数据包这意味着它的目标范围远不止HTTP服务。在内网里很多服务都是基于文本协议或简单二进制协议都可以尝试用Gopher打一打。最经典的目标是Redis。Redis是一个内存数据库本身支持未授权访问而且它的命令协议也是纯文本的。常见利用手法是往Redis里写crontab或者SSH公钥然后获得内网机器权限。用Gopher发Redis命令和直接连上Redis客户端的效果几乎一致。构造方法不复杂。假设要向Redis发送set test hello这条命令只需要按Redis网络协议格式拼好再整体编码成Gopher URLredis命令格式*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n将这个格式整体编码进Gopher selector最终GET请求就变成了gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$4%0d%0atest%0d%0a$5%0d%0ahello%0d%0a目标服务器发送后Redis服务端会解析这段内容完整地执行set test hello。除了RedisMySQL、FTP、SMTP甚至一些自定义的TCP协议服务理论上都可以用Gopher进行交互。凡是你能写出它的协议报文格式Gopher就能帮你送达到内网服务。这个覆盖面常规SSRF工具链里几乎找不到替代品。4.2 利用Gopher做内网端口探测和指纹识别Gopher还能顺带完成端口探测和指纹识别的任务。原理很简单向一个端口发送一段特定的数据观察返回内容或超时状态就能判断端口是否开放、服务类型是什么。比如你想知道内网某台机器的3306端口是不是MySQL可以向它发送一个MySQL的握手请求包。如果返回的是MySQL特有的版本号报文那就说明端口是开的而且服务身份确认。这个手法比常规的TCP SYN扫描更隐蔽因为它是在SSRF平台的控制之下发出的源IP是目标服务器而不是你自己的机器不易被边界设备追踪。再比如内网有SMTP服务你可以通过Gopher发送HELO指令看是否返回250状态码就能判断这个服务是否存活。但这里有一个重要的限制Gopher的响应数据能不能被看到取决于SSRF漏洞所在的上下文。如果目标应用只是后台发请求不把响应渲染到页面上那你就盲打了只能通过延迟和报错来猜测服务是否存在。如果是页面直接展示响应内容的SSRF那Gopher配合探测就非常舒服回显一目了然。4.3 间接利用Gopher作为跳板攻击链的一环很多时候Gopher单独的利用价值看起来有限但它真正强的地方在于能把SSRF攻击链延展开来。举个例子目标Web应用存在SSRF内网有一台Redis未授权服务但Redis配置了保护模式只允许本机或内网特定IP访问。这个时候你就可以用Gopher从目标Web应用发起请求去操作内网这台Redis再通过Redis写文件的方式拿下一台内网机器。拿到内网机器之后原有Web应用的边界就已经被突破了后续的内网渗透可以从这台机器继续往外打。整个过程Gopher就是最核心的“跨协议跳板”。我之前在某次模拟演练中遇到过一个场景目标应用有两个SSRF接口一个能访问外网一个只能访问内网。内网这个接口做了协议白名单只放行gopher://和白名单域名。起初我觉得没什么用后来发现白名单域名背后是内网某台缓存服务器而且服务端用的协议是Redis的变种。最终就是用Gopher构造一串缓存命令改写了内网缓存内容导致了目标业务崩溃。这个思路放在考试里可能不常见但在真实攻防里灵活运用协议转换往往能解决意想不到的问题。5. 防御侧怎么看Gopher攻击的检测与阻断5.1 从日志检测Gopher请求的特征说了这么多攻击侧的用法这会很多人会觉得Gopher简直无敌了。其实防御侧也不是完全无解只不过很多防护系统对Gopher协议的特征识别做得太差才让它成了漏网之鱼。Gopher请求在Web服务器日志里会留下一些非常明显的痕迹。常规HTTP请求的URL里一般只包含路径和参数而Gopher请求的URL会以gopher://开头后面跟着一大长串的%编码字符。把这个特征做成正则表达式配合威胁情报系统是可以有效识别一部分攻击尝试的。另外Gopher请求发往内网服务时目标服务会收到一段特殊的TCP数据。比如Redis服务正常客户端连接后第一条消息是*1\r\n$7\r\nCOMMAND\r\n这类带星号和美元符号的RESP协议内容。而Gopher构造的HTTP请求发向Redis时Redis会直接报错协议解析失败并在日志里记录下“Protocol error”之类的关键字。排查内网服务日志同样能够发现异常。5.2 代码层面如何阻止Gopher协议滥用在代码层面SSRF防御的核心就是限制协议白名单。很多应用在实现SSRF功能时往往只校验了http://和https://完全没有考虑gopher://、dict://等非HTTP协议。这正好是攻击者的切入点。开发者在实现任意URL请求功能时应该维护一个严格的白名单协议列表默认只允许http://和https://对URL进行解析提取协议字段并确保解析结果和实际使用的协议一致防止UR歧义绕过对目标IP进行校验阻止内网IP、环回地址、链路本地地址等特殊网段禁止重定向跟随防止请求被带到非预期协议其中第四点最容易被人忽略。很多应用默认情况下会跟随HTTP重定向而重定向目标甚至可以写成gopher://开头的地址。也就是说即便你外面限制了协议白名单攻击者可能仍通过30x重定向间接打出Gopher请求从而绕过防护。处理这个问题的办法是手动关闭自动重定向或者对重定向后的URL做二次校验。5.3 内网侧加固不能只靠外网WAF外网WAF能拦截一部分明显带有gopher://标识的请求但内网服务的自身防护更关键。Redis可以通过开启requirepass配置密码、启用保护模式、绑定本机回环地址来减少暴露面。MySQL、FTP等服务也应该限制来源IP白名单。Gopher之所以能在SSRF里奏效根本原因是内网服务之间缺乏足够的认证和访问控制。打好补丁、收紧权限哪怕攻击者能发任意TCP数据也会因认证问题卡在门外。从长远看微服务架构内的服务间认证比如mTLS、流量加密、网格化管控都是有效抑制这类攻击的手段。Gopher攻击再强也强不过一道道标准的安全边界。6. 实操心得构造Gopher Payload时那些“坑”和“巧劲”6.1 确保目标端口支持对应的协议这是最基本但也是最容易出错的地方。你构造了一段完美的HTTP请求但目标端口是个Redis服务那服务端会把它当Redis命令来解析大概率直接报错。反过来你构造了一段Redis命令但目标端口是MySQL也会得到一堆无法理解的报错。所以在构造payload之前一定要先确认端口的服务类型和版本必要时用常规扫描工具或SSRF的dict探测先摸清底细。确认服务类型有个小技巧先发一段简单的文本看返回内容。如果返回中包含$、*这类RESP协议特征字符基本可以确认是Redis如果返回是MySQL的风格会包含版本号字符串和问候报文。通过这种交互式的识别比单纯扫端口准确得多。6.2 URL编码的层级和转换时机构造Gopher payload时最折磨人的就是URL编码的层级。不同的编程语言、不同的URL解析库、不同的框架层数都会影响最终发出去的原始数据。我见过有人用Python的urllib.parse.quote做一次编码然后又用requests发一次请求结果被requests库自动解码一次导致最终数据多了一倍%。建议的做法是用最原始的方式发送payload绕开自动解码逻辑。比如在Python里你可以用requests库的URL参数传gopher://形式但要注意requests会不会对URL做编码最好先用curl直接测试因为它不会做额外的自动编码拿到稳定的payload后再移植到代码里。6.3 测试时用本地监听“排雷”前面我已经提过一次但这里想把它单独讲透。构造好一个Gopher payload不要直接打内网服务先在自己机器上开一个监听端口把payload打过去看看原始数据是否完全符合预期。比如用NetCat监听8888端口nc -l -p 8888然后构造一个访问本机8888端口的Gopher payloadgopher://127.0.0.1:8888/_GET /test HTTP/1.1%0d%0aHost: 127.0.0.1:8888%0d%0aUser-Agent: test%0d%0a%0d%0a发送后NetCat窗口显示的应该是一模一样的HTTP请求文本。如果发现少了某个换行或者多出了%符号就能立刻定位问题是出在编码层数还是回车换行上。这个“排雷”思路适用于一切构造Gopher payload的场景。6.4 把“响应盲区”转化成一枚可利用的延时炸弹刚才提到过有些SSRF不展示响应内容这时候Gopher的作用看起来会受限。但你可以换一个思路利用请求的超时和延迟作为侧信道判断目标服务的状态。比如内网某个端口上跑了个服务你向它发送Gopher构造的连接如果端口是关闭的目标会立即返回连接拒绝错误整个请求耗时极短如果端口开放即使服务端不返回内容TCP握手和请求发送的过程也会让响应时间产生微妙差异。多次采样求平均值能区分出端口开闭的相对关系。更高级一点你可以故意构造一个有延迟的请求。比如给MySQL发一个开启事务的命令然后发起一个长时间的sleep查询让整个SSRF请求挂起几十秒通过响应时间的异常来做端口指纹识别。这种方式在完全盲打的状态下也是一个可用的探测手段。7. 别只把Gopher当“奇技淫巧”它对架构设计的启发当我逐渐熟练运用Gopher在SSRF里做各种操作后我发现它对防御方的价值其实更大。理解了Gopher为什么能穿透边界、为什么能伪装成各种协议你会对“协议和数据包”有更深的认识。从架构层面看Gopher攻击能成功本质上暴露了两个问题第一应用对外部输入URL的信任度过高第二内网服务之间的通信毫无认证与隔离。任何过程性防护都应该从这两个根源上去解决。开发者在做后端请求代理、URL回调、Webhook等功能时不能只看协议名还要看最终发出去的字节流。安全人员在设计内网防线时不能只依赖防火墙端口封禁更要关注服务自身是否具备独立的认证机制。如果你的内网Redis裸奔、MySQL免密即使没有Gopher也有无数种方法可以打到它。Gopher协议提醒了我一件事安全攻防永远是在比谁更了解底层。攻击者并不需要掌握多么高深的0day他只需要熟悉一个被遗忘的协议就能突破精心构建的防线。防御者如果只看表层不看底层数据流终有一天会被“被遗忘的角落”打破防线。8. 写在最后关于Gopher的冷思考也可能有人觉得现在CTF题目里Gopher已经不算新鲜玩意儿了而且现实环境中能同时满足“SSRF可控发Gopher”、“内网存在无认证服务”、“响应可见或侧信道可测”这三条件的场景越来越少。但我不这么看。Gopher的利用思维核心不在于Gopher本身而在于“协议转换”——同一个攻击目标换一种协议表达方式可能就绕过了所有检测规则和防护策略。这就像两军对垒正面进攻被防得密不透风那么走一条无人关注的下水道反而可能是最快捷的路线。Gopher就是那条下水道。实操中我个人非常建议安全方向的从业者尤其是做Web安全的朋友花半天时间把Gopher协议完整玩一遍。从最简单HTTP GET、POST到打Redis写文件每一个步骤都亲手构造、亲眼验证。你会发现原先很多固定的攻击路径被打破后会衍生出非常多意想不到的攻击链。这个东西能不能用上是一回事你用不用得上是另一回事。但学到手了它就是武器库里一把随时能拔出来的刀。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/21 23:51:22
Docker生产实战避坑指南:从启动失败到故障自愈
2026/10/21 23:51:22
连杆平行度测量仪:激光传感器与回转轴方案详解
2026/10/21 23:51:22
10机39节点电力系统仿真指南:Matlab/Simulink暂态稳定建模与故障分析
2026/10/22 0:01:24
rea项目极简架构实战:响应式与实时系统的设计、实现与避坑指南
2026/10/22 0:01:24
大数据人才需求分析毕设实践:从招聘数据到可视化系统
2026/10/22 0:01:24
若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定
2026/10/22 0:01:24
压缩空气储能与热网热惯性联合优化调度:热电联产系统建模与求解
2026/10/22 0:01:24
电商智能运营体系从0到1:架构设计、落地步骤与避坑指南
2026/10/21 23:56:23
三菱PLC张力控制系统:从模拟量采集到PID整定的全流程实践
2026/10/22 0:01:24
电商智能运营体系从0到1:架构设计、落地步骤与避坑指南
2026/10/22 0:01:24
压缩空气储能与热网热惯性联合优化调度:热电联产系统建模与求解
2026/10/22 0:01:24
若依框架AI代码生成不稳定?用Agent Skill三层结构+五步法搞定
2026/10/21 19:05:16
OpenResearch实战:Claude Code、Codex、OpenCode、Cursor 工作流搭建与故障排查
2026/10/21 14:47:26
知识管理核弹:用Obsidian构建第二大脑的工程化方案(TaoToken 统一 Key 接入版)
2026/10/21 19:05:04
金融AI落地指南:从调研报告到技术选型与工程实践
2026/10/20 2:24:03
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/20 2:23:57
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/21 19:01:08
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)