
1. SSRF漏洞成因拆解为什么一个“正常功能”会变成内网敲门砖SSRFServer-Side Request Forgery服务端请求伪造这个漏洞在我接触过的Web安全问题里一直属于“看着不起眼、打起来要命”的类型。它不像SQL注入那样有个明确的报错入口也不像XSS那样能直接弹个框证明存在它更像是攻击者借服务器之手把原本只对外开放的请求能力悄悄转向了内网。简单说SSRF的本质是服务端接收了用户传入的URL然后替用户去请求这个URL但没有对目标地址做严格校验导致攻击者可以诱导服务器去访问内网地址、本地服务甚至云环境的元数据接口。要理解SSRF为什么会产生得先回顾一下它最常见的出现场景。很多确实在业务中需要“服务端主动发起请求”的功能比如图片远程抓取、URL预览、Webhook回调、PDF生成器、在线代理、API网关的请求转发这些功能天生就需要服务器去访问一个用户提供的地址。问题就出在这里如果开发者在实现时图省事直接file_get_contents($_GET[url])或者requests.get(user_input)再或者只做了非常浅层的过滤那攻击者传入的地址一旦是http://127.0.0.1:6379/或http://10.0.0.8:3306/服务器就会老老实实地替攻击者访问这些内网资源。这里有个很重要的点需要区分SSRF和前端页面里的跳转、重定向不是一回事。普通用户访问网页时浏览器发出的请求是用户终端发起的目标地址是公网或客户端可达的网络而SSRF请求是服务器发起的攻击者利用的是“服务器所在网络环境”的访问权限。内网里很多服务默认绑定在0.0.0.0或只监听内网网卡外部攻击者直接访问不到但服务器可以访问到。一旦功能设计里允许用户指定URL又没有过滤内网地址或特殊协议攻击者就等于拿到了一根伸进内网的“探针”。从漏洞成因的更深层看SSRF还牵扯到一个很微妙的点URL解析的差异。同样一个地址开发者以为是公网地址但解析器可能会解析成内网地址。举个例子http://www.xxx.com127.0.0.1/这种带的URL很多解析器会把后面部分当作真实主机http://2130706433/这种十进制表示的IP不少过滤器不会识别出来是127.0.0.1还有http://[::1]/、http://0/等变体都会让不够严谨的URL校验变成摆设。这就是SSRF绕过的核心战场也是后面探测内网时最需要花心思琢磨的地方。2. 探测内网方案与实操要领2.1 先摸清过滤规则再谈内网探测在实际拿到一个SSRF点之后第一件事绝对不是急着扫描内网而是先判断这个点能发什么请求、不能发什么请求也就是摸清过滤规则。我自己的习惯是分三步走。第一步用最简单的公网地址做基线测试。比如准备一个自己能控制的服务器或站点让SSRF去请求http://自己的公网IP/log然后在服务器上观察是否收到请求、请求头长什么样、携带了什么协议信息。这一步能确认SSRF链路是否通、是GET还是POST、是否跟随重定向、出网DNS解析是否正常。第二步测试内网地址和本地回环地址是否被拦截。直接请求http://127.0.0.1:8080/、http://127.0.0.1:80/或者请求http://内网常见的网关地址/比如http://192.168.1.1/、http://10.0.0.1/。观察返回内容、响应时间、错误信息。如果返回了预期的页面内容说明内网访问没有被限制如果返回“非法地址”“url not allowed”之类的提示说明存在地址黑名单或白名单校验。第三步也是很重要的一步测试协议限制。SSRF里最好用的协议不只是HTTP还有file://、gopher://、dict://等。先试file:///etc/passwd如果能把文件内容读出来说明漏洞直接升级成了任意文件读取后面很多探测思路都能简化。再试dict://127.0.0.1:6379/info或gopher://构造的报文看协议是否被过滤。这一步能直接决定后续利用的“武器库”有多大。2.2 绕过校验的常见方案与原理做SSRF探测时最容易被卡住的就是各种地址校验。我整理了几个比较实用的绕过方案每个方案在实际测试中都有对应的场景。DNS解析类有些过滤规则只做字符串匹配比如判断URL里有没有127.0.0.1那我们可以用域名指向内网地址的方式绕过去。比如http://spoofed.burpcollaborator.net解析到127.0.0.1。或者使用短域名服务把目标IP转成短链。这里有个很关键的技巧用外部DNS解析到内网IP时要注意目标机器能否正常完成外部DNS查询有些隔离网络环境内网机器解析外网域名会超时反而会暴露探测行为。IP地址变体类这是最常用的绕过思路。刚开始学的时候可能觉得换个进制挺玄乎其实原理很简单IP地址在URL解析时最终会被解析成32位整数或实际地址而过滤器往往只识别标准的点分十进制格式。常见的变体包括十六进制形式http://0x7f000001/、八进制形式http://017700000001/、十进制整数形式http://2130706433/、IPv6形式http://[::1]:8080/。我自己实测下来这些变体在过滤不严的应用里成功率很高但有个坑是部分编程语言的URL解析器不认这些变体所以要先确认后端是什么语言、用什么解析库。URL语义类利用、#、?等URL保留字符制造解析歧义。http://www.google.com127.0.0.1/实际请求的是127.0.0.1但一些过滤器只看了前面的域名就放行了。还有利用#让过滤器认为请求的是某个合法路径实际解析器却把#前面部分当作地址。这类绕过的关键在于找到“过滤器解析URL的方式”和“后端实际发起请求时解析URL的方式”之间的差异两个不同的解析器一旦对URL的理解不一致绕过空间就出来了。跳转类利用重定向。如果目标站点允许自定义重定向或者我们可以控制某个公网URL跳转到内网地址那即使代码里校验了第一次请求的地址是公网域名但请求跟随重定向后就会进入内网。https://weibo.cn/xxx这类公开跳转服务在某些场景下也可以借用不过在实战里不太稳定最好用自己控制的重定向服务。2.3 内网存活主机探测的方法论过滤规则摸清了、绕过方案也验证过了才开始正式探测内网。内网探测的核心目标是回答三个问题内网有哪些网段哪些主机存活哪些端口开放先说网段推断。很多内网使用常见的私有网段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16。还有一个非常实用的信息源错误信息泄露。有些应用在SSRF请求失败时会返回详细的错误内容比如connect timeout、connection refused、Connection refused (Connection refused)甚至还能看到后端的代码栈信息。通过观察请求不同网段IP时返回的差异比如“超时”说明主机或网段可能不存在“连接拒绝”说明目标IP存活但端口没开“协议不匹配”或者返回了特定横幅信息说明端口开放我们就能非常高效地盲摸内网拓扑。再说存活探测的效率问题。如果用SSRF逐个请求所有IP的所有常见端口那速度会慢到让人抓狂。我常用的策略是分阶段先探测少量常见的网关和核心IP比如每个网段的.1、.2、.254再结合DNS域名、Windows机器名等方式推断主机的角色最后挑重点主机做全端口扫描。在时间可控的情况下配合字典直接扫常见端口比全端口盲扫更实际比如22、80、443、3306、6379、8080、9200、11211这些端口。实际操作中有个细节特别值得注意很多SSRF点会设置超时时间比如3秒。这意味着探测一个不存在的主机要等满超时时间扫描一大段IP时会非常耗时。我建议在探测时把响应时间记录下来通过“快速连接拒绝”和“超时”的差异来区分存活主机这比单看返回状态码更可靠。像curl -m 5设置超时时间配合并发或异步脚本能把扫描效率提升不少。3. 端口扫描与内网服务利用实战3.1 从端口开放到服务指纹识别端口扫描能发现端口但真正决定SSRF能不能深入利用的是端口的背后跑的是什么服务。因为SSRF不像Nmap那样能基于协议栈指纹做精细识别我们更多是通过“发起特定协议的请求看响应是否符合预期”来判断。用HTTP探测端口时如果端口开放返回内容里往往包含Web服务的特征头比如Server: nginx/1.18.0、X-Powered-By: PHP/7.4或者返回登录页面的关键词。用dict://协议探测时像Redis、Memcached这类明文协议服务会直接返回错误信息或欢迎横幅比如Redis会返回-ERR unknown command之类的响应。这个方法在探测内网常见服务时非常实用因为dict://协议本质上允许我们向任意TCP端口发送一条简单的命令并读取响应相当于一个轻量级的TCP交互工具。当然dict://不是万能的。它发送的命令格式有限很多协议并不识别这时候gopher://就是更强大的选择。gopher可以构造任意TCP数据流比如向Redis发送一条完整的SET命令、向MySQL发送一个握手包、向未打补丁的FastCGI发送一个PHP执行请求。可以说只要能构造出目标协议的数据包gopher就能帮我们和任意内网服务“对话”。我列一个自己常用的内网端口探测清单和指纹判断方式供参考端口常见服务指纹识别方式22SSH发送SSH协议头响应为SSH版本字符串80/443WebHTTP响应头、页面标题3306MySQL发送握手包响应为MySQL协议头6379Redis发送PING响应为PONG或错误信息11211Memcached发送stats响应为STAT信息9200ElasticsearchHTTP响应大段JSON节点信息泄露8080常见Web管理端口HTTP响应头、登录页面27017MongoDB发送连接握手响应为MongoDB协议标志3.2 典型利用场景Redis未授权访问与内网Web服务端口扫描和信息收集做完之后就该进入真正有利用价值的部分了。这里我根据自己的实战经验重点聊两个最常见的利用方向。第一个是Redis未授权访问。内网里很多Redis实例为了部署方便直接绑定在0.0.0.0且没有设置密码这在边界防护较弱的网络里是很常见的。攻击者通过SSRF确认某个主机的6379端口开放后可以用gopher://构造Redis命令比如向Redis发送SET命令写入一个包含Webshell的键值再通过CONFIG SET dir和CONFIG SET dbfilename把数据持久化到Web目录下拿到Webshell。另一种常见的打法是写SSH公钥到authorized_keys前提是目标主机开了SSH服务且Redis运行权限足够高。这个利用链路在真实内网渗透里非常经典但要注意不要只顾着打还要考虑会不会破坏目标的数据比如FLUSHALL这种危险命令在生产环境里千万别乱发会造成严重的业务影响。第二个是利用内网Web服务的敏感信息泄露或管理接口。比如内网某个主机开了8080端口的Spring Boot Actuator通过SSRF可以直接GET到/actuator/env或/actuator/heapdump从里面掏出数据库密码、云密钥、内部API地址。或者内网有某个数据库管理面板、消息队列控制台如果存在未授权访问先用SSRF探测出来再通过后续的端口转发、代理链进一步渗透。这里必须强调一个技术前提用gopher://构造TCP数据包时要特别注意请求的长度和编码。有些后端对URL长度有限制超过限制的gopher报文会被截断导致发送的命令不完整。另外gopher报文的_或者变体编码问题在不同语言实现里处理方式不一样可能要试几个不同的编码方式才能成功。我踩过最深的坑就是Redis命令里带有\r\n换行符必须正确URL编码后在gopher协议中传递否则Redis会认为命令不完整而拒绝执行。实际构造时很多工具和脚本已经封装好了gopher生成逻辑但在使用时仍然要确认目标对协议的支持度。3.3 协议探索顺序与参数选型建议在内网探测的不同阶段不同协议发挥的作用差异很大我给出一套行之有效的探索顺序和选型建议。第一优先级永远是file://。如果能够读取文件内容那么/etc/passwd、.env配置文件、代码文件都有可能暴露。这在信息收集中是性价比最高的协议。第二优先级是http://。HTTP协议兼容性最好能覆盖Web类服务的探测而且不太容易被过滤。可以先探测常见端口上的Web服务再根据返回的标题、Server头判断服务类型。第三优先级是dict://和gopher://。这两个协议在探测和理解非HTTP服务时不可替代但也是最容易被WAF和应用层过滤重点盯上的。使用gopher://时建议先小范围测试比如打一次目标Redis的INFO命令确认协议通不通再进行更复杂的利用。这里再补充一个容易被忽略的参数选型点URL长度限制。在做内网探测和利用时URL太长可能导致截断。比如用gopher打Redis写Webshell时payload可能长达几百个字符有些PHP应用会限制URL长度在2048以内超出部分被丢弃导致命令不完整。解决办法是把payload尽量精简或者利用Redis的多条命令分段写入。4. 常见问题与排查技巧实录4.1 探测不到内网可能卡在哪几个环节这个问题我在带新人和自己做实验时经常遇到。探测内网一切正常但一换到内网IP就“全员超时”大概率是下面几类原因。DNS解析出不去。目标应用所在服务器可能处于隔离网络无法解析外网DNS或者DNS解析结果里包含了内网地址时直接被系统拒绝。解决方法是优先用IP直连不要依赖域名。协议被过滤。应用层做了协议白名单只允许HTTP和HTTPS导致file://、gopher://都不通。这时候要回到2.2节里的绕过思路先解决协议层面的校验再考虑内网探测。出口策略被限制。目标服务器可能部署了防火墙策略只允许访问公网80、443端口其他所有出站流量都会被阻断。判断方法很简单请求一个公网2233端口的服务如果超时而出网80端口正常那大概率就是出站ACL把非标端口限制了。应用层解析器差异。后端语言和解析库对URL的处理方式差异很大比如PHP的parse_url和Python的urlparse对某些特殊URL的解析结果完全不同。如果内网IP被应用层直接拒绝先用各种编码绕过试试如果绕不过去那就用重定向法前提是确认后端会跟随重定向。4.2 一个关键的排查思路把每一次响应都当作情报SSRF探测成功与否不只是看能不能拿到内网数据响应时间、响应内容、错误信息、状态码变化每一项都是情报。我经常用一个简单的判断矩阵来分析响应响应特征可能原因下一步建议立即连接拒绝目标IP存活但端口未开放记录目标存活换下一个端口3秒超时目标IP不存在或防火墙丢弃包换IP继续探测降低单点等待HTTP 200且内容正常端口开放服务可访问分析内容提取指纹信息HTTP 502/504目标服务异常或代理层拦截尝试直连后端IP或用dict协议探测特定协议响应端口开放非HTTP服务使用gopher/dict进行协议级交互这个矩阵越用越顺因为每次探测返回的细节都暗示着目标网络环境的配置和过滤策略。比如同一个网段的IP如果大部分都是“连接拒绝”说明主机存活但端口少如果全是“超时”说明可能整个网段不可达或防火墙丢弃了请求需要调整网段假设。4.3 修复与加固建议站在攻击者的视角做防御聊了这么多利用方法如果不提防御就有点不负责任了。做安全工作的人常说“最好的防御就是理解攻击”理解了SSRF的利用链就能针对性地修复。第一层修复是入站限制对所有提供URL输入的功能做严格白名单校验只允许请求已验证的域名或IP而不是黑名单过滤内网地址。黑名单永远会被各种编码变体绕过白名单直接掐断了攻击面。第二层是协议限制代码层面禁止file://、gopher://、dict://等非HTTP(S)协议只保留业务必需的协议。第三层是DNS解析防护对目标URL做解析后将解析结果再校验一次确保解析后的IP不是内网地址这能有效阻断DNS rebinding攻击。第四层是网络出口隔离服务器如果不需要访问内网就不要给这台机器访问内网的网络权限这也是最容易被忽略的一点。4.4 当外带通道被切断时的替代方案SSRF探测过程中经常会遇到一个尴尬情况服务器无法直接连出公网或者连出公网的通路被防火墙切断导致内网探测的结果根本传不回攻击者这边。这种情况下“外带”数据就成了关键问题。我的经验是分场景处理。如果SSRF点本身能返回响应内容那就直接看响应不需要外带。比如用file://读取文件、用dict://探测端口返回内容直接就显示在页面上。关键在于选择那些“有回显”的探测方式。如果目标应用对响应做了过滤或只返回部分内容那就优先利用错误信息回显比如让目标发起一个到内网不存在的端口的连接让报错信息中带上目标IP和端口再根据报错判断端口状态。如果实在无法回显那就需要借助“间接外带”。常见方式包括利用目标请求一个我们控制的公网服务在公网日志或DNS解析记录中看到目标发出的请求从而确认“目标能否出网”“内网IP是什么”。这种方式就是俗称的DNSLog或HTTPLog原理很简单目标访问了我们的公网地址在日志里能看到目标的出口IP、User-Agent、请求路径等信息。这样做还有一个额外好处不需要目标能回传大段数据只需要它“访问一下”即可。当然间接外带也有局限性比如目标网络对出站DNS和HTTP做了严格ACL那么连“打一下”都打不出来。碰到这种情况只能回到纯内网探测的思路尽量在无外带条件下靠响应差异和响应时间来判断。5. 一点经验之谈回到SSRF这件事本身我最大的体会是这个漏洞的利用价值往往取决于“目标服务器在网络中的位置”和“SSRF点能使用的协议范围”这两个变量。很多初学者拿着Payload一头扎进内网扫描结果处处碰壁本质是没有先把这两个变量摸透。把前期的探测做扎实——判断过滤规则、确认协议支持、摸清网段边界——后面真正打端口、利用内网服务时反而会很顺。最后分享一个我个人在实验和CTF里经常用的小技巧在内网探测时优先找那些“响应快、特征强”的服务比如RedisPING回PONG、Memcachedstats回大量信息、ElasticsearchHTTP响应大段JSON。先用这些高特征服务确认主机的存活状态和服务类型再去碰那些可能要求更复杂协议的端口。这个顺序能让你在同一个SSRF点里尽可能少发请求少暴露痕迹但拿到最有价值的那部分信息。毕竟是安全测试低调、精准、有条理永远比蛮力更管用。