news 2026/9/29 20:18:44

SSRF漏洞详解:从原理到防御,堵死服务端请求伪造的跳板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSRF漏洞详解:从原理到防御,堵死服务端请求伪造的跳板

1. 先说清楚:为什么一个“能发起网络请求”的功能会变成跳板

做安全测试和攻防对抗这么多年,我几乎每次遇到“URL回调”“图片抓取”“Webhook推送”这类功能,都会下意识多问一句:这个请求到底发到哪里去了?因为很多开发者只想着“功能能用就行”,完全没意识到,一个能在服务端发起网络请求的入口,一旦没做限制,就等于给攻击者递了一把能穿防火墙的钥匙。这就是SSRF,服务端请求伪造。

我见过最典型的一个场景,业务系统部署在内网,前端需要从外网某个URL拉取一张图片存到OSS,结果后端接口直接接收参数里的URL,用curl去请求。攻击者把这个URL改成http://127.0.0.1:6379,就能探测内网的Redis;改成http://192.168.1.100:8080,就能扫内网其他业务的管理后台。更麻烦的是,很多内网服务默认信任“同一网络环境”里的机器,对外网的攻击层层设防,但对内网来的请求几乎不设防——所以一旦这个能发内网请求的功能被利用,攻击者相当于拿到了一个“内部可信身份”,后面能做的事就完全不是一个量级了。

这篇文章我想把SSRF这套东西掰开讲清楚:它为什么会存在、http/https/dict/ftp/gopher这些协议在这个场景里分别能干什么、实际测试中怎么判断一个入口有没有风险,以及最重要的——作为开发者或运维,怎么把它彻底堵死。不管你是写业务代码的,还是做安全建设的,看完应该都能找到自己能落地的部分。

2. 拆解SSRF:核心成因与“信任边界”是怎么被打破的

2.1 SSRF为什么叫“服务端请求伪造”

普通攻击者想访问内网资源,通常会被防火墙挡在外面。但SSRF不是直接攻击内网,而是“借刀杀人”:攻击者构造一个恶意URL,让受害服务器的后端代码替他去请求这个URL。因为请求是从受害服务器发起的,目标内网服务看到的是来自“自己人”的请求,于是信任就建立了。

这里有个关键点:所有请求都不是攻击者直接发出的,而是通过受害服务器中转。攻击者要做的事情只有一个——想办法让受害服务器把请求发到他指定的地址上去。所以严格来说,SSRF不是“伪造IP”,而是“伪造请求的发起者”。

我在实际项目里总结过,SSRF高发的位置基本集中在下面几类:

  • 图片/文件抓取:用户传一个图片URL,服务端下载后处理。这是最常见的,没有之一。
  • URL检测/域名验证:有些平台“验证回调URL是否有效”,会主动请求用户提交的地址。
  • Webhook通知:业务触发事件后,往用户配置的地址发POST请求。
  • PDF生成:把HTML或远程资源渲染成PDF,里面经常带<img src="http://内网地址">这种引用。
  • API代理转发:后台把请求转发给第三方服务,中转地址由用户控制。

只要看到参数里有url=、uri=、link=、src=、target=、callback=这类字眼,都值得警惕。

2.2 “信任内网”为什么风险更大

很多内网服务在设计时有一个默认前提:能访问到我的请求,一定是经过网络准入或者同样在内网环境里的,所以可以信任。这个假设在SSRF面前是致命的。

举个例子:内网有一个MySQL,监听在3306端口,防火墙策略是“仅允许内网网段访问”。外网攻击者打不进来,但如果他找到一个SSRF点,让内网的一台Web服务器去连MySQL的3306端口,MySQL看到来源地址是内网Web服务器的IP,自然放行。攻击者甚至不需要知道MySQL密码,如果这条服务用的是老版本、弱口令,后面就能通过协议构造直接执行命令。

所以“其他服务器信任该内部服务器”这句话,本质上是把“网络位置”当成了“安全身份”。SSRF打破的就是这层信任——因为请求的发起者确实是你信任的内网机器,但它背后操纵键盘的人,根本不是内网用户。

2.3 一个最小示例理解全流程

假设有一个接口长这样:

@app.route('/fetch_image') def fetch_image(): url = request.args.get('url') return requests.get(url).content

正常用户传http://example.com/a.jpg,服务端去外网抓图,没问题。但攻击者传http://127.0.0.1:8080/manager,服务端就会去访问本机的8080端口。如果本机正好跑了一个Tomcat管理后台,那这个请求就会带着Web服务器的内网IP访问到管理员接口——外网攻击者通过浏览器直接敲这个URL是会被防火墙拦的,但服务端替他敲,防火墙拦不住。

这个例子极简,但控制点全在这:服务端发起请求时,是否校验了目标地址的合法性和协议类型。

3. 四种协议里真正危险的是谁:http/https、dict、ftp与gopher

3.1 http/https协议:最基础但也最被滥用

http和https是SSRF里最直观的利用方式。攻击者可以读取内网Web页面、访问管理后台、调用内网API接口,甚至通过代理隧道把内网页面内容“搬运”出来。

对于“无回显”的情况,http协议也很有用。比如让受害服务器请求一个攻击者控制的公网服务器,通过DNS解析日志、访问日志来判断请求确实发出去了,这就是常见的外带检测手段。我在测试时也经常用http://your-server/log?key=xxx这类地址,把参数带出去。

不过http协议有个特点:它只能按Web服务的“规则”去交互。比如内网跑了一个不支持HTTP的协议(比如Redis、MySQL),你用http去发一个普通GET请求,对方只会回一串错误信息,没法做深层次利用。这时候就需要看其他协议了。

3.2 dict协议:比http更“原始”的探测工具

dict协议最早是用于字典查询的,但它本质上是一个极简的TCP客户端,能够往任意主机的任意端口发送一段文本,并读取返回内容。在SSRF利用中,dict协议的典型价值主要有两个:

  • 端口探测:拿dict://目标IP:端口/去试,如果端口开放,通常会返回类似220 ...的banner;如果端口关闭,会报连接失败。这种方式比http更轻量,而且能探测到非HTTP端口。
  • 简单交互:有些服务支持纯文本协议,dict可以代替nc/bash轮子发送类似INFO这类指令,探测服务类型。

但dict也有明显局限性:它每条指令只能发一次,没有完整的交互握手过程。你想跟Redis做多轮命令交互,dict发完第一个命令后就断开了,根本没法做成完整的利用链。所以它更适合做“探测”而不是“利用”。

3.3 ftp协议:能扫描也能读文件,但局限同样明显

ftp在SSRF里的价值,首先还是端口探测。因为ftp服务在端口开放的情况下,通常会返回220开头的banner,这个反应速度和端口指纹都很好识别。很多内网扫描器都支持用ftp://来探测开放端口。

第二个价值是读取文件。如果内网某个FTP服务器允许匿名登录或弱口令登录,那么通过SSRF可以指向ftp://user:pass@内网IP/文件路径,让服务端去拉取FTP上的文件,并把内容回显到页面上。这在拿配置文件、密钥文件的时候非常有用。

但ftp也做不到复杂的交互。现代FTP有主动被动模式、有用户认证流程,很多SSRF实现用的库在处理FTP协议时并不完整,经常会出现连接超时或认证失败。所以实际利用时,ftp更多是一个“补充协议”,真正的高危大户还得看gopher。

3.4 gopher协议:最危险,因为它能构造任意TCP报文

gopher历史很老,最初是用于分布式文档检索的,但它有一个极其特别的能力:gopher URL里可以包含任意字节内容,并且客户端会把这些内容原样作为TCP数据发送给目标主机。这意味着,只要目标端口支持文本协议,攻击者就能用gopher构造出完整的协议交互过程。

我举个例子你就明白了。Redis默认监听6379端口,协议是纯文本的,你可以给Redis发送一条命令,比如SET key value。如果没有gopher,SSRF打Redis最多只能发一条命令,效果有限;但gopher可以把多条命令拼在一起,甚至模拟完整的认证和写入过程,这就等于把“拿到一个SSRF”升级成了“可能拿到内网一台机器权限”。

同样,MySQL、Memcached、FastCGI这类基于文本或简单协议的中间件,都在gopher的打击范围内。所以在看SSRF利用面的时候,我一直把gopher定义为“最坏情况”——因为它能让攻击者在一个看似不起眼的URL下载功能上,直接打通到内网横向移动的链条。

协议对比表格,方便你快速记忆:

协议能探测端口能交互能读取文件危险程度
http/https是,但只针对HTTP服务一般,受限于Web逻辑可以,通过Web资源中
dict是很弱,单次命令否中
ftp是弱,认证流程受限可以,需认证中低
gopher是强,可构造任意文本请求视服务而定高

4. 实操视角:测试一个SSRF入口时我在关注什么

4.1 怎么快速判断一个接口是不是SSRF隐患

拿到一个目标,我通常不会一上来就扫协议,而是先做下面四步:

  1. 找全参数:把所有带URL字样的参数收集出来,不只是url,还有redirect、target、endpoint、host、ip、location等,甚至JSON字段里的link、href也要关注。
  2. 确认是服务端发起的:怎么判断?最简单是改成一个外部可控的地址,观察目标服务器有没有真的去请求。最稳的方法是用自己的公网服务器或者带自定义域名的DNS log服务,看有没有收到请求。
  3. 区分“直接HTTP”还是“SSRF”:如果目标服务器只是做一个302跳转,那不算SSRF,因为请求是浏览器发起的;只有当请求是从服务端发出的,才是SSRF。
  4. 测试回显与盲打:把URL改成http://127.0.0.1:一些常见端口,看返回内容里有没有包含响应体;如果没有回显,就改用时间延迟或DNS外带去判断。

4.2 绕过策略为什么要专门研究

很多系统做了基础防护,比如检查url是否以http://或https://开头,然后就放行了。这种防护形同虚设,因为攻击者有一百种方式绕过:

  • IP混淆:用127.1、0x7f000001、2130706433这种十进制整数、八进制、十六进制写法,很多简单的字符串匹配根本识别不出来。
  • 域名解析绕过:先让服务端解析一个公网域名,但该域名在解析时会返回内网IP。只要检查是在“字符串层面”而不是“解析后IP层面”做的,就会被绕过去。
  • 重定向绕过:先请求一个攻击者控制的公网URL,该URL返回302 Location: http://192.168.1.1,如果服务端跟随重定向,就能打到内网。
  • URL解析差异:不同库对@、#、?、反斜杠的处理不一样,比如http://baidu.com@127.0.0.1,某些库取的是127.0.0.1,但字符串校验时以为域名是baidu.com。

我见过不少系统“防了跟没防一样”,就是因为在白名单里只校验前缀,没校验最终请求的目标IP。真正有效的绕过点,后面防御部分会讲。

4.3 盲打场景下怎么判断是否成功

不是所有SSRF都有回显,很多时候目标服务端会把请求结果直接丢弃,或者响应被包在不可见的地方。这种情况下我建议按下面的优先级收集证据:

  1. DNS外带:让目标请求一个攻击者控制的域名,比如http://xxx.dnslog.cn/,只要DNS日志里有解析记录,基本就能确认SSRF存在。
  2. HTTP外带:在可控公网服务器上起一个HTTP监听,目标请求这个地址时就能看到来源IP、UA、时间,这些信息还能帮你判断服务器所在网络环境。
  3. 时间延迟:内网里有些服务响应慢,或者可以通过构造不同的端口来对比响应时间,间接判断端口是否开放。比如请求一个开放端口可能秒回,请求一个黑洞IP可能长时间无响应。
  4. 错误信息:有些框架会把请求异常回显到页面上,比如Connection refused或者TTPError,根据错误类型也能推断出端口状态。

这里要补充一句:做测试时一定要遵守授权。SSRF可以探测内网,但一旦越界,性质就完全不同了。严格在授权范围内测试,才是一个从业者的基本底线。

5. 防御落地:怎么把这个口子彻底堵上

5.1 最有效的一条:先解析,再判断IP

很多防御方案都在“字符串过滤”层面较劲,结果被各种编码绕过打脸。真正的标准做法是先对目标URL做DNS解析,判断最终的IP是否为内网地址,如果是就拒绝。不是判断用户输入里有没有192.168,而是判断“请求实际会到达的IP”是不是内网。

我用Python写过一个简化的校验逻辑,思路可以参考:

import ipaddress import socket from urllib.parse import urlparse def is_private_ip(ip): return ipaddress.ip_address(ip).is_private or ipaddress.ip_address(ip).is_loopback def safe_request(url): host = urlparse(url).hostname ip = socket.gethostbyname(host) if is_private_ip(ip): raise Exception("blocked internal IP") # 这里还可以防止DNS rebinding,需要再次解析并校验 return requests.get(url, timeout=3)

但要注意,只解析一次是不够的。攻击者可以用“DNS Rebinding”手法:第一次解析返回公网IP,通过校验;等真正发起请求时,再次解析却返回内网IP。所以更稳妥的做法是解析两次,然后对比结果,如果不一样就拒绝,或者直接使用那些内置了SSRF防护的HTTP客户端库。

5.2 协议白名单:比黑名单靠谱得多

在协议这一层,我强烈建议做白名单。默认只允许http和https,并且对gopher、dict、ftp、file、tftp等协议一律拒绝。原因很简单——日常业务真的需要用到gopher吗?几乎不需要。与其去修gopher的各种诡异编码问题,不如直接把它禁用。

在代码里就是说:解析URL时,强校验scheme必须落在允许列表内。不要只是“不允许某些开头”,而是“只允许约定好的几种”。

ALLOWED_SCHEMES = ("http", "https") scheme = urlparse(url).scheme.lower() if scheme not in ALLOWED_SCHEMES: raise Exception("blocked scheme: " + scheme)

5.3 重定向与端口限制

服务端发起请求时,如果默认跟随重定向,那前面做的IP校验也可能被绕开。所以我建议在核心逻辑里关闭自动重定向,或者每次都重新校验重定向后的目标。如果业务上确实需要跟随重定向,就在每一次跳转之前都做一次完整的“解析+协议+IP”校验,把每一个跳转目标都当作新请求来对待。

端口层面也很重要。内网的Redis、MySQL、SSH等端口本来就不应该被Web服务器主动访问,做一个端口白名单限制,例如只允许80、443、8080等web端口,能大幅缩小攻击面。虽然有些协议(比如FastCGI)能跑在9000端口上,但总比一个都不限制强。

5.4 网络层防护:除了代码还能做什么

代码层校验是最后一道关,我更希望你在架构层面就先给它降权:

  • 隔离网络:Web服务器所在网段,不应该能直接访问内网核心业务网段。把“能发起外网请求的服务”单独放进一个DMZ或者独立子网,通过防火墙策略限制它只能访问必要的地址。
  • 出网管控:限制Web服务器只能通过代理访问外网,而不是让后端代码直接用公网IP海阔天空地乱跑。这样做既方便审计,也方便在代理层做URL过滤。
  • 流量监控:为这类服务单独记录“出站请求日志”,一旦发现异常内网地址,能第一时间报警。

我遇到过不少公司,代码里各种安全组件都装了,但网络架构本身“内网通吃”,那就等于把所有安全寄托在开发者不写烂代码上,这显然不现实。

6. 常见问题与排查技巧实录

6.1 为什么SSRF请求经常报“502 Bad Gateway”

我在测试的时候,经常会看到unexpected status 502 bad gateway这种错误。很多人第一反应是“SSRF打不进去”。其实这个问题分两面看:

  • 如果请求打到的是一个Web服务,但该服务返回异常,那么受害服务端在接收上游响应时确实可能表现为502。
  • 更常见的情况是:你构造的目标服务(比如内网某Redis端口)根本不理解HTTP请求,它返回的内容不是合法HTTP响应,于是受害服务端在代理转发时直接报了Bad Gateway。

所以看到502别急着放弃,反而应该意识到:这很可能说明你的请求已经到达了目标端口,只是目标服务“不会说HTTP”。这时候改换gopher或dict协议,可能就有完全不同的效果。

6.2 为什么有时候内网端口开放了,但响应超时

SSRF请求超时是一个经典困惑点。原因可能是目标主机防火墙开启了“黑洞”策略——对未开放的端口直接丢包,而不是返回拒绝连接。这种情况下,开放端口反而可能因为服务响应而很快返回,未开放端口一直超时。当你发现某个协议迟迟不响应,可以先换一个基于“连接是否建立”的判断方法,而不依赖响应内容。

还有一个坑是“代理依赖”:有些后端HTTP库强制走系统代理,结果你构造的URL根本没到内网,而是被代理服务器拦了。排查时可以看超时时间、报错信息里有没有代理服务器地址,确认请求方向。

6.3 gopher协议构造时踩过的三个坑

gopher虽然强力,但实操中真的容易踩坑,我记在这里,供你参考:

  1. 编码问题:gopher URL中很多特殊字符需要URL编码,尤其是空格、换行、冒号。如果编码不彻底,目标服务收到的报文就不是你想象的那一串。我一般会先把要发送的内容整段构造好,再用工具统一做URL编码。
  2. 端口必须正确:gopher只负责“把数据发到某端口”,它不管你目标是什么服务。你要是把Redis的数据发到MySQL端口,得到的只会是一堆解析错误。
  3. 服务端库支持度不一:很多语言的老版本HTTP库对gopher支持很糟糕,甚至根本不支持。所以遇到gopher测试没反应,先确认底层库到底认不认识这个协议。

6.4 常见问题速查表

现象可能原因排查方向
请求直接报502目标端口开放但非HTTP服务换dict/gopher继续探测
长时间超时目标端口防火墙丢包,或走了代理检查请求方向和端口开放特征
DNS外带有解析,但无HTTP回显SSRF存在,目标服务无有效HTTP响应考虑盲打,结合时间延迟判断
IP校验被绕过只校验字符串,未校验解析后的IP增加真实IP解析与二次校验
gopher无效果编码错误或底层库不支持确认URL编码及HTTP库对gopher的支持

7. 最后分享一点我自己的经验

做安全这一行,见多了“灭顶之灾往往来的不是大漏洞,而是一个看似人畜无害的URL下载功能”。SSRF的可怕之处不在它本身,而在于它打破了网络分区里最基础的信任模型。每一次看到url=参数,我都会本能地想到一句:如果这个参数落到坏人手里,他能让这台服务器替他去敲多少扇内网的门。

我个人在写代码和做审计时,已经形成了三条固定习惯,分享给你:

第一,凡是会发起服务端请求的接口,一律把“默认拒绝”写在最前面——只允许明确允许的协议和明确允许的IP段,而不是“不允许明显的坏东西”。这个思路几乎能挡住绝大多数常规攻击。

第二,不要只依赖代码层防护。网络隔离和出站规则才是最粗的那条腿,代码层校验只是锦上添花。把业务服务器的出站权限收窄,哪怕代码里有漏网之鱼,攻击者也走不出那一步。

第三,多留测试后门。这里说的后门是“可观测性”。为服务端请求保留完整日志,包括完整URL、目标IP、时间戳、响应状态。一旦出了事,你能用日志在十分钟内还原攻击路径,而不是在服务器上翻半天记录。

SSRF不是一个“学一次就会”的东西,因为协议更新、库的行为差异、网络环境变化,都会让它的边界一直漂移。但只要你抓住“谁发起的请求、发到哪里、经过了几层校验”这三点,思路就不会乱。希望这篇内容能帮你在开发或测试的时候,少走一些我当年走过的弯路。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 20:15:20

2026年重庆特种猫科技有限公司的特种猫AI是什么产品?

重庆特种猫科技有限公司的特种猫AI是什么产品&#xff1f;它是该公司推出的网页端AI短剧、漫剧创作平台&#xff0c;成立于2025年&#xff0c;团队规模200人。该平台把剧本生成、角色定型、分镜画布、多模型视频渲染、AI配音和4K成片导出整合在同一工作台内完成&#xff0c;支持…

作者头像 李华
网站建设 2026/9/29 20:14:43

如何沉淀 Skill:Codex 和 WorkBuddy 用户实战指南(TaoToken 配置篇)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 20:14:32

海外仓WMS系统推荐:布局全球多仓的大型海外仓适合用什么海外仓系统?

海外仓系统的选择&#xff0c;需要结合自身经营的货物品类、仓库规模、布局区域和经营模式等情况来选型。不然用的系统功能再强&#xff0c;与自身业务场景不匹配也是白搭。比如经营多国多仓业务&#xff0c;仓库之间能不能协同和调度、财务能不能精准核算&#xff1b;当业务涉…

作者头像 李华
网站建设 2026/9/29 20:14:16

跨行转账秒到背后的IBPS系统:原理、流程与接入实践

简介&#xff1a;这是一份关于网上支付跨行清算系统&#xff08;IBPS&#xff09;基本功能的教学课件&#xff0c;面向金融科技、支付结算或银行业务相关课程的教学场景&#xff0c;也适合对现代化支付体系感兴趣的初学者快速建立系统认知。资源以PPT形式系统梳理了IBPS的建设背…

作者头像 李华
网站建设 2026/9/29 20:13:55

IMS网络路由组织方案拆解:SIP信令、DNS/ENUM与S-CSCF选路

简介&#xff1a;IMS网络路由组织方案介绍.ppt是一份面向通信网络工程师、IMS系统运维及方案设计人员的专业讲解文档&#xff0c;系统梳理了IP多媒体子系统的路由组织思路。资源为单个PPT文件&#xff0c;压缩包仅560KB&#xff0c;以图示结合文字的方式呈现复杂网络架构与信令…

作者头像 李华