☰
SSRF漏洞详解:从原理、绕过到内网渗透与修复实战
2026/9/25 15:26:56 网站建设 项目流程

先声明一句:我在安全测试这条路上认识SSRF有几年了,真正让我重视它的是某次授权渗透里,一个看似不起眼的URL输入框,直接让我拿到了内网一台数据库的血拼权限。SSRF全称是Server-Side Request Forgery,服务端请求伪造,它利用的是服务器端的URL解析缺陷,让服务端自己发起恶意请求。很多开发朋友觉得它只是个"HTTP请求转发",实际上它的杀伤范围覆盖内网探测、云元数据窃取、Redis等内网服务利用,甚至能联动RCE。这篇内容写给三类人:刚入门的安全同学想搞懂原理和利用逻辑,开发同学想弄清楚为什么自己写的代码会中招,以及做企业防护的运维想知道怎么彻底堵住这个洞。我会从原理、攻击面、利用链路、绕过手法、修复方案和实战复盘几个维度展开,尽量还原我在真实项目中的排查思路。

1. SSRF漏洞的成因:服务器为什么会帮你请求任意地址

1.1 从"服务器替你访问"的常见业务说起

你先忘记技术名词,想一个最普通的场景:你的产品做了一个"文章封面抓图"功能,用户提交一个图片URL,后端PHP用file_get_contents($url)抓取这张图,存到本地。这个流程里,服务器收到URL之后自己发起了HTTP请求,绕过了浏览器的同源策略,服务器成了HTTP客户端。

问题来了:你怎么限制这个URL指向哪里?现实是很多代码只判断了URL是否以http/https开头、扩展名是否为jpg/png/gif,然后就把请求发出去了。于是用户可以提交http://127.0.0.1:3306、http://169.254.169.254/latest/meta-data/、http://192.168.1.100:8080,服务器都会照单全收。这就是SSRF最基本的形态:用户传入URL,后端发起请求,且请求的目标不受严格白名单约束。

1.2 哪些代码写法最容易踩中这个雷

从代码审计角度看,SSRF的高发函数都有一个共同特征:接收外部输入作为URL参数,然后交给HTTP客户端库或系统命令去执行。例如PHP里的file_get_contents、curl_exec、fsockopen,Java里的HttpClient、URLConnection、OkHttp,Python里的requests、urllib,Node.js里的axios、http.request。这些函数本身没问题,但开发者往往忽略了"用户输入"与"请求目标"之间的信任边界。

我见过一个典型的Java案例:业务系统需要访问用户填写的合作方API地址做回调验证,代码直接把参数传给HttpURLConnection。测试时一切正常,但没有任何人想过用户会填内网地址。直到某天安全团队做内网扫描,发现这台web服务器能访问云环境的元数据接口,才意识到它其实已经成了内网跳板。凡是业务逻辑里有"代理用户指定的URL"、"抓取远程资源"、"根据URL生成二维码/PDF"这类需求,都要默认它是SSRF候选。

1.3 判断SSRF的关键标志:请求从服务器发出

很多人混淆SSRF和其他漏洞,关键点就在"请求发出者是谁"。XSS是浏览器发请求,CSRF是受害者浏览器发请求,而SSRF是服务器端发请求。在Burp Suite里,如果提交一个URL后,你看到HTTP历史里多了一条来自服务器的请求记录(可以用collaborator或自己的日志服务器观察),并且这条请求的目标可以被你控制,那基本就是SSRF。

验证时有个小技巧:把URL指向你自己可控的域名,比如notify.dnslog.cn这类DNSLog服务,或者自己的VPS,然后观察是否收到来自目标服务器IP的解析请求。如果收到了,说明目标服务器确实出网了;如果目标地址填的是内网段,则说明它正在帮你探测内网。这一条验证逻辑贯穿整个利用流程,后面我会反复提到。

2. 攻击面盘点:这些业务场景是SSRF的"高产区"

2.1 图片处理、截图服务与文档转换

图片相关功能是SSRF重灾区,原因很简单:图片本身是远程URL资源,产品天然需要服务端去拉取。比如用户头像上传时支持"从URL导入"、网页版文章编辑器支持"粘贴图片外链下载"、以及最常见的"网页快照截图"服务,用户输入一个网址,服务器用无头浏览器打开后截图。后者更危险,因为截图服务往往使用完整浏览器内核,不仅能发HTTP请求,还能执行JS,SSRF可能演变成内网浏览器攻击。

文档转换也是典型场景:把PDF转换为图片、把HTML渲染成PDF、Office文件在线预览,许多转换库(如wkhtmltopdf、LibreOffice)在解析文档时,会主动获取文档里引用的外部资源,包括内网地址。我在一次测试中遇到一个"在线简历转PDF"功能,简历里嵌了img标签指向http://127.0.0.1:6379,转PDF时服务器对本地Redis端口发起了连接,虽然没拿到数据,但通过延迟差异能确认端口开放。这就是SSRF不必"回显",借助时间盲打也能探测内网。

2.2 代理与转发型API

某些后端接口设计为"网关代理"形式:前端传一个完整URL,后端统一代理请求,用于解决跨域、隐藏真实API地址或聚合内容,例如"rss代理""oembed解析""链接嗅探/外链检查安全服务"。这类接口几乎就是SSRF的"标配",因为它的核心逻辑就是替用户请求任意URL。外链检查服务尤其讽刺:明明是安全功能,却常常因为只校验了协议头就放过内网地址,反而成为最直接的内网探测口。

2.3 云环境下的额外杀手锏:元数据服务

如果你测试的应用跑在阿里云、腾讯云或AWS上,SSRF的价值会被放大一个量级。云厂商通常提供http://169.254.169.254/latest/meta-data/这种链路本地地址,目标服务器通过它获取自身实例ID、IAM临时凭证、用户数据等敏感信息。攻击者利用SSRF访问这个地址,就能拿到云API的临时密钥,进而直接操作云资源,比如读写OSS存储、创建虚拟机、删除数据库快照,影响面从"一台内网服务器"扩张到"整个云账号权限"。

需要强调一下,云元数据接口在公网不可达,只有服务器自身的链路本地可以访问,所以它是天然的SSRF利用对象。所有部署在云上的应用,SSRF风险等级都应该默认调高。我在写修复方案时,从来不对云场景做"低危"误判,就是因为这个链路太长。

3. 从探测到内网漫游:SSRF利用的完整链路拆解

3.1 第一步:确认参数点能控制完整URL

拿到一个疑似SSRF功能,先看它接受什么形式的输入。有的接口支持完整URL,有的会把域名槽位单独拆出来拼接,实际测试方式完全不同。完整URL场景最理想,直接替换为攻击者控制的目标即可;如果服务端强制补充了一个前缀或路径,需要先摸清楚拼接规则,才能判断能否通过#、?、@等字符做语法层面的"逃逸"。

这里有一个重要经验:先区分"直接回显SSRF"和"盲SSRF"。直接回显指响应里包含请求结果,比如图片抓取功能把远程图片内容返回给浏览器;盲SSRF指响应看不到目标内容,只能通过状态码差异、响应时间差异、DNSLog回连等间接信号判断。后者在利用时更需要想象力,很多内网服务数据未必能原样返回,但配合错误信息,一样能拿到版本号等指纹信息。

3.2 第二步:绕过的本质是"你以为限制住了,其实没有"

开发者常用的防护是检测URL主机名是否内网IP、是否localhost、域名后缀是否合法,但绕过手法层出不穷。整个攻防博弈的核心是:开发者的校验只能基于字符串或者一次DNS解析,而真实请求发生时会经历多次DNS解析、重定向、URL规范化,校验时看到的东西和实际请求的东西可以完全不一样。

举例来说,校验代码检查"host不能是127.0.0.1"时完全基于字符串。但黑客提交http://127.0.0.1.nip.io:8080(这是*nip.io的域名解析服务,会把这个域名解析到127.0.0.1),或者用十进制IP表示http://2130706433/,都能绕过基于点分十进制的字符检查。"绕过"不是魔法,只是找到校验与实际行为不一致的地方,这一点我会在下一章详细展开。

3.3 第三步:内网信息收集的三板斧

确认SSRF可控后,内网渗透往往从这里提速。

第一板斧是端口存活探测。将URL指向内网IP的特定端口,通过响应内容、响应时间或错误类型判断端口是否开放。比如http://192.168.1.10:22,SSRF返回SSH的banner或连接超时,就能判断22端口开启;http://192.168.1.10:3306如果被数据库拒绝,错误详情往往能暴露MySQL版本。

第二板斧是访问云元数据与敏感管理端口。除169.254.169.254外,内网常见的服务如Redis(6379)、Elasticsearch(9200)、Docker API(2375)、Kubernetes API(6443)、Hadoop YARN(8088)都是高价值目标。某些协议配合请求方法的差异,还能利用SSRF去操作这些服务,比如Gopher协议可以构造完整Redis协议包,实现写Webshell或反弹Shell;这已经不是单纯的HTTP探测,而是服务交互层的攻击了。

第三板斧是利用重定向扩大攻击面。不管后端限制多么严格,只要一个外部URL能302跳转到内网地址,很多只关心第一次请求的防护就会失效。我会提供一个可靠建议:测试时先找目标服务器上是否有自己的重定向器(比如开放的跳转接口),如果没有,可以用自己VPS上写一个302重定向服务,同样能达到绕过目的。

4. 防绕过清单:我在项目和实战中总结的修复与校验方案

4.1 常见绕过方式汇总

安全测试人员的价值在于把攻击者手法透明化。我按"解析层""网络层""协议层"梳理了绕过方式,方便对应修复。

绕过类别典型手法实际效果
字符串黑名单用十进制/八进制IP,如http://2130706433/绕过"127.0.0.1"的字符串匹配
DNS解析差异使用解析到内网的外部域名,如*.nip.io、sslip.io绕过IP黑名单,但DNS最终指向内网
URL规范化利用@符号,如http://admin@127.0.0.1使校验函数识别错主机名
重定向URL先访问外网可控服务,302跳到内网绕过只检查首个请求的限制
协议扩展file:///etc/passwd、gopher://内网Redis从HTTP探测升级为任意文件读取/服务交互

回到修复视角,你不可能在代码里写死所有恶意花样,思路要从"防特征"转向"做边界"。

4.2 自顶上而下的防护措施

业界公认的修复函数是"白名单+网络层隔离"双层防线,单靠其中一层都不够。白名单方面,如果业务确实需要用户指定URL,至少遵循三个限制:协议只允许http/https;域名必须属于业务允许的域名集合,且是精确匹配、不允许子域泛匹配;端口限定在80/443,如果不是云场景且业务不需要内网访问,则绝不放开自定义端口。

但白名单本身有坑:假如业务允许用户提交任意网站的图片链接,那域名集无法固定,就必须靠第二层——网络边界。在服务器上通过防火墙/安全组限制出口流量,只允许访问公网必要的IP段,并禁止访问本机回环地址、链路本地地址(169.254.0.0/16)、保留内网段( 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)。这是我认为最有效的兜底方案:即使应用层校验被绕过,攻击者也无法访问内网IP。

DNS校验也应当做加固:不能只做一次域名解析就放过,因为存在DNS rebinding攻击(第一次解析为公网IP,第二次解析指向内网IP,两次解析结果不一样)。可靠的解法是用现有的SSRF防护SDK或自己实现"解析校验后携带解析IP连接"的机制,确保连接使用的IP就是校验通过的IP。

4.3 云元数据接口的专项防御

在云环境,优先级还要加一条:禁用或关闭实例元数据服务,或者为元数据服务加访问限制。例如某些云服务商支持通过metadata token来访问元数据,普通HTTP请求无法直接拉取;如果业务不需要使用元数据,直接在系统层面封禁169.254.169.254的访问。另外,如果有业务必须访问该地址,一定要把它加入请求代理的全局拒绝列表,因为DNS解析、重定向都可能把这个地址重新引到请求流程里。

我在实际修复时,会特别检查代码里的重定向跟随设置。很多HTTP库默认跟随重定向,即使第一次请求访问的是安全域名,第二次也可能跳到内网地址。建议将重定向跟随设为手动,并在每次跳转时重新执行白名单校验,不能信任"第一次安全就永远安全"。

5. 一块靶场日志还原:SSRF从识别到利用的完整记录

为了让大家直观感受利用链路,我基于自己练习用的CTF靶场环境做一个复盘,目标站点是一个内网的"网页截图API",接口为/api/shot?url=。

5.1 第一步:确认回显型SSRF

向接口提交url=https://example.com,返回的是截图文件的地址,说明服务端会真实访问该URL并将渲染结果保存到本地,回显型SSRF确认。接着提交url=http://127.0.0.1:8080,返回内容里出现了Tomcat默认错误页,说明服务端确实尝试访问了本地8080端口,并且把错误信息带回了响应。此时基础能力已经齐了:能访问内网、有回显、支持任意HTTP端口探测。

5.2 第二步:利用重定向绕过域名白名单

靶场其实限制了url参数首段必须是http(s)://且不能是localhost。于是我在自己VPS上配置一个302跳转,跳转地址指向联系方式中的内网地址http://10.0.0.11:7001。提交url=http://我的VPS/redirect?to=http://10.0.0.11:7001后,截图结果展示了WebLogic管理后台的登录页。这再次验证了上面强调的"重定向可绕过只查首跳的校验",这种绕过在真实应用里太常见了。

5.3 第三步:从HTTP探测转向内网指纹

利用跳转URL逐个探测内网常见端口,我拿到了这些开放端口:10.0.0.11的7001(WebLogic)、10.0.0.12的6379(Redis,且用gopher协议验证不需要密码)、10.0.0.13的9200(Elasticsearch)。Redis这个问题在真实测试中非常致命——如果允许SSRF发起Gopher协议请求,可以构造Redis写计划任务或写公钥,直接RCE。虽然靶场环境没有继续深入,但整个利用链已经从"网页截图功能"扩展到了"未授权访问Redis",危害一目了然。

5.4 修复对照

复盘后我给这个靶场的防护建议是:截图服务必须改用固定内网代理池,且代理池只能访问白名单公网域名;对出网流量实施VPC网络ACL封锁,禁止访问保留地址段;gopher协议在应用层直接禁用,HTTP客户端只允许http/https;重定向跟随必须手动校验每一次跳转。这套组合在任何环境都值得复制。

6. 开发与测试同学的协作视角:SSRF修复不是安全部门一家的事

SSRF经常被当作"开发随便修一下"的问题,但真实修复难点在于业务形态各异。比如"用户提交任意URL生成二维码"这种需求,你很难用域名白名单约束,因为用户本来就可以提交任意网址。这时最务实的方案是让请求走一个专用的"只读请求代理",这个代理自身不持有内网访问权限,并且对响应做严格的内容白名单,再把返回内容过滤掉内网IP、错误堆栈等敏感信息,然后把代理地址和原始SSRF地址保持隔离。

开发同学看到"限制访问内网"的第一反应常常是:"那我的服务怎么调内网其他组件的API?"这就需要分清"业务内部访问"和"用户可控URL发起的访问":内部组件访问走配置中心的服务发现,不依赖用户输入的URL;用户可控URL的请求一律走独立出口,两个流量面的权限边界必须分开。我在推动修复时,会建议开发给用户可控请求单独建一个出口代理,而不是在业务主逻辑里简单加个判断,这个边界一旦建立,后续很多同类漏洞都能被自动隔离。

代码层面也应该统一封装一个SafeHttpClient工具类,把DNS解析校验、端口白名单、重定向跟随限制、响应敏感信息过滤全部收敛到一个地方,而不是在每个调用curl、requests的地方零散过滤。我见过太多因为"只在一个接口修了,其他接口忘了"导致的SSRF复发。统一封装并配上单元测试,专门构造绕过案例做执行,这类问题才能从根上减少。

7. 测试工程的优先排序:漏洞挖掘中别漏掉这三个易忽略点

7.1 不要忽略"盲SSRF"下的时间探测

盲SSRF没有回显时,许多测试人员会直接跳过,其实时间盲探测一样有效。提交url=http://10.0.0.1:8080如果目标端口开放,请求会快速失败或建立连接;如果端口关闭,连接超时要等很久。把同一地址的开放端口和关闭端口的响应时间做成基线,可以靠"延时差"判断端口状态。更细的还可以对目标网段的多个IP做扫描,代价低且隐蔽。

7.2 非HTTP协议可能比HTTP更危险

多数SSRF测试默认只有http/https,但代码若使用了支持任意协议的库(如curl支持Gopher、Dict、FTP),攻击面完全不同。利用file协议读本地文件是最直接的验证方式;Gopher协议虽然构造起来复杂,但对Redis这类基于TCP明文协议的服务几乎为所欲为,可以直接写入恶意请求。所以测试时不要只试HTTP,多试几个协议,看到响应变化再判断支持范围。

7.3 二次跳转与无限重定向

任何"URL参数后接拼接"的场景,都可能通过#、?等控制实际请求路径。比如代码写了http://www.safe.com/{input},你可以提交../../secret试试路径穿越;代码写了http://api.example.com/?url={input},你提交@127.0.0.1试试凭证混淆。同时注意服务端可能自行拼接路径,导致即使域名无法绕过,路径部分仍然可以精准指向内网服务的特定端点。

8. 常见问题快答

问:SSRF修复中,过滤百度增加了127.0.0.1、localhost、127.x.x.x,是否就够了? 答:不够。只能过滤常见的字面量,十进制IP、DNS重绑定、重定向、URL编码都能绕过,必须配合DNS解析校验和出口ACL。

问:为什么我做的网站只在浏览器里允许访问,后端依然有SSRF? 答:SSRF本质是服务端发请求,用户是否在浏览器能直接访问完全不重要。只要后端接受用户可影响的传入URL,攻击者可以完全绕开浏览器,直接发HTTP请求给后端接口。

问:Gopher协议为什么危险? 答:Gopher能把一串原始TCP数据写进请求里,这意味着可以手动构造Redis协议、MySQL协议等,不只是发个HTTP GET那么简单,可以直接与服务交互。修复时最好在HTTP客户端层直接禁用非http/https协议。

问:SSRF能盈利吗?我听说有人靠挖SRC拿了几万奖金。 答:在漏洞挖掘授权范围内能。云环境上的SSRF常被定级为高危甚至严重,因为它能触碰元数据凭证;内网穿透型SSRF也常被评为中高危。但我必须强调:只允许在授权范围内测试,任何未授权扫描都涉嫌违法,不存在"赚快钱免罪"的说法。

处理SSRF这么多年,我最大的体会是:它不像SQL注入那样"一条语句打天下",而是非常依赖攻击面理解和上下文组合,同一个SSRF在图片剪辑型应用、文档编辑型应用、云上网关型应用中的威胁等级完全不同。真正防住它,靠的不是某个函数写得多严谨,而是从一开始就尊重"用户输入不可信任"这个底线,把请求边界制度化、统一化。希望这篇内容能帮你在下一次代码分析或渗透测试中少走几步弯路,尤其是那些容易让请求悄悄穿过内网的隐蔽坑,早点发现、早点堵上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询