首页 / 资讯中心 / 文章详情

SSRF漏洞从原理到防御:服务端请求伪造的检测与修复实战指南

SSRF漏洞从原理到防御:服务端请求伪造的检测与修复实战指南 ★ FEATURED ARTICLE
1. 初见SSRF这个冷门漏洞为什么值得你花时间搞懂我最早接触SSRF这类漏洞是在一次内部安全评估里。当时有个同事拿着一份测试报告说这个站点好像能让服务器自己去访问任意地址。说实话那时候团队里对这个问题重视程度不高因为相比SQL注入、XSS这些耳熟能详的漏洞SSRF在三家漏洞排行里总是排得靠后很多人甚至说不出它的全称。SSRF全称是Server-Side Request Forgery中文一般叫服务端请求伪造。它的核心问题一句话就能讲明白攻击者通过诱导服务器去发起本来不应该发起的网络请求。但如果真只把它理解成能发点怪请求那绝对低估了它。这东西一旦利用到位轻则扫内网端口重则直接拿下云服务器的元数据凭证翻出整个内网拓扑后续横向渗透一步到位。可以说在实战中SSRF是一条非常典型的高价值攻击链路起点。但有意思的是大部分零基础的学习者第一眼看到SSRF往往会陷入好像看懂了、又好像不会用的状态。原因很简单SSRF的触发点藏得深它不像SQL注入那样有个明确的报错可以顺着摸也不像XSS那样刷新页面就能看到效果。它的攻击目标往往是服务器自己而你看不到服务器发出去的请求到底去了哪、带回了什么所有的利用都像隔着一层黑纱。这种看不见的特性恰恰是SSRF最磨人的地方。这篇文章我打算从一个相对完整的视角来讲从SSRF到底是什么、它为什么会产生到它到底有多大危害、检测时有哪些思路再到怎么去防御和修复最后聊聊一些实际判断和利用进阶逻辑。我会尽量多用白话多举例子把那些文档里不爱讲的边界情况和经验性判断也放进来。毕竟这种漏洞光看理论是不行的得真的理解请求从哪来、到哪去、谁能控制这条链子才算真正入门。2. SSRF到底是什么从一次代发请求说起2.1 一个形象的生活类比服务器帮你跑腿想象你所在的公司有一栋写字楼。你在里面上班外面来的人进不来——这是公网和内网的关系。正常来说外网用户发给公司服务器一个请求服务器处理完返回一个结果仅此而已。但如果服务器上有这样一个功能用户可以提交一个URL然后服务器去帮你抓取这个URL的内容并回显给你。类比一下就是你让大楼里的同事帮你跑去某家公司拿一份资料回来。这个帮忙跑腿的过程本身是安全的问题在于——你没有限制这位同事只能去哪些地方。他可以帮你拿到街对面公开店铺的传单也可以顺手帮你去25楼某个没有门禁的实验室翻一翻资料甚至还能帮你带出实验室门禁卡的照片。SSRF的本质就是这个。Web应用为了让用户方便提供了URL参数或文件导入之类的功能让服务器主动发起请求。但因为没有严格限制请求的目标地址、协议、端口攻击者就可以通过这个功能让服务器去访问攻击者指定的任意地址。这个地址可以是外网的一个商量好的服务器更重要的是可以是服务器所在内网里的任意主机。这些内网主机往往有各种数据库、管理后台、监控系统、文件服务它们通常不对外网开放但服务器自己是能访问到它们的。于是服务器就成了一个跳板绕过了外网和内网的边界隔离。2.2 产生SSRF的高危场景哪些功能天生容易踩雷从我接触过的真实业务来看下面这几类功能是SSRF的高发区也是做安全测试时第一优先级要去看的地方URL分享/转码服务给个链接帮你生成预览图或解析标题。像社交平台分享卡片、IM工具的消息预览都会让后台去请求目标URL。图片上传/远程加载上传图片时允许填写图片URL而不是直接上传本地文件。服务器需要去拉取远程图片这里就很容易被改写成内网地址。Webhook回调设置让用户填一个回调地址服务端主动向这个地址发POST或GET请求。很多开放平台功能都有这个。PDF/文档生成器传入一个URL后台用无头浏览器或导出服务去访问并渲染打印。这类型往往能附带出更多花样比如读取本地文件。文件导入/数据同步从远程URL导入CSV、XML、JSON等数据源服务器会主动去抓取。数据库内置功能有些数据库、消息中间件的管理面板自带克隆导入功能允许指定远程主机地址一旦暴露到外网就是送分题。这些场景的共同点是应用需要充当一个HTTP客户端。只要有服务器主动发起网络请求这个动作并且请求的地址里存在任何用户可控的成分SSRF就有机会冒头。很多开发者在写远程加载功能时只考虑能不能抓到很少考虑抓到之后会不会有问题——这恰恰是漏洞产生最根本的原因。2.3 SSRF不是CSRF别把两者搅浑初学的时候很多人会把SSRF和CSRF搞混。其实这俩名字有点像但完全不是一回事。CSRF是Cross-Site Request Forgery跨站请求伪造。它伪造的是浏览器里的用户请求——攻击者诱导浏览器带上用户的Cookie去发请求服务器以为是用户本人操作。被害者是用户被骗的是服务器。SSRF是Server-Side Request Forgery服务端请求伪造。它伪造的是服务器自己的请求——服务器在收到用户请求后再向任意目标地址发起请求。被害者可以是内网里的任意系统被骗的是服务器。直观区分一句话CSRF是借用户之手打用户SSRF是借服务器之手打内网。CSRF的请求是从受害者浏览器发出去的SSRF的请求是从受害者服务器发出去的。理解这个区别之后再去看各种漏洞文章就不会串味了。还有一个容易混淆的概念是开放的URL重定向就是那种跳转漏洞。URL跳转虽然也是服务器返回一个可控制的Location头但请求是浏览器发的危险程度和SSRF差远了。有些文章会把它们放在一起讲用来比较可以但本质一定得区分干净。3. SSRF是怎么产生的请求链路的每一环都可能有突破口3.1 代码级别的成因一个典型的脆弱实现为了讲清楚SSRF的产生过程我用一个典型的服务端代码片段来演示。不管用什么语言核心逻辑都一样。以PHP为例一个最简单的远程图片抓取功能很可能长这样?php $url $_GET[img_url]; $content file_get_contents($url); // 直接将内容输出给用户 header(Content-Type: image/png); echo $content;这段代码的问题一眼就能看出来img_url这个参数完全由用户控制后端拿到之后直接交给了file_get_contents去请求没有任何校验。用户传https://example.com/a.png服务器就去公网抓图用户传http://192.168.1.1:8080/admin服务器照样会去请求并把返回内容原封不动地吐出来。Python版本也很常见比如用requests库去下载文件import requests from flask import Flask, request app Flask(__name__) app.route(/fetch) def fetch(): url request.args.get(url) resp requests.get(url, timeout5) return resp.contentJava的HttpURLConnection、OkHttpGo的http.GetNode.js的axios或http.get只要代码写成用户传URL、后端直接去请求本质上都是同一个问题。这里有一个关键认知SSRF并不是某种语言或某个框架特有的漏洞而是远程资源加载这整个功能类别在缺乏地址校验时必然产生的通病。所以不管是写代码还是做测试都先别急着找具体函数而是先找哪里有服务器主动请求外部地址这个动作。3.2 请求链路的三个节点入口、转发、出口拆开来看一次典型的SSRF攻击链路里有三个节点入口节点用户从哪里把URL传入应用。常见的是GET参数、POST表单字段、JSON里的某个Key、HTTP头里的某个自定义头比如X-Forwarded-For这种也有案例。入口节点的核心特征是用户可控。转发节点应用内部怎么处理这个URL。可能是直接调用语言自带的HTTP库也可能是经过一层封装的代理服务或者内部网关。转发节点的核心特征是请求是服务器以自己的身份发出去的所以NAT之后的出口IP是服务器IP而不是用户IP。出口节点请求最终打到了哪里。正常情况下出口应该是用户指定的公开资源地址。漏洞情况下出口可以是内网IP、回环地址、云元数据地址、本地监听端口等等。理解这条链路之后很多问题就清晰了防御者的任务是想办法在链路中间加闸门攻击者的任务是想办法绕开闸门。一个完整的SSRF防护方案必须在入口做白名单在转发节点做协议限制在出口做访问控制三层都到位才算基本稳妥。3.3 为什么很多应用死活防不住常见的开发误区经常有开发同事跟我说我们加了校验啊怎么还是有漏洞然后我去看代码发现了几个高频误区第一个误区只防IP不防域名。有的应用确实做了IP黑名单把127.0.0.1和192.168.x.x都过滤了。但攻击者改成localhost、改成内网域名、或者用localtest.me这类DNS解析到回环地址的域名就直接绕过。因为服务器最终还是得做DNS解析解析出来的是内网IP请求照样发出去了。第二个误区只判断Scheme不判断目标。应用只校验了URL必须以http或https开头就认为安全了。但HTTP协议本身就能用来访问其他端口比如http://127.0.0.1:6379/——6379是Redis默认端口。只要攻击者摸清内网有哪些服务用http://内网IP:端口/的格式直接撞就行。第三个误区用正则匹配来做黑白名单。正则这东西写得好是强大的校验写不好就是筛子。比如只简单判断URL里有没有127.0.0.1这个字符串攻击者改成http://127.1/在很多Linux系统里也能解析到回环地址改成http://2130706433/这是127.0.0.1的十进制写法同样能通。你以为你过滤了实际上只是换了个写法而已。第四个误区没有做响应校验。有的应用确实限制了只能访问某几个可信域名但是对返回内容完全不检查。这意味着即使走了白名单域名如果攻击者能让可信域名返回一个跳转到内网地址的响应应用跟着跳转依然可以打到内网。比如用DNS rebinding的办法第一次解析时给一个合法地址通过校验第二次解析时解析到内网地址。这些误区说明一个道理SSRF防护不能只靠一个地方堵要意识到地址解析这个环节本身是多变的任何只做一次静态校验的方案都有被绕过的空间。后面讲防御的时候我会详细展开这里先记着。4. SSRF能造成多大危害从探测到拿下的完整链条4.1 危害一内网端口扫描与指纹探测SSRF最常见的初级利用方式就是内网端口扫描。因为服务器能访问内网攻击者只需要不断修改URL里的IP和端口就能通过服务器的响应差异来判断端口是否开放、运行的是什么服务。举个例子假设内网有一台主机192.168.1.10我用URL去请求http://192.168.1.10:3306/。如果返回的响应时间、错误信息、或者响应体跟请求一个关闭的端口有明显区别就说明3306端口是开着的。用这种方法一张内网主机存活表和端口开放表就能慢慢拼出来。更进一步的指纹探测也很有价值。比如去请求http://192.168.1.10:8080/如果返回的内容里包含某种框架的特征页、登录框标题、默认图标路径攻击者就能识别出这是某个管理后台或者监控系统然后针对性查已知漏洞去攻击。有个容易被忽略的点如果应用把远程请求的响应体直接回显给了用户那扫描和探测就非常高效——相当于攻击者有一个内网浏览器的视角。如果应用不回显响应体只是引入资源或者触发回调那判断端口就只能靠时间差、错误码等侧信道效率低一些但并非不可用。4.2 危害二访问内网Web应用与敏感数据如果内网里有一个管理后台本来只允许内网IP访问比如运维面板、数据库管理工具、云控制台、日志系统攻击者通过SSRF让服务器去请求这个后台的地址并且把响应内容回显出来后果就是攻击者直接看到了内网页面的全部内容。如果这个后台还允许执行操作比如命令执行面板、远程重启API、工单处理接口那攻击者甚至可以借用服务器的身份直接操作它。这里有个关键点很多内网系统在认证上是有缺陷的。它们假设能访问到我的一定是我们内网自己人所以要么不鉴权、要么用很弱的默认口令、要么只查来源IP是不是内网段。这种基于网络位置的信任模型在SSRF面前不堪一击。服务器发起请求时来源IP就是内网IP天然就通过了这类系统的信任检查。这也是为什么SSRF被称为内网信任模型的粉碎机。4.3 危害三云环境下的元数据凭证窃取如果说上面那些危害还算常规那云环境下的元数据服务攻击就是SSRF的王炸场景。在主流云平台上每台云服务器都有一个特殊的地址用来查询自身元数据比如常见的http://169.254.169.254/。这个地址只能从云服务器内部访问外部网络根本到不了。元数据服务会返回这台服务器的基础信息主机名、网卡MAC、甚至临时访问凭证。当攻击者发现目标站点的SSRF漏洞后只需要让服务器去请求http://169.254.169.254/latest/meta-data/就有可能拿到云服务器自身的元数据信息。在部分配置不当的场景下还能进一步拿到与角色绑定的临时凭证这意味着攻击者可以直接调用云API批量操作这个账号下的云资源。这已经不是打下一台服务器的问题了而是接管一个云账号的问题。这里需要注意不同云平台的元数据服务路径和字段结构不完全一样。有的在http://169.254.169.254/有的可能使用IPv6地址有的需要额外加请求头。但思路是通用的凡是能让服务器发起任意URL请求的SSRF在云环境下都要把元数据地址列入首先排查的清单。4.4 危害四攻击内网非HTTP协议服务SSRF的利用面远不止HTTP协议。因为服务端发起请求时可以指定协议和端口攻击者可以让服务器去连内网里各种非HTTP的服务——比如Redis、Memcached、MySQL、MongoDB、Elasticsearch、Docker API等等。比如内网Redis没设密码或者用了弱密码攻击者通过SSRF向Redis端口提交命令在某些场景下可以往磁盘写入SSH公钥或计划任务进而拿下主机的权限。Docker的API如果暴露在内网且没有鉴权攻击者通过SSRF向Docker API发送请求可以直接创建容器把宿主机的目录挂载进容器里然后轻松代码执行。这类利用的难度比单纯扫端口要高很多因为需要同时掌握目标服务协议格式和SSRF对协议的支持程度。有些语言或框架只支持HTTP和HTTPS那攻击者想打Redis就比较费劲但如果应用底层是用curl或者支持多协议的网络库实现的那协议面就宽得多。做防御的时候限制协议种类是一个非常重要的手段我后面会专门讲。4.5 危害五文件读取与内部文件泄露有些编程语言或库在处理URL时支持file://协议。当攻击者传入file:///etc/passwd时服务器不是去发HTTP请求而是直接读取本地文件。如果响应体有回显等于白送一个任意文件读取漏洞。文件读取的危害显而易见应用配置文件中可能包含数据库密码、第三方API密钥、内部系统连接串。Linux下还有/proc/self/environ这种藏着环境变量的特殊文件Windows下则可以读C:/Windows/win.ini确认系统类型。在Docker环境里读取/proc/1/cgroup还能判断当前容器是否是特权模式为后续逃逸做铺垫。不过也要说实话file://协议并不是所有语言都默认支持。file_get_contents在PHP里就很顺滑但Java的HttpURLConnection默认就不接受。所以这属于环境相关的高危能力测试时值得优先试一下但不要默认所有SSRF都能读文件。4.6 组合利用思路从SSRF到RCE的典型路径单独看某个危害可能觉得也就那样。但把链组合起来之后你就会明白为什么SSRF被圈内人称为高价值漏洞。一条非常典型的链路是这样的先通过SSRF扫描内网发现一台Redis服务没有鉴权再通过SSRF对Redis发命令写入计划任务最终拿到内网主机权限。拿到一台内网主机之后再利用内网横向移动逐步摸到核心数据库或管理后台。整个过程的第一块多米诺骨牌就是从那个不起眼的URL分享功能开始的。另一条云上链路则是发现SSRF请求元数据服务拿到云凭证调用云API创建一台新的高权限实例或直接读取对象存储里的敏感文件数据泄露和资源接管一步到位。在安全演练或红队项目中SSRF经常作为突破口之一被反复使用。原因很简单它往往出现在公网入口附近容易被打到而且一旦打穿就直接撕开了内外网的隔离。5. 怎么发现SSRF检测思路与踩过的坑5.1 手工检测的基本思路从功能入口出发一个负责任的安全测试者拿到一个待评估站点后第一步应该先从功能层面判断哪里有服务器主动发请求的动作。我的经验是可以直接从这五类入口开始翻翻遍页面上所有能填写URL、链接、地址的输入框。比如请输入图片URL博客地址RSS源地址等。看网络请求里有没有url、uri、link、src、dest、redirect、image_url、callback、webhook、target这种名字的参数。用浏览器开发者工具看加载的资源里有没有明显由后端转发的请求。测试API文档或接口清单看有没有URL提交类接口。观察网站的请求响应时间某些功能明显花了好几秒才返回很可能内部做了远程调用。找到入口之后最直接的验证办法就是用自己的服务器搭一个监听器比如在https://webhook.site/这种临时Webhook服务上生成一个URL然后把这个URL提交到目标功能里看自己的服务器有没有收到请求。收到了就说明服务器确实会为这个功能发起外部请求。这个办法好处是零误报但坏处是只能证明能发请求到公网还不能证明能打内网。要验证内网可达性可以试着提交http://127.0.0.1:80/看响应是否与请求正常URL时有区别。很多应用会在出错信息里暴露内部细节。5.2 盲SSRF的判断思路不看响应也能确认有一种情况比较磨人应用确实会发起请求但请求的响应不会回显给你也不会有任何前端反馈。这种叫盲SSRF。盲SSRF虽然看不见响应但并非无法验证。判断思路有几个利用时间差提交一个指向不存在内网IP的URL比如http://10.255.255.1/通常因为TCP超时需要等很久。再提交一个指向本机回环地址的URLhttp://127.0.0.1/如果应用立刻返回超时或错误说明请求确实发起到了内网目标。通过对比时间可以判断IP是否可达。利用外部DNS查询日志如果你有一个可自己控制DNS解析记录的域名可以生成一个随机子域名让目标服务器去请求http://随机子域名.你控制的域名/。如果DNS解析服务里能查到来自目标服务器出口IP的解析记录就实锤了这个功能会发起请求。这个方法几乎适用于所有SSRF检测场景而且不影响业务稳定性。利用可交互的HTTP服务如果没法在公网开监听可以把requestbin之类的服务当作接收方看目标请求是否到达。做验证时记得用一次性随机路径避免被其他扫描流量混淆。盲SSRF在实际利用时比有回显的SSRF麻烦得多因为所有内网探测都要靠盲猜和侧信道。但这不代表它不重要——很多真实的高危事件就是从盲SSRF开始的。5.3 自动化工具能做什么、不能做什么自动化的SSRF检测常见工具有两类思路。一类是从已发现的URL参数里去替换成检测地址然后观察响应和外部请求记录另一类是基于已知指纹规则在流量里筛选可疑的参数名和服务响应特征。工具的价值在于能快速在大规模资产中筛出值得人工深挖的可疑点。但工具也有明显的天花板——很多SSRF需要业务逻辑层面的配合才能触发。比如需要先登录、需要先上传文件、需要特定的请求头单纯的URL替换往往触发不了。而那些藏在API接口、Webhook回调、异步任务队列里的SSRF更是很难被无状态扫描器发现。所以我的判断是工具负责广撒网人负责精捕鱼。真正靠谱的SSRF发现还是要靠对业务逻辑的理解和对请求链路的感觉。这个只能靠多看、多测、多复盘来积累。5.4 踩坑记录检测SSRF那几年交过的学费我分享几个自己踩过的坑给后来的人提个醒第一个坑是拿跳转后的地址当最终地址结果被坑了。检测时如果提交一个URL应用先请求了一次、然后跟着3xx跳转再去请求第二次外网接收到的请求可能是两次不同地址的。做验证时收到请求之后要看看请求路径、Header、来源IP是不是预期中的不要只看收到请求了就下结论。第二个坑是只测主流入口忽略了异步任务。有些应用的URL抓取功能是消息队列异步执行的你提交URL之后立刻去看结果根本还没抓到呢。等几分钟再回来看可能才看到效果。所以测SSRF时要有耐心提交后等一等、刷新一下别急着判不存在。第三个坑是把内网特征当成了SSRF特有表现。有些应用做了内容转换或者编码导致访问内网地址时返回的错误跟正常错误看起来一样这时候要小心判断。最好用多个不同IP、不同端口做对比实验再用外部监听辅助验证不要单独依赖某一项证据。6. 如何防御SSRF三层防护策略与实操细节6.1 第一层输入侧校验——把住入口最基础也最必要的防御是在入口处对用户传入的URL做严格校验。但注意这里的校验绝不能是简单黑名单我更推荐用白名单思路。白名单校验分两种情况业务限定范围明确如果业务只允许访问某些固定的外部域名直接把这些域名做成白名单其他的一律拒绝。这是最安全也最简单的方案。实现时注意匹配应该精确到主机名而不是模糊匹配。业务需要开放任意公网URL这种情况比较棘手白名单定义不了只能退而求其次做禁止内网规范化校验。核心策略是解析URL拿到协议、主机名、端口然后分别校验。Protocol限定为http和https主机名解析出的IP禁止为私网地址段、回环地址、链路本地地址端口限定为常用端口范围。这里有一个容易翻车的地方URL解析的标准化。http://example.com127.0.0.1这种写法有些解析器会把example.com当用户名、把127.0.0.1当主机名http://127.0.0.1#example.com这种写法有些解析器会把example.com当片段而忽略。不同语言、不同库对URL的解析标准不完全一致所以校验时不能只调一次现成函数还要自己写一份标准化后的目标地址用于后续的网络连接。否则你校验的是A地址实际请求的是B地址白名单形同虚设。6.2 第二层转发侧限制——控制协议与出口输入校验再怎么严格也架不住各种奇技淫巧的绕过。所以第二层防护的重点是即使攻击者构造出一个看起来合法的目标地址应用也不具备发起危险请求的能力。协议限制很好理解如果业务只需要HTTP和HTTPS就在转发协议这一层把其他协议全部掐掉。PHP的curl可以设置CURLOPT_PROTOCOLS来限定协议集Java的URL连接在部分场景下可以自定义协议处理器更稳妥的做法是引入一个专门做安全请求转发的库内部只支持HTTP和HTTPS。Python的requests库底层是urllib3默认只支持HTTP/HTTPS相对好一些。出口访问控制是更硬的一层在服务器的网络层或系统层限制出网访问。具体做法包括用防火墙规则或安全组禁止服务器主动访问内网网段和回环地址除非有明确的业务需要。如果服务器必须访问某些外部服务把这些外部服务的IP或域名加白其他出口流量一律deny。在云环境里通过VPC的网络ACL策略限制实例对169.254.169.254等元数据地址的访问。不过要小心有些合法的云管理场景是需要访问元数据服务的得仔细确认。这一层的本质是把应用层不管的事情交给网络层去兜底。即使应用层的代码有漏洞网络层也能挡住大部分危险请求。6.3 第三层响应侧校验——防止泄露即使入口和转发都做了限制也不能掉以轻心。响应侧校验的作用是对远程请求返回的内容做检查避免敏感数据直接回显给用户。如果业务场景允许最好把远程请求的响应体做一层处理。比如验证返回内容的Content-Type只允许图片、普通文本等预期类型对返回内容里的敏感关键词做过滤或脱敏当发现响应内容是内网页面特征时直接返回一个通用的错误信息。还有一种更严格的做法是让远程请求的结果不直接返回给用户而是只反馈成功/失败这种状态。就是说如果需要抓取内容可以先把它在服务端缓存或转存然后用户只能拿到处理后的结果拿不到原文。这样可以大大降低SSRF的回显风险。当然这会影响某些功能体验需要产品侧做权衡。6.4 修复方案的完整代码示例一个相对完善的过滤逻辑说了那么多理论给一个相对完整的校验示例。我用Python写一个基于urllib.parse的校验函数示例import ipaddress import socket from urllib.parse import urlparse # 这些网段必须禁止访问 BLOCKED_NETWORKS [ ipaddress.ip_network(127.0.0.0/8), # 回环地址 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(169.254.0.0/16), # 链路本地地址 ipaddress.ip_network(::1/128), # IPv6回环 ipaddress.ip_network(fc00::/7), # IPv6私有地址 ] def is_safe_url(url): try: parsed urlparse(url) except Exception: return False # 只允许 http 和 https if parsed.scheme not in (http, https): return False hostname parsed.hostname if not hostname: return False try: # 如果主机名本身是IP直接判断 ip ipaddress.ip_address(hostname) for net in BLOCKED_NETWORKS: if ip in net: return False except ValueError: # 主机名是域名需要解析后判断 try: results socket.getaddrinfo(hostname, None) except Exception: return False for result in results: ip_str result[4][0] try: ip ipaddress.ip_address(ip_str) except ValueError: continue for net in BLOCKED_NETWORKS: if ip in net: return False return True这个示例只是最基础的框架。在实际项目中你还需要考虑端口白名单、DNS解析的TTL缓存问题防止DNS重绑定攻击、对重定向的二次校验等等。但核心思路已经能看出来必须在实际发起请求前把URL真真切切解析一轮对解析出的IP和协议做最终判断。这里特别强调一件事DNS重绑定这种绕过方式非常容易被忽略。攻击者可以用一个域名第一次让DNS解析到公网合法IP来通过校验等应用真正发起请求时DNS又解析到内网IP。应对办法很简单解析一次判断通过之后连接时强制使用刚才解析得到的IP而不是让网络栈重新解析域名。很多语言的标准库在这点上不够安全需要自己处理。6.5 测一测你的修复方案绕过测试清单每写一个修复方案我就习惯性从攻击者视角过一遍绕过思路。下面是我常用的一个检查清单你也可以按这个来验证自己的防御逻辑用IP格式绕过十进制、十六进制、八进制、省略前导零的IP写法是否被解析成内网IP用域名格式绕过localhost、localtest.me、自定义域名解析到内网IP能否通过校验用URL解析差异绕过http://evil.com127.0.0.1、http://127.0.0.1.evil.com、http://evil.com#127.0.0.1这类写法解析器会把哪个当真实主机名用重定向绕过提交一个公网URL这个URL返回302跳转到内网地址服务器会不会跟着跳过去用协议绕过file://、gopher://、dict://、ftp://等协议是否被协议白名单阻止用DNS重绑定绕过域名在两次DNS解析时返回不同IP能否第一次通过校验、第二次打到内网用IPv6绕过直接使用IPv6内网地址或者用IPv4-Mapped IPv6地址形如::ffff:127.0.0.1是否被网络过滤规则覆盖如果你的修复方案能扛住上面这些测试有效性就相当不错了。7. 讲讲那些容易让人误判的细节协议与编码的坑7.1 URL编码与二次解码的攻防博弈URL编码这关非常容易出事。开发者在做参数校验时经常会忽略编码层的多层处理。举个例子如果应用先对用户输入做一次URL解码再把解码结果传给远程请求库就可能出现校验时看到的是编码后的安全字符串实际请求时解码成了危险地址的情况。常见操作是攻击者提交http://127.0.0.1%2fadmin有的代码在解析时%2f可能被解码成/也可能不被解码关键看这一层校验在哪个环节执行。如果是先解码再校验那%2f解码成/之后自然会被识别但如果是先校验再解码那校验时看到的是127.0.0.1%2fadmin这个字符串正则可能匹配不上实际请求时又恢复成了127.0.0.1/admin拦截失效。这类问题的核心是输入经过的编码解码次数和顺序必须和校验代码执行的时机完全对齐。否则就会出现校验器和执行器各看各的地址中间永远隔着一条缝。作为防御者最简单粗暴的解法是在入口处统一做规范化和解码把最终结果固定下来再拿这个结果去做校验和转发。7.2 短地址服务与第三方跳转间接的SSRF放大器有些业务允许用户提交短链接或者任意第三方URL应用为了展示标题或内容会跟着重定向去最终地址。如果攻击者先用短链接服务把http://127.0.0.1:6379/包装成一个看起来人畜无害的公网短链应用的校验只看到短链域名就放行了结果服务端跟随着重定向一路跳到了内部地址。这个问题的难点在于从产品角度你不一定能禁止短链功能。所以更务实的做法是不管用户提交的URL是什么服务端在跟随重定向的过程中每一跳都要重新做安全校验。尤其是当目标地址从一个公网域名跳到一个IP地址或内网网段地址时必须立刻中止请求并记录告警。很多现成的HTTP客户端库在重定向时可以配置拦截回调别放过这个开关。7.3 那些看起来像文件的远程地址还有一种场景经常让人迷惑远程加载功能允许用户传一个图片URL但实际上后端用的是curl并且把URL传给了命令行。如果整个链路没有对命令行参数做转义攻击者传的URL里带上了-o参数或file://协议可能会导致文件写入甚至命令注入。这个已经不是单纯的SSRF了更像是SSRF与命令注入的组合问题。遇到这类场景防御的核心是不要用拼接命令的方式去加载远程资源尽量用编程语言自带的HTTP库。如果绕不开命令行工具至少用数组参数传递而不是字符串拼接并严格限制协议、域名、端口。7.4 与WAF的相爱相杀规则拦截的盲区不少站点部署了WAF也配了一些SSRF相关的拦截规则。但WAF只能基于它看到的请求特征来判断看不透服务端内部到底怎么解析。常见的WAF绕过思路包括利用URL解析差异、编码差异、短地址、DNS解析到公网但业务内部再解析到内网的场景。这些我上面已经提过一部分。作为防守方应该清楚一点WAF是安全体系的一部分但绝不能是唯一防线。应用代码层的白名单校验、网络层的出口访问控制才是SSRF防御的核心骨架。WAF顶多算一道拦路查证的哨卡指望它把所有攻击都拦下来是不现实的。8. 遇到SSRF之后的修复流程实战中的处理步骤8.1 第一步确认漏洞点与影响范围发现SSRF之后不要急着改代码先把信息收集完整。具体要做的事确认漏洞入口哪个接口、哪个参数、哪种请求方式触发。确认是否能回显响应内容是否能直接返回给攻击者。这决定了漏洞的严重级别。确认受影响的目标是否已经访问到元数据服务、是否能访问内网其他主机、是否有其他协议被支持。确认影响面是只有某一个接口存在还是整个应用的所有URL抓取功能都有问题。确认是否有日志记录如果有及时保留请求日志方便后续溯源分析。8.2 第二步临时措施先止血如果漏洞正在被利用或者评估风险较高先做止血措施再慢慢修在应用前的网关或WAF层临时添加针对该接口的URL参数校验规则先把明显可疑的请求拦下。在服务器防火墙紧急添加出站规则禁止内网网段方向的主动连接除非有业务必需。如果是云环境检查安全组策略确认元数据服务地址的访问没有被非必要放行。确认该接口是否能够暂时下线。如果功能本身不是核心业务下线是最快的止损方式。8.3 第三步根因修复与代码改造临时止血之后才轮到根治。按照前面讲的输入校验协议限制出口控制三层思路去做代码改造。具体来说入口增加URL规范化与白名单校验拒绝内网地址和危险协议。转发层使用安全配置的HTTP客户端库关闭不必要协议限制重定向行为。网络层增加出站白名单或防火墙规则兜底拦截。对远程请求的响应内容做二次检查避免敏感数据直接回显。改造完成之后把之前的绕过测试清单完整地跑一遍确保所有绕过思路都被堵住。另外不要忘了做回归测试原来的业务功能——比如远程图片加载、URL分享——要确保还能正常使用。很多修复方案太激进直接把正常业务也砍了这在我们这儿叫修漏洞修挂了比漏漏洞还烦人。8.4 第四步把修复经验沉淀为团队规范单人修完一个漏洞不算结束。如果团队里只有你一个人知道SSRF的坑下次别人写代码照样会踩。比较好的做法是梳理一份远程URL请求安全开发规范写清楚禁止事项和推荐写法挂在团队文档里。把URL请求封装成统一的安全工具类所有业务都走同一个入口校验逻辑只写一遍、只维护一遍。在代码评审的checklist里增加所有远程请求必须经过安全校验项。如果团队有条件把SSRF探测规则接入自动化测试或安全扫描流程作为发布前的必检项。这类事情的价值在于它不是救一次火而是让整条开发链路对同类风险都有免疫力。9. 进阶视角如果是代码审计你会怎么快速锁定SSRF9.1 用污点追踪的思路过代码做代码审计时最高效的SSRF排查方式就是追踪用户可控数据的流向。这个在安全领域叫污点追踪Taint Analysis。具体来说就是找入口函数和发送函数然后看两者之间是否有路径连通。入口函数就是所有接收用户输入的地方$_GET、$_POST、request.getParameter、request.args、Body里的JSON字段、请求头等等。发送函数就是所有能发起网络请求的APIfile_get_contents、curl_exec、requests.get、HttpURLConnection.connect、http.Get等等。当一个用户可控的变量没有任何校验或过滤地流入了一个发送函数基本就可以判为SSRF候选点。如果中间有过滤就继续看过滤是不是可以被绕过——比如只做了字符串替换、只匹配了常见关键字、只校验了开头字符之类。9.2 常见框架里的高危API速查不同语言和框架里负责远程请求的API不太一样。我列一个高频速查表方便你过代码时快速定位语言/框架常用请求函数/库高危点PHPfile_get_contents、fopen、curl_execfile_get_contents支持file协议curl默认协议面广Pythonrequests.get、urllib.request.urlopen、httpx.get默认只支持HTTP(S)但重定向处理和DNS解析需要关注JavaHttpURLConnection、OkHttp、HttpClient重定向默认不开启的话反而更安全但代码如果手写会踩坑Gohttp.Get、http.Client.Do标准库很克制但自定义Transport时容易放松限制Node.jsaxios.get、node-fetch、http.request默认行为不一定安全需要看代理和重定向配置RubyNet::HTTP、OpenURIOpenURI支持多种协议需要额外限制这个表不用背关键是拿到项目代码后先全局搜索哪些地方出现了远程请求API再顺着这些API的调用链往回看参数来源。9.3 业务逻辑层面的SSRF藏在复杂流程里的威胁最后一类容易被漏掉的SSRF是藏在复杂业务逻辑里的情况。比如一个从远程导入CSV的功能可能先下载文件、再解析内容下载这一步就存在SSRF。一个生成PDF报告的功能可能内部用无头浏览器去访问报告中的封面图片URL封面图片URL如果可控制就又是SSRF。一个客服聊天中的文件预览功能可能把用户发来的链接交给后台爬虫去抓取用于显示摘要这里也藏着SSRF。这类业务逻辑型SSRF光靠自动扫描往往发现不了需要审计者对业务流程本身有理解。这也是为什么我说SSRF检测的经验最终会沉淀成一种业务敏感性——看到某个功能脑子里自动闪过这里服务器会不会去请求外部地址这个念头。有了这个直觉就不容易漏。10. 最后说几句实在话SSRF这个漏洞在OWASP Top 10里虽然是近几届才被单独列出来但它在实际安全场景中的价值一直非常高。从入口暴露面广、到内网穿透能力强、再到云环境下的高危利用它几乎涵盖了Web安全里从外到内框架的经典路径。对我个人来说学SSRF最大的收获不是记住那几条利用语句而是建立了一种怀疑一切服务器主动请求的思路。现在看到任何远程加载URL分享Webhook回调之类的功能我的第一反应永远是这个请求地址可控吗可控的话服务器能访问到什么这些内部资源有没有被暴露到回环地址或内网网段如果你也想把SSRF真正学透我的建议是不要只靠看文章要自己搭一个脆弱服务去练手。找一个简单应用的源码删掉所有校验逻辑然后亲手去试各种绕过方式再亲手去写一套修复代码最后再亲手绕过自己的修复。这一整个循环走下来你对SSRF的理解会比读十篇文章都深。希望这篇内容能帮你把SSRF从听说过变成懂原理、会排查、能修复。如果有实际案例拿不准的欢迎一起讨论——毕竟这类漏洞多聊一次就可能少踩一个坑。
阅读完成 · 觉得有帮助?
咨询建站