资讯动态

SSRF服务端请求伪造:原理、利用与防御实践

发布时间:2026/10/22 23:59:23 来源:尧图企业网站定制
先说一个我自己的经历。某次线上排查发现一台业务服务器在半夜不断向内网发起非常规的请求抓包一看目标端口从 3306 扫到 6379再扫到 11211。顺着调用链往上追问题落在了我们自己做的一个“URL预览”功能上——前端传一个链接后端为了生成预览信息直接拿这个链接去请求。这其实就是一场典型的服务端请求伪造也就是 Security 圈里常说的 SSRF。SSRF 的全称是 Server-Side Request Forgery中文一般叫“服务端请求伪造”。很多刚入门的朋友一听这个名字就觉得高深其实它表达的意思非常直白服务端接收了一个攻击者可控的 URL然后以服务端自己的身份去请求这个地址。关键在于“服务端发起的请求”而不是用户的浏览器发起的请求。浏览器发出的请求只能访问公网资源但服务端发出的请求往往可以够到内网里那些根本不对外暴露的主机和服务。这篇内容我会从零开始拆这个漏洞。适合三类人看一是刚接触 Web 安全、想系统理解漏洞原理的初学者二是写后端接口、担心自己功能被利用的开发者三是做安全测试或线上排查、需要快速定位问题的人。整篇不涉及高深数学也不需要你提前掌握多少协议知识只要你写过接口、调过接口就能看懂。1. SSRF 的成因拆解服务端为什么会“帮”攻击者发请求1.1 从一次正常请求说起要想理解 SSRF先得弄明白普通网络请求是怎么流动的。假设你在浏览器里访问一个公开页面请求是你发出去的服务器收到后把页面返给你。整个过程里发起请求的是你的浏览器终端IP 是你的 IP服务器只负责响应。现在换个场景。你写了一个后端接口它要做一件事从某个 URL 获取 JSON 数据解析之后存到数据库。这时候你自己的服务器就变成了“请求者”。服务端代码里写了类似“去访问这个地址并读取内容”的逻辑而这个地址可能是写死在配置里也可能来自用户输入。SSRF 之所以成立需要有三个要素同时满足服务端代码中存在“主动发起网络请求”的逻辑目标地址由外部输入控制而不是固定写在代码里服务端对输入的目标地址没有做足够严格的限制。当这三个条件凑齐攻击者输入一个内网地址比如 192.168.x.x服务端同样会去请求它。因为请求是从服务端发出的内网会认为这是可信来源于是把本不该暴露的数据或服务响应返回给服务端。这就是“伪造”的含义——伪造的是发送方身份攻击者借了服务器的名义去探访内部资源。1.2 漏洞的核心条件输入可控加请求触发很多初学者会把 SSRF 和普通的“URL 跳转”混淆。URL 跳转是服务端返回一个 302 让浏览器跳转到指定地址跳转动作发生在浏览器端。SSRF 则是服务端自己拿着 URL 去请求动作发生在服务端。两者最大的差别就是“谁在执行请求”。从触发方式上看SSRF 往往藏在一些看起来很普通的功能里。比如上传图片时填写一个图片 URL前端展示前需要后台拉取提交一个分享链接后端要抓取网页标题、描述、缩略图Webhook 回调地址由用户填写触发事件后服务端去回调服务器需要代理请求外部接口而代理地址直接取参数值。这些功能表面上是“为了用户方便”但都共同指向一个后端行为按用户提供的地址由服务器去访问它。只要地址可控内网就会暴露在这个请求半径之内。1.3 最容易写出 SSRF 的代码长什么样来看一段很典型的伪代码用 PHP 的 file_get_contents 实现远程拉取内容// example: /fetch?urlhttps://example.com/robots.txt $url $_GET[url]; // 用户可控 $content file_get_contents($url); // 服务端直接请求 echo $content;这段代码的逻辑非常清晰从参数取 url请求它然后把响应当作页面内容返回。功能上没毛病可是攻击者只要把参数从https://example.com/robots.txt换成http://127.0.0.1:3306服务端就会尝试连接本地数据库端口。如果换成内网某台主机就相当于以服务端为跳板向内网发起访问。Python 场景也很常见用 requests 库的话问题更隐蔽# example: /proxy?urlhttp://api.example.com/data from flask import request import requests def proxy(): target request.args.get(url) resp requests.get(target, timeout5) return resp.text代码写得没问题从功能角度它确实是个现成的代理。但这正是问题所在任何用户可以来用任何地址都可以填目标端口、目标协议都不受限。Java 里 HttpClient、Go 里的 http.Get本质都一样。凡是把外部输入直接拼进请求目标的操作基本都是高危写法。代码审计时看到这一类写法就要打起精神问一句这个输入真的可控吗输入做了什么限制吗即使没有直接回显服务端发起请求本身也可能被用来探测端口、标识可用服务。2. 业务场景与危害面先知道哪些功能要重点留意2.1 高危功能入口盘点把所有可能出现 SSRF 的功能点列一遍你在排查代码时就能按图索骥。我自己整理的高频区域有这些功能类型典型实现风险说明图片加载/抓取传入图片 URL 后服务器下载并转存可探测内网 HTTP 服务、读取文件链接预览/分享卡片抓取标题、描述、封面最常见的 SSRF 入口开发最常忽略文档导入/转换通过 URL 导入文件再转 PDF 或图片可配合跨协议扩展为读取本地文件Webhook 回调用户填回调地址后端事件触发时 POST 数据地址完全可控且能携带内部数据接口代理/转发后端转发请求到前端指定的地址等于是送了一个可控代理文件下载服务用户填地址后端下载到本地内网文件甚至云元数据都可以访问这些功能出现在后台管理、内容发布、用户个性化配置等场景里。尤其是后台管理系统因为访问者少开发时往往默认“公司内部人使用”限制做得更松。可一旦攻击者通过其他漏洞进入后台这些 SSRF 入口就成了横向移动的跳板。2.2 SSRF 与 CSRF经常被搞混的两个“请求伪造”初学阶段容易把 SSRF 和 CSRF 放在一起记因为他们名字里都有“RF”——Request Forgery。但两者的含义可以差出十万八千里。维度SSRFCSRF发起方服务端受害用户的浏览器攻击者控制点URL/目标地址伪造整个请求动作主要影响内网信息泄露、服务探测以用户身份执行操作请求携带的身份服务端自身的权限用户的登录态典型场景图片抓取、链接预览更改密码、转账、改邮箱一句话总结CSRF 是“冒用用户身份”SSRF 是“借用服务器能力”。两者都需要重点防护但思考的方向完全不同。我在排查项目时遇到“请求伪造”相关的问题第一步都是先判断请求是发生在哪个上下文环境里再判断该往哪个方向排查。2.3 从请求者的角度理解影响面理解 SSRF 的影响面关键要理解一个概念服务端发起请求时携带的是服务器自己的 IP 和权限。外网攻击者平时到不了内网但服务器就生存在内网。攻击者能让服务器替他访问内网的任何地址就等于给服务器装了一双“遥控的眼睛”。这双眼睛能看到什么取决于服务端放在哪、能拿到什么权限。如果服务器能访问数据库那攻击者可以通过探测确认 3306 端口是否开放如果服务器在云环境里访问某个内部元数据地址可能拿到密钥和临时凭证如果服务端能访问本地文件跨协议读取可以让任意文件内容进入响应体。这些单看任何一个好像都还行串起来就是一个完整的攻击链。3. 利用视角下的深层危害3.1 从一个端口扫描的角度理解探测行为攻击者拿到一个 SSRF 漏洞后第一件事往往不是直接打内网应用而是先做信息收集。怎么收集靠的是请求时间差和返回内容差异。比如用 SSRF 访问http://127.0.0.1:8080和访问http://127.0.0.1:81响应时间、报错提示、页面内容都会有细微区别。端口开放时连接建立得快端口不通会超时或连接失败有些服务会暴露 banner 信息返回协议头部。攻击者把这些结果记录下来就能在几十分钟之内摸清内网一段 IP 里哪些主机活着、哪些端口开放、运行着什么服务。这种探测最大的特点是隐蔽。从攻击者的角度来看请求的源 IP 是受攻击服务器本身的 IP不是攻击者的 IP。内网日志里看到的是一台业务服务器在“正常地”访问其他内网主机没有明显的异常终端标识。这比攻击者直接向内网发包要隐蔽得多。3.2 跨协议请求不只是 HTTPSSRF 经常被误解为“只能探测内网的 HTTP 服务”。事实上服务端发起的请求完全可以携带其他协议。PHP 的 file_get_contents/curl、Python 的 requests、Java 的 URLConnection在开启允许协议的配置下可以访问 file、gopher、dict、ftp 等协议。这里我重点说两个协议理解这两个协议的价值就能理解跨协议攻击的严重性。第一个是 file 协议。当后端允许 file 协议时攻击者可以直接通过 URLfile:///etc/passwd读取服务器本地文件内容。如果响应内容会回显那你等于送出了一个任意文件读取漏洞。即使不回显攻击者也可以用盲读的方式探测文件的存在性。第二个是 gopher 协议。gopher 协议的核心特点是允许客户端构造任意字节流发送给服务端这就意味着攻击者可以“模拟”一次完整的 TCP 会话——比如向本地的 Redis 发送命令。虽然现代 Redis 默认配置已经不允许非认证访问但在内网环境里仍存在配置不当的老实例。一旦打出这一步就从网络层探测升级到了代码执行级攻击。需要注意不是所有后端语言都默认支持这些协议很多拦截策略也能限制协议类型。但确认协议支持情况之前攻击者只要多试几个前缀就能知道后端到底放开了多少能力。防御者在设计时不能只盯着 HTTP必须假设所有协议都可能被调用。3.3 为什么这些危害对业务是致命的把上面几点串起来一个完整的 SSRF 攻击路径大约是这样的先发现一个可控 URL 的接口然后探测内网存活主机与端口接着发现某个老的内部应用或数据库再利用跨协议读取文件或写入命令最后撬动整片内网。这里面每一步都不需要极高深的技巧真正困难的反而是“能不能发现第一个入口”。一旦入口被确认后边的操作非常机械化。这也是为什么现在的一些安全评审会把 SSRF 的严重级别定得和 SQL 注入差不多——单看漏洞本身只是“能发请求”但结合内网环境和业务逻辑它可以演变成不同层级的真实入侵。4. 防御设计为什么很多限制方案会被绕过4.1 常见的限制手段说到 SSRF 防御大家第一反应通常是“过滤内网 IP”。这个想法没错但实现起来远比想象中复杂。常见的防护措施有限制请求协议只能是 HTTPS/HTTP对目标地址做主机名白名单校验对解析后的 IP 做内网地址黑名单限制禁止请求跳转或限制跳转次数通过代理网关统一出网业务层不直接发外部请求。这些方案单独看都有道理但组合起来才有效。真正的问题是很多项目只做了其中一条而且校验逻辑本身存在细节漏洞。下面重点说说这些“细节漏洞”长什么样。4.2 绕过思路的技术根源先说一个冷知识同样是“127.0.0.1”可以写成很多不同形式。比如十进制整数形式、十六进制形式、短横线形式、IPv6 映射形式。如果后端只做字符串过滤判断“字符串中是否包含 192.168”那攻击者只要换一种表示形式就绕过了。解析差异也是一个大坑。有的方案在进入请求前先做一次域名解析解析出 IP然后拿这个 IP 做内网判断。但内部客户端请求时往往会再做一次解析。如果两次解析的结果不一样——也就是所谓的 DNS 重绑定攻击——那你校验时看到的是公网 IP真正请求时拿到的却是内网 IP。既然校验和实际执行用的是两次解析结果那中间就是天然的绕过空间。还有跳转处理。很多开发者想到了限制跳转但方案是“只允许一次跳转”。攻击者构造一个公网地址这个公网地址再 302 跳转到内网地址。第一次校验通过了跳转后就没有第二次校验了。这种绕过实现起来非常简单根本不用理解复杂原理试一次就能成功。再一个常见坑是 IP 判断不全面。只过滤了 IPv4 的内网地址段但没过滤 IPv6 的内网地址段或者没过滤本机回环地址的各种别名形式。攻击者的想象力远比防御者的规则表丰富漏一种就相当于没防御。4.3 一套相对可靠的防御方案踩过不少坑之后我现在比较推荐这套组合策略协议白名单只允许 HTTP 和 HTTPS其他协议一律当场拒绝。实现上用标准库解析 URL 的 scheme 部分不要用简单的是否包含判断。域名解析与 IP 多重校验拿到目标地址后先解析域名得到 IP 集合对这个集合做内网地址判断。内网地址段要覆盖 IPv4 和 IPv6 的常见保留段包括回环、链路本地、私有地址、保留地址等。请求解析与实际请求拆分DNS 解析结果只作为检查用实际请求时用一个代理商层代理或网关让网关再校验一次出口目标。两层都允许才放行从根本上规避 DNS 重绑定。禁止跳转客户端库里的跟随跳转能力全部关闭。如果需要支持跳转就在每一跳之后重新执行校验绝不信任上一次的校验结果。统一出网构建一个独立代理或出网网关业务服务器不直接访问外部所有外联请求必须经过网关。网关配置目标白名单未命中的请求直接拒绝。这套方案不能说是绝对无法绕过但它把需要攻击者同时利用的漏洞点提升到了多个。想绕过得同时处理协议问题、解析差异、跳转处理再穿越网关难度就不是单纯字符串过滤能比的。5. 检测、排查与代码审计要点5.1 日志侧怎么发现异常平时做线上巡检日志是最容易发现 SSRF 的入口。需要重点关注几类日志服务器对外发起请求的 IP 和端口记录同一来源短时间内大量访问不同目标的端口高度可疑带有 url、target、callback 等参数名的请求日志如果访问目标里出现了内网地址段基本可以判定存在问题业务日志中同一会话内出现高频连接失败记录可能是在探测端口。有一说一纯日志检测对 SSRF 的覆盖效果有限因为正常的业务外联也会产生大量日志。更靠谱的方式是网络侧监控做流量画像或者直接做代码审计从源头发现问题。日志更适合用于“发现了异常之后回溯攻击路径”而不是作为第一道防线。5.2 代码审计时要盯住的关键函数代码审计时我一般从哪些函数看起不同语言有不同的关注列表PHPfile_get_contents、curl_exec、fopen、readfilePythonrequests.get/post、urlopen、httpx 调用JavaHttpURLConnection、HttpClient.send、OkHttp 请求Gohttp.Get、http.Post、net.Dial只看关键函数还不够还要追一层调用链。确认传入的参数来源、是否经过过滤、过滤发生在哪一层。有的项目在入口处做了全局过滤那只需要检查过滤逻辑覆盖了哪些函数调用有的项目只在部分路由里做了过滤那其他路由全部是裸奔状态。我习惯用“谁能控制这里面的参数”来作为出发点。如果参数来源是后端配置文件或环境变量风险较低如果参数来源于 GET/POST 参数、请求头或用户上传的内容就要排查所有可能的校验路径。面试时很多人能背出 SSRF 的定义但一问“你负责的项目里哪个接口存在这个风险”就卡住了。解决这个问题的唯一办法就是顺着代码从数据入口一路跟到请求函数。5.3 常见问题与排查技巧速查最后整理一个我在日常交流中经常被问到的问题列表以问答形式给出来方便快速定位。问题判断思路处理方向为什么限制了内网 IP 还是会被打可能是校验和解析不是同一份结果也可能是 IP 格式变体或 IPv6 漏配统一解析点检查所有内网地址段返回内容看不到任何内部信息是不是就没危害即使无回显也可用于端口探测和盲打不能只依据回显判断风险禁止了 http 协议就能解决吗如果后端支持其他协议提出了也未必堵死协议白名单设置在解析层服务端不支持时只能完全禁用外联吗不建议禁用业务往往需要外联用专门的出网代理做白名单管控开发测试环境能不能放开限制测试环境的内网设备通常和线上共享环境间安全等级建议保持一致这几个问题对应的是实际场景里的高频误区。单独记结论不难难的是遇到具体的业务场景时综合判断。我自己的经验是把校验逻辑抽象成一个独立的公共组件而不是散落在每个接口里写一遍。散落的逻辑最容易出偏差一个接口写一种过滤最后总有漏网之鱼。写了这么多核心还是要回到“为什么”上。SSRF 的成因不复杂复杂的是它对系统架构的牵动。真正见过一次服务器被当跳板扫内网你就能理解为什么我在每次代码评审时都要追着问“这个 url 是从哪来的”。安全不是靠某一个神级方案而是靠每一个细节都不给攻击者留可乘之机。希望这篇内容能让你在遇到类似逻辑时多留一个心眼。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑