资讯动态

SSRF漏洞实战详解:从原理、绕过到防御的完整指南

发布时间:2026/10/17 8:38:41 来源:尧图企业网站定制
1. 从一次误报排查说起SSRF到底离我们有多近上周帮某团队排查一个线上告警安全设备报了疑似横向探测内网22端口一开始大家以为是被扫描器盯上了结果翻应用日志发现流量是从我们自己的业务服务器发出去的。顺着请求链路查下去问题出在一个用来下载远程头像的接口——它接收一个url参数然后服务器直接把这个URL请求了一遍。这已经不只是扫描问题了这是服务器被当枪使替攻击者去访问了内网。这个漏洞类型就是今天要聊的主角Server-side request forgery服务器端请求伪造简称SSRF。SSRF这类漏洞严格来说不算是某种新潮攻击但它在现代应用架构里反而比以前更能打。以前的应用大都是单体一个Web应用对外暴露的接口有限SSRF就算存在攻击面也就是本机那点东西。但现在不一样了微服务拆了一堆内部接口Redis、Elasticsearch、配置中心、云上那一堆管理端口全在内网边界防护只做了外部一层一旦攻击者能借你服务器的身份发请求里面基本是裸奔状态。更麻烦的是SSRF的出入口经常被业务需求包装得很自然——用户传一个图片链接、传一个回调地址、传一个Webhook URL这些看似普通的功能背后全是服务器主动去访问外部地址的逻辑每一个都是SSRF的潜在触发点。所以现在无论是做渗透测试还是做代码审计SSRF都是我必查的一类点。这篇文章就用某知名漏洞实训平台圈内都叫它靶场上那个经典的SSRF专题来拆解。这个专题分了好几关从最简单的参数直接可控到带过滤、带白名单、带重定向限制的情况一层一层把人从知道SSRF带到能利用SSRF再到知道怎么防SSRF。我前后花了一个周末把这些关卡过完有不少关是通过看了提示才过的整个过程踩的坑和总结的规律比单纯看理论文档学到的东西多得多。2. 先搞明白SSRF的本质服务器发出去的请求凭什么可以被伪造2.1 正常请求和伪造请求的差别要理解SSRF先把一条正常的请求链路画出来。用户浏览器发起一个请求到你的服务器服务器再根据参数去访问另一个资源把结果拿回来给用户。比如一个图片缩略图功能用户提交https://example.com/a.jpg服务器拿到这个URL自己去下载图片然后缩略返回。整条链路里服务器是发起下载的那一方连接的目标是example.com。问题来了如果用户提交的URL不是公开网站而是http://192.168.x.x:8080/呢服务器不知道这个地址是内网地址它照样会去请求。服务器对URL后面的目标没有任何是不是该访问的判断能力它只会执行。这就是SSRF的核心——攻击者控制了一个本应由服务器决定的目标地址让服务器帮他去访问。这个帮字分量很重。因为服务器处在内网边界它拥有访问内网资源的权限这个权限是外部用户本来没有的。攻击者通过SSRF等于拿到了一个跳板可以用服务器对内网的访问能力去探测、攻击、读取。2.2 实战中最常见的几类SSRF触发点从我审计过的代码和看过的漏洞报告来看SSRF的高发位置其实挺集中基本就是下面几类远程图片/文件抓取功能用户传URL服务器抓取后展示或入库。典型的如头像上传、URL缩略图、商品图片采集。Webhook/回调通知功能用户在后台配置一个回调地址服务器在后面某个事件发生时去POST数据过去。比如支付回调、消息通知。PDF生成/截图服务传一个URL进去服务端启动无头浏览器去访问并渲染成PDF或截图。这类特别危险因为无头浏览器支持JavaScript能做的事情更多。文档/数据导入导入一份远程XML、CSV、JSON服务器会自己去拉取文档内容。API代理/转发服务服务器作为API Gateway把前端请求转发到后端有些实现里会允许前端指定部分目标地址。判断一个接口有没有SSRF风险最简单的方式就是看它有没有用户可控的URL/地址参数并且服务器会主动去访问。这个特征足够用来做一个快速的代码审计扫描了。2.3 为什么说SSRF危害性被严重低估很多开发者第一次听到SSRF的反应是这不就是让服务器去访问一个地址嘛能有多大危害它又不是直接执行命令。这个想法很危险。SSRF的直接危害看目标主机是什么但常态下攻击者会做组合拳内网资产探测通过响应时间、端口状态、响应内容的差异把内网存活主机和开放端口摸一遍。读取本地文件如果应用允许file://协议直接读服务器上的文件如密钥、配置、代码。攻击内网服务Redis未授权、Elasticsearch、MongoDB这类很容易被直接打穿。尤其gopher://协议的支持会放大这个能力甚至能打Redis写计划任务拿主机权限。窃取云服务器元数据云环境下服务器能访问169.254.169.254这个特殊地址里面挂着临时密钥、角色凭证。拿到以后等于拿到了你云账号下这一台机器的完整权限甚至能横向接管其他资源。单看一条都只是请求合在一起就是一条完整的攻破链路。这也是为什么现在OWASP把SSRF列进Top 10越来越多的扫描器和WAF都在专门盯这个。3. 靶场实战从直接回显到内网穿透的完整利用链3.1 第一关一个不带任何防护的简单案例实训平台上的SSRF第一关非常简单它的业务场景是商品库存检查有一个接口会接收一个URL参数然后服务端提醒你去访问这个URL查看商品。我直接把参数改成http://192.168.0.1:8080/admin服务器返回了一个内网管理后台的页面。这一关想说明的道理很直白只要URL是用户可控的且服务器直接把响应内容返回给用户这个SSRF就是直接回显的。回显意味着攻击者在外部就能看到内网响应内容利用效率极高。在我实际测试中遇到过的大多数简单案例也都是这样业务方做了很粗糙的URL参数校验比如看看字符串里有没有http开头的字样甚至根本不校验直接让请求发出去。这种情况下基本就是把内网敞开给攻击者。3.2 进阶没有回显的时候怎么判断有没有通靶场后面有一关开始做了限制它不直接返回响应内容只告诉你请求完成或者请求失败。很多人在这一步卡住因为看不到回显总觉得没法利用。其实SSRF在没有回显的情况下依然可以做很多事情靠的是以下三类侧信道信息响应时间差异请求一个内网存活IP的80端口和请求一个不存在IP的80端口返回时间有明显差异。前者如果有服务握手时间短响应快后者直接超时或拒绝。这个差异可以帮你绘制内网存活地图。响应状态码差异即使不返回页面内容很多实现会返回HTTP状态码或简单的成功/失败提示。状态码200和404已经透露了端口是否开放、路径是否存在。错误信息的泄露有些服务器会把后端的报错原样抛出比如Connection refused就说明端口没开Connection timed out说明IP不可达但请求确实发出去过。我测试过不少接口即使它只回显OK我用它去请求本地的不同端口根据返回OK/ERROR就能把常见端口扫出来。这种方法效率低但有效而且不需要任何额外工具纯靠业务接口就能实现。3.3 云环境下的杀手锏元数据服务攻击实训平台专门有一关是考云元数据的这也是现代SSRF利用链里含金量最高的一环。在公有云环境里每台云服务器内网都能访问一个特殊的链接http://169.254.169.254/latest/meta-data/。这是云厂商的元数据服务上面挂着这台服务器的实例ID、主机名、内网IP、安全组信息更重要的是如果配置了实例角色还能拿到临时访问密钥Token。攻击思路是先通过SSRF确认内网通然后直接替换URL为元数据地址http://169.254.169.254/latest/meta-data/iam/security-credentials/返回的是角色名继续拼上角色名就能看到AccessKeyId、SecretAccessKey、Token。之后攻击者完全不需要服务器本身了他在自己的电脑上用云厂商的SDK用这组临时凭证去操作资源。这个点的可怕之处在于它把打入一台服务器升级为接管整个云账号的部分权限。我在靶场里跑通这条链的时候后背都发凉——因为现实中很多云环境恰恰默认给ECS配置了角色而防护方往往只关注公网入口忽略了元数据这个内网终点的暴露。3.4 别小看file://协议从访问网络到读文件很多入门的SSRF演示只会说可以用http协议访问内网但实训平台有一关专门考了非HTTP协议。把URL换成file:///etc/passwd服务器直接把本地/etc/passwd的内容读取返回了。如果攻击面是Windows主机那就是file:///C:/Windows/win.ini或者直接读应用配置文件。这背后反映的问题是很多编程语言里的URL解析库默认支持file协议但业务代码只检查了前缀是http或https没有限制协议类型。代码写着if (url.startsWith(http))但file:///etc/passwd也以file开头吗很多情况下校验的是http开头那file://就绕过去了有的校验是url.contains(http)那更容易构造变体。防御端一个小的校验遗漏攻击端就能直接从网络访问升级为服务器文件读取。这不是理论推演我见过的国内厂商漏洞报告里这种SSRF任意文件读取的组合占比很高。4. 各种过滤绕过哪些手段真的有效哪些只是看起来美实训平台后面几关全是在做防护条件每一关都不一样有的做IP黑名单有的做域名白名单有的做协议限制有的做重定向限制。刷完这几关最大的收获是理解了一个现实SSRF的防御往往比利用更复杂而绕过本质上是解析差异和信任边界的对抗。4.1 IP与域名解析差异导致的过滤绕过最常见的防护是黑名单比如禁止请求127.0.0.1、192.168.0.0/16这类内网地址。绕过方式千奇百怪十进制IP127.0.0.1写成2130706433不少URL解析库能正常识别。短IP/进制转换127.1等效于127.0.0.10177.0.0.1是八进制写法。IPv6映射[::ffff:127.0.0.1]绕过只匹配IPv4的黑名单。URL编码把127.0.0.1编码成127%2e0%2e0%2e1如果校验发生在解析之前、请求发生在解析之后就可能绕过。符号与#符号http://expected.com127.0.0.1/admin在部分语言里实际连接的是127.0.0.1但字符串层面看起来域名是expected.com。DNS重绑定第一次解析域名时返回公网白名单IP校验通过后第二次解析返回内网IP。利用的前提是服务器对同一个域名做两次DNS解析这在很多HTTP客户端里是真实存在的。这些绕过手段里前几种我在靶场验证过是有效的而DNS重绑定需要自己起一个域名和DNS服务器实操成本高但真实渗透中遇到严格白名单时它可能是唯一的路。4.2 白名单域名的信任陷阱有一关的防护是域名白名单只允许请求.example.com后缀的域名。直觉上的绕过方式是把域名写成example.com.evil.net因为字符串里确实带有example.com。实测这个不行因为正确的白名单校验通常会解析并比对完整域名后缀而不是做子串匹配。但白名单真正的坑在重定向里。假设允许访问http://legit.example.com/但这个合法域名下有一个路径会302跳转到他站如果服务器跟随重定向那么最终请求就被带到了任意地址。更典型的是开放重定向漏洞结合SSRF白名单校验通过了第一个域名跳转后到达内网IP全链路合法。平台这一关的教学点非常清晰白名单校验只能针对最终请求的Host做校验任何先校验再跳转的设计都等于没校验。我在测试里自己实现了一个简单的跟随重定向版本发现只要目标域名上有任意一个开放重定向接口白名单就是摆设。4.3 协议限制的作用边界协议限制做得好的应用会把允许的协议限定死成http和https这样file://、gopher://、dict://都被挡在外面。但光是限定协议还不够http://169.254.169.254/本身就是HTTP协议协议限制管不了元数据攻击所以协议白名单通常是和IP限制配合使用的。实训平台上有一个关卡专门禁止file协议但没禁gopher于是我用gopher://打的本地Redis绕过了协议校验。这提醒我协议限制如果只做黑名单永远会有漏网的协议尤其是gopher这种能把任意TCP字节流打过去的协议后缀名和端口都很难用简单规则拦住。4.4 我实测过的绕过优先级排序刷完这些关卡我给自己整理了一个SSRF绕过绕过优先级表验证过的标注了已实测绕过思路应对的防护实测有效性成本URL编码/十六进制/十进制IP简单IP黑名单高低符号、#符号、路径混淆字符串级白名单中低IPv6映射IPv4黑名单高低重定向绕过域名白名单高中DNS重绑定域名白名单高高gopher/dict协议协议黑名单高中并发/模糊测试请求频率限制中中这个表格看起来简单但在做防护评审的时候特别有用。面对一个SSRF风险点先看它用的什么语言、什么HTTP库再看校验是在哪个层面做的就能快速判断哪些绕过适用。5. 防御SSRF能落地到代码的几种设计5.1 最优先限制请求目标为IP白名单/网段真正有效的SSRF防御通常不是靠过滤URL字符串而是靠在网络层限制服务器出网行为。说白了就是回答一个问题这台服务器到底需要访问哪些目标如果业务只需要访问公网固定几个API那就在防火墙/安全组上配置出网白名单只允许到指定IP和端口其余出站流量全部阻断。这个方案的缺点是业务变化时可能误伤但它是最彻底的——因为无论攻击者怎么构造URL服务器本身就没有去内网的路。如果是内网服务间的调用尤其是那种这台应用就是要去内网访问另一个服务的场景那就要做更细的IP/网段限制只允许访问指定服务所在的IP段和端口禁止访问网关上其他网段。我在给某团队做方案建议时明确提出过一句话先把出网收敛做好再谈应用层过滤。很多团队把精力花在写WAF规则、写URL校验但服务器本身能出网到所有内网IP那攻击者只要绕过了应用层那层薄薄的正则就畅通无阻了。5.2 应用层解析后的IP再校验而不是字符串校验如果只能在代码层面做防护这里有一条非常核心的原则永远不要校验用户给的URL字符串本身要校验URL解析之后得到的Host和IP。以Python的urllib为例一个相对可靠的流程是import socket from urllib.parse import urlparse ALLOWED_DOMAINS [.example.com] BLOCKED_IP_PREFIXES (127., 10., 172.16., 192.168., 169.254.) def is_safe_url(url: str) - bool: parsed urlparse(url) if parsed.scheme not in (http, https): return False host parsed.hostname if not host: return False # 第一层域名后缀白名单 if not any(host.endswith(domain) for domain in ALLOWED_DOMAINS): return False try: # 第二层解析所有IP并检查 ips socket.getaddrinfo(host, None) except socket.gaierror: return False for ip_tuple in ips: ip ip_tuple[4][0] if ip.startswith(BLOCKED_IP_PREFIXES): return False if ip ::1: return False # 更严谨的IP段判断需要用ipaddress模块 return True这个代码有几个关键点用的是urlparse解析后的hostname不会受、#等符号的干扰。用socket.getaddrinfo解析出最终的IP再检查IP地址是否命中内网段。域名白名单和IP黑名单同时生效避免只靠一个维度。但要注意这段代码仍有DNS重绑定的风险因为校验时解析了一次IP请求时可能又解析一次。要彻底封掉DNS重绑定需要把解析和请求绑在一起比如自己指定IP发起连接或者使用库的IP固定功能在比较严格的安全场景下面可以配合ipaddress模块做网段运算。import ipaddress def ip_allowed(ip: str) - bool: addr ipaddress.ip_address(ip) private_networks [ ipaddress.ip_network(10.0.0.0/8), ipaddress.ip_network(172.16.0.0/12), ipaddress.ip_network(192.168.0.0/16), ipaddress.ip_network(127.0.0.0/8), ipaddress.ip_network(169.254.0.0/16), ipaddress.ip_network(::1/128), ] return not any(addr in net for net in private_networks)5.3 处理重定向唯一安全的方式是校验后的重定向目标前面说过跟随重定向是白名单绕过的经典路径。安全的做法不是跟随重定向但加限制而是根本不跟随或者跟随前重新做一次校验。以requests库为例可以关掉自动重定向然后手动判断import requests def safe_fetch(url: str): resp requests.get(url, allow_redirectsFalse) if resp.status_code in (301, 302, 303, 307, 308): redirect_target resp.headers.get(Location) if not redirect_target: return resp # 这里需要对redirect_target重新走一遍is_safe_url校验 abs_redirect_url requests.compat.urljoin(url, redirect_target) if not is_safe_url(abs_redirect_url): raise ValueError(redirect not allowed) return safe_fetch(abs_redirect_url) return resp这段代码本质上是把重定向当成一次新的请求整个安全校验重新执行而不是盲目信任第一次的校验结果。在我处理过的项目中很多SSRF漏洞就是出在第一次校验通过后跟随跳转没再查这个细节上。5.4 关于响应的处理别把原始响应直接吐给用户就算前面防护全部到位了最后的响应回显也是一个不可忽视的点。理想的情况是服务器把远程资源处理后返回给用户但如果设计得懒直接把远程响应的Body原样返回那么一旦出现漏网的SSRF攻击者直接就能读取内网响应内容。我推荐的设计是如果业务只是下载图片并缩略就只返回处理后的图片/文件绝不返回原始HTML页面。如果业务需要读取远程文档在返回前过滤掉可能泄露敏感信息的内容。错误信息的展示要统一不管是连接失败、超时、解析失败一律返回通用的错误文案不要把后端的内部异常堆栈抛出去。5.5 平台侧的兜底出网代理统一收口在大型企业里最稳的SSRF治理方案是在基础设施层面把服务器的对外请求统一收敛。具体做法是所有需要访问外部资源的应用统一走一个出网代理这个代理只允许访问合法的域名和IP并且对每个请求做独立的安全校验。业务侧就不需要自己去实现复杂的SSRF防御了只需要把请求丢给代理。这个模式我在某公司的微服务架构里实际落地过一次效果很明显之前经常收到安全团队发现SSRF的告警后来几乎从告警列表里消失了。因为它把防御从每个业务开发者都要写对校验变成了一个安全团队维护一套校验出错概率完全不是一个量级。6. 一次完整排查复盘从日志异常到定位SSRF想着重说一下真实的排查过程。毕竟靶场练的是纯技术链但实际项目中SSRF往往混在正常业务流量里要能分辨出来。6.1 前置背景怎么发现疑似SSRF的某天安全监控平台报了一条告警内容大概是服务器A对外发起了到内网网段10.50.0.0/16的连接目标端口22连接频率每分钟约30次持续了5分钟。看起来确实像是横向扫描的特征。当时第一反应是服务器被入侵了因为正常业务不会主动去扫内网22端口。为了确认我先查了这台服务器的登录日志和进程列表均未发现异常。然后才回头去看出网流量对应的进程——结果是Java进程应用是一个内容管理平台。这就很可疑了。6.2 顺着特征反查业务发现可疑参数由于出站流量没有精确的URL日志只能去翻应用本身的访问日志。搜索时间窗口内所有请求发现有一个/api/fetchImage?url...接口在同一时间内被高频调用参数值全是类似http://10.50.1.1:22、http://10.50.1.2:22格式的地址一眼就看出来是拿这个接口在扫端口了。我本地复现了一下请求/api/fetchImage?urlhttp://127.0.0.1:80服务器返回了一个无法访问的提示但请求确实发出去了。同时把参数换成file:///etc/passwd接口返回了文件内容。到这里事件性质已经确定有人利用了这个图片抓取接口的SSRF漏洞先是扫内网22端口探测存活主机然后配合file协议读取了服务器本地文件。也就是说这台服务器既是被入侵的入口也是持续扫描内网的工具。6.3 修的时候才发现防的是看起来像SSRF但不彻底修复工作分了几步。第一优先是立即去掉file等非HTTP协议支持同时把这个接口的URL参数改了不再允许任意外部地址只接受图片资源的固定域名白名单。代码评审时发现原实现里的校验竟然存在但写得非常安慰自我——它确实禁止了127.0.0.1但只挡住了字面意义上的127.0.0.1没有解析DNS没管内网IP段没检查重定向更没限制协议。后来我们自己做了一个模拟攻击验证用http://2130706433/这种十进制IP就把原校验绕过去了。修复后的方案很简单有效协议白名单限定为http和https解析域名并校验最终IP禁止所有内网段和保留IP段禁用跟随重定向或者对重定向目标重新校验响应数据只返回图片缩略图不返回原始文件内容对这个接口单独限流防止被用来做批量探测。6.4 事件复盘常见的三个认知误区这件事里最大的教训是团队里对SSRF的认知有几个典型的错误我们用了HTTPS所以不会有SSRFHTTPS解决的是传输加密跟服务器是否主动访问恶意地址没有任何关系。我们做了URL参数校验做了校验不等于做对了校验字符串层面的正则和真正解析后的IP校验差了十万八千里。这个接口只接收图片链接不会有问题的用户传一个HTML页面服务器照样下载如果后续逻辑用了它指不定还能引出存储型XSS或者别的类型。在那次复盘会上我反复强调一个判断方法不信任任何用户提供的URL所有出网请求的目标都要经过解析后的二次确认这是防御SSRF的地基。7. 给正在学这套靶场的人一些建议如果这篇文章能帮你少走几步弯路那非常值了。最后结合我在靶场里卡关的经历给后面要刷这套SSRF专题的人几个具体的建议不要只记Payload要理解URL解析的差异。什么时候用什么时候用#什么时候用十六进制IP本质上是不同库解析URL的方式不一样。可以拿一个Burp发给服务器观察重定向时发出的请求实际对的是哪个Host很快就理解了。每一关做完之后一定要敲一遍防御代码。我在靶场刷完自己照着防御思路写了个工具函数被团队拿去直接用了。不是为了生产级质量而是写一遍能发现很多以为懂了但实际没掌握的细节。把响应时间差异当一等公民。很多关卡没有回显但响应时间一测就出来这个技能在真实渗透里也很有用。留意DNS重绑定。靶场里有专门的关卡考这个很多人一开始觉得复杂但一旦理解了两次解析不一致再回头看那些白名单校验代码一眼就能看出它们是不是有问题。SSRF这个漏洞类型说难不难说简单也不简单。难的不是利用手法而是防御思路的完整性。以前总觉得服务器帮我请求一下是个小事现在看到任何一个带URL参数的接口第一反应都是这个请求如果被恶意控制了能到内网哪里、能读什么文件、能打什么协议。这种警觉性是刷完这套靶场最大的收获。

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

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

免费获取报价 →
↑