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

电商系统SSRF漏洞实战:攻击路径与三层防御体系

电商系统SSRF漏洞实战:攻击路径与三层防御体系 ★ FEATURED ARTICLE
电商系统的开发迭代节奏快业务方追着上线安全侧往往赶着补漏。SSRFServer-Side Request Forgery服务端请求伪造在这类系统里出现频率相当高——原因很直接电商天生需要拉取外部资源的功能而这类功能一旦写得不谨慎就等于把一把内网探测的钥匙交到了攻击者手里。多年来我做电商安全相关工作见过几个真实被SSRF打穿的场景——图片代理、回调通知、订单导出每一个都实打实造成了内网敏感信息泄露。这篇文章我会从攻击者视角把电商系统里SSRF的常见利用路径拆开来讲再切回防守方视角说清楚每个漏洞背后到底缺了哪一层防护以及我在实际项目里怎么一步步把洞补上的。内容适合刚接触Web安全的新手也适合正在给业务系统做加固的开发者参考。1. SSRF漏洞的本质与电商场景的特殊性1.1 一个比喻服务端帮你代驾把SSRF理解成代驾就很容易了。正常情况下你自己开车去某个地址路径完全由你控制。但有些系统功能设计成你把地址告诉服务端服务端开车帮你跑一趟——比如电商的图片代理、支付回调、URL预览。问题在于服务端这台代驾坐在一辆权限很大的车里它能去很多你自己去不了的地方内网、本机、云元数据服务而它出发前往往只看了你给的地址一眼根本不管那个地址通向哪里。从技术上说SSRF就是服务端接收了用户可控的URL并代替用户发起请求。本质原因是信任边界没划清楚——服务端请求出去的流量默认被赋予了比客户端更高的网络权限。攻击者利用的恰恰是这层身份差你让服务端去请求一个内网地址它真的会去而且响应还会带回来给你看。1.2 电商系统为什么容易踩这个坑电商的业务形态决定了它有一大堆必须请求外部URL的功能点商品图片处理卖家传一个图片URL平台后端去拉取生成缩略图。支付回调通知网关回调商户系统业务侧再去确认订单状态。物流轨迹查询对接第三方物流API需要转发请求。发票/电子凭证生成渲染PDF时嵌入用户提供的远程图片或链接。开放平台回调开发者配置webhook地址平台往这个地址推消息。运营活动配置运营填一个链接做跳转或内容抓取。这些功能在开发时往往由不同团队实现各自都有自己的URL解析逻辑和网络配置。最麻烦的是很多系统最初部署在一个整体内网环境里服务端不仅能访问公网还能直接访问数据库、Redis、内部管理系统、云元数据地址。这种能出能进的网络模型为SSRF提供了充足的发挥空间。我在一次评估里看到过一个典型的案例某电商后台的按URL生成商品主图功能后端直接把前端传来的URL拼进HTTP客户端去请求响应数据回写本地文件。当时我一测请求http://127.0.0.1:6379/都能返回Redis的报错信息说明服务端根本没有做任何限制。这个洞的修复优先级直接拉到最高——因为它不仅能读本机服务还能往内网横向打。2. 攻击面梳理电商系统里的危险入口2.1 五个典型入口业务排查时对照自查结合我实际测试过的项目电商系统里SSRF高发的入口大致有五类你可以拿着这个清单去自查代码图片处理类。这是重灾区。很多电商平台支持卖家通过URL上传商品图、头像、素材。后端拿到URL后不管三七二十一就用file_get_contents或者HTTP客户端去拉取。攻击者把URL改成内网地址后响应内容如果被写进图片文件并回显到前端就成了一个完美的信息读取通道。支付与订单回调类。支付成功后第三方网关会回调商城的通知接口商城收到通知后要主动去查询支付平台的订单状态。这里虽然URL是固定的但有些系统为了灵活允许配置回调地址前缀或测试环境地址。一旦这个配置项暴露给用户攻击者一样可以构造请求打到内网。导出与渲染类。电子发票、订单报表导出、PDF生成这些功能经常需要加载远程资源。特别是PDF渲染引擎它内部会去请求HTML里引用的图片、CSS。如果你能控制模板内容或者参数等于给了服务端一个任意URL请求的入口。开放平台Webhook类。开放平台一般允许开发者配置消息接收地址平台服务器作为客户端去POST事件数据。这个入口的问题在于有些平台对配置的URL只做了简单的前缀校验攻击者稍微绕一下就能打到云元数据或其他内网服务。爬虫与内容抓取类。商品详情抓取、竞品监控、评论聚合这类功能本质就是给我URL我去抓。如果你把抓取结果比如标题、摘要、图片URL回显到页面上那就是个天然的SSRF回显组合。2.2 攻击者是怎么推演路径的站在攻击者角度思路其实很朴素先找一个能让我指定URL的参数再逐步探测可以打到哪些地方。他们通常按下面几条线索走找参数名url、link、src、target、callback、redirect、image这些词太显眼了全量抓包扫一遍参数就能定位。看功能入口图片上传、PDF生成、Webhook配置这些功能往往是产品经理明确提过要支持输入链接的直接翻功能清单就能找到。看客户端代码前端JavaScript里如果出现img.src baseUrl userInput这类代码后端必然有一个对应的拉取逻辑。确定入口之后攻击者不会上来就打内网而是先做验证。验证方法很简单配置一个自己的服务器把目标URL指向自己的服务器看目标服务端会不会来请求。如果来了好SSRF确认了。下一步就是试探它能访问的网段——先测127.0.0.1再测内网段再测云元数据地址。这些探测请求在日志里看起来就是一堆异常的IP访问记录如果当时没人盯着攻击者就能从容地扩大战果。2.3 一条真实的利用链路示例我复盘过一个电商客服系统被SSRF打穿的真实链路整个过程非常典型。入口是客服后台的消息图片预览功能——坐席在聊天框贴一个图片链接后端会去拉取并生成预览图。攻击者把链接改成云平台内网API的地址发现响应被原样返回了。于是他顺着这个通道用内网IP字典扫了一遍192.168.x.x网段找到一个开放的运维管理系统通过该系统的一个弱口令进入了后台拿到了大量客户订单数据。整条链路从最初的SSRF到数据泄露前后只花了半天。这个案例给我的教训很深SSRF很少是一次攻击就结束的漏洞。它更像一个跳板本身可能没多严重读个本机端口而已但一旦配合内网其他弱点破坏力就会指数级放大。所以防御的时候不能只盯着SSRF本身还要想清楚你的内网里有哪些一旦被跳进去就会很惨的东西——那些才该是防护的重点。3. 从攻击者视角拆解SSRF利用手法3.1 先说探测怎么确认一个SSRF真实存在判断某个参数是不是SSRF核心就看两件事能不能让服务端发出请求响应能不能回显。第一件事用自己可控的服务器做探针。我在公网搭一个简单的HTTP服务记录所有访问日志。然后往目标参数里填http://我的服务器/ssrf-test提交后去日志里翻有没有来自目标系统服务器IP的记录。有就是存在请求发出没有可能被拦截了也可能目标系统压根没执行这个逻辑。第二件事看回显。有些SSRF是盲打型的请求发出去了但响应不回显只能靠时间差比如请求一个很慢的地址观察响应时间变长来判断。电商场景里大部分SSRF带回显因为拉取图片、抓取内容这些功能本身就要把结果展示出来。回显形式有几种直接输出响应体、以图片形式输出二进制内容、在报错信息里透露部分响应内容。其中以图片形式输出最隐蔽——内容会被放在一个img标签里浏览器自动解析。就算返回的是文本只要文本里有关键信息照样能被看到。探测信息的方法也很多我常用的几条请求http://127.0.0.1:80/看返回的页面标题或者内容判断是不是本机的Web服务。请求http://127.0.0.1:6379/Redis在不开启认证时会返回ERR wrong number of arguments之类的报错这个报错本身就是有效的指纹信息。请求http://127.0.0.1:22/SSH服务会返回一段版本字符串部分HTTP客户端会把这段内容原样带回来。请求http://169.254.169.254/latest/meta-data/云环境下的元数据地址如果网络能通这就是最致命的探测——直接暴露云账号的临时凭证。3.2 绕过思路黑名单和白名单是怎么被突破的确认SSRF之后很多系统的第一道防线往往是一份IP黑名单或域名白名单。攻击者真正花心思的地方就在绕过这些名单。我总结了几个最常见的绕过方向防守方照着这份清单就能把自己的防护测个七七八八。解析差异绕过。这是最经典的一类问题。比如黑名单里写了127.0.0.1但攻击者用十进制的2130706433表示同一个地址——127.0.0.1的十进制就是2130706433不少解析器会把后者解析成同一个IP。十六进制0x7f000001、八进制0177.0.0.1效果也一样。还有http://127.1/这种简写形式很多客户端会补全成127.0.0.1。这类问题本质上是URL解析器之间的实现差异——你校验用的解析器和后端实际发请求用的解析器不是同一个自然就能找到两个解析器判断不一致的写法。重定向绕过。攻击者先请求一个白名单里的域名比如自己的公网服务器配置一个302跳转跳转目标指向http://127.0.0.1:6379/。如果后端HTTP客户端默认跟随重定向白名单校验只发生在第一次请求那第二次请求就绕过了。这个问题的关键点是校验和实际请求之间存在一个时间窗口重定向让这个窗口直接放大为一次完整的请求。DNS重绑定。攻击者注册一个域名第一次解析返回白名单IP比如8.8.8.8第二次解析返回内网IP比如127.0.0.1。后端如果先做DNS解析校验再发起请求两次解析的结果不一致校验就被绕过了。这个手法的实现复杂度稍高需要控制DNS服务器的TTL和响应但在SSRF利用里属于相当成熟的技术。URL格式歧义。利用符号http://白名单域名127.0.0.1/很多解析器会把后面的内容当成真正的地址。还有#号http://127.0.0.1#白名单域名.com#后面在标准URL里是fragment不参与连接但有些字符串匹配逻辑会把它当成域名的一部分来判断。再有就是编码http://%31%32%37%2e%30%2e%30%2e%31/URL编码后的IP绕过纯字符匹配的黑名单很轻松。3.3 从SSRF到RCE协议层面的更进一步HTTP协议只是SSRF的起点真正让人头疼的是攻击者把服务端请求客户端的协议能力挖掘出来。Java的HttpURLConnection、Python的requests、PHP的cURL它们在协议支持上各不相同有的能发gopher://有的能发dict://这些协议往内网打会有完全不同的效果。gopher://在SSRF利用里地位很高因为它可以发送原始的TCP字节流。一个经典的利用是把服务端的Redis当跳板拿Shell——通过gopher协议构造Redis命令写入crontab或者SSH公钥如果Redis服务以root权限运行且没有认证攻击者就能拿到一台内网机器的控制权。我在实际测试中见过不止一次类似的场景电商系统的运营后台有一台Redis因为内部使用所以没开认证恰好又被一个不设防的SSRF功能指到了结果就是被写入了计划任务。防范这类利用的核心思路是在协议层做白名单——明确允许http/https其他协议一律拒绝。但要注意有些编程语言的HTTP客户端库本身支持重定向到其他协议需要在配置层面显式关闭。比如Java的HttpClient在某些配置下会跟随重定向到file://或gopher://导致你以为只允许HTTP实际却放行了任意协议。这个问题我在代码审计时见到过很多次配置上的一个小疏忽就是整条防线崩塌的起点。4. 防御体系搭建从业务代码到网络架构4.1 代码层的核心原则校验要跟实际请求对齐代码层防SSRF我总结下来就三句话拒绝内网、白名单协议、解析一致。第一拒绝内网。这意味着要定义清楚哪些IP不能访问——本机回环地址127.0.0.0/8、内网保留地址段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、链路本地地址169.254.0.0/16——都要列进黑名单。但这里有个关键经验不能只校验用户传进来的字符串要在DNS解析之后校验真实IP。因为攻击者可以用一个公网域名指向内网IP字符串层面的检查根本不生效。正确顺序是解析域名拿到IP列表逐一校验每个IP是否命中黑名单任何一个命中就拒绝。第二白名单协议。把允许的协议收敛到http和https这一步通常不会破坏业务——电商系统的图片拉取、回调通知、网页抓取没有哪个功能真的需要gopher或者file协议。第三解析一致。这是最容易忽略的一条。我见过一个系统用Java的URL类做校验却用Apache HttpClient发请求两个库对http://user127.0.0.1:8080/这类URL的解释完全不同结果校验通过了实际请求却打进了内网。解决的思路是校验用的解析器和实际发请求的客户端必须是同一个库、同一个版本或者干脆自己实现一个统一的URL解析函数所有逻辑都走这一个函数。下面这段代码是Python后端里一个比较稳妥的SSRF防护实现实测在生产环境跑过一年误伤业务的案例为零import ipaddress import socket from urllib.parse import urlparse def is_ssrf_safe(url: str) - bool: # 1. 校验协议 parsed urlparse(url) if parsed.scheme not in (http, https): return False # 2. 解析域名并获取所有IP host parsed.hostname if not host: return False # 3. 防止DNS重绑定解析出的IP必须全部合规 try: infos socket.getaddrinfo(host, None) except socket.gaierror: return False 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(0.0.0.0/8), ipaddress.ip_network(100.64.0.0/10), ] for info in infos: ip ipaddress.ip_address(info[4][0]) # 4. 检查是否为私网/保留地址 for net in blocked_networks: if ip in net: return False # 5. 检查是否为公网但指向异常可选IP反查域名 return True这段代码的思路是协议白名单先挡一层DNS解析后拿到所有候选IP逐一和阻断网段比对任何一个命中就直接拒绝。注释里的防DNS重绑定很关键——不要只校验第一个IP要把所有解析结果都过一遍。当然这套代码本身不能完全防住DNS重绑定解析和请求之间仍有时间窗口要彻底解决需要配合网络层的双向校验这就要说到第二部分。4.2 网络层兜底最后的防线代码写得再好也架不住某个老项目绕过你所有的校验逻辑。所以网络层的兜底是必须做的。核心思路很简单服务端对外发出的流量默认全部阻断只放行明确需要的目标。具体做法是在防火墙或者Kubernetes的网络策略上配置出站规则。电商系统大部分对外请求都集中在少数几个目标支付网关的固定域名、物流API的固定域名、CDN地址、资源抓取的白名单域名。把这些域名解析后的IP段加进出站白名单其余出站流量一律拒绝是最理想的状态。但现实往往没这么美好——业务要抓取的商品图片可能来自任意第三方没法做成白名单。这时候退而求其次按端口收敛只允许出站访问80和443端口其他端口全部阻断。这一步就能挡掉大部分gopher://打Redis这类利用因为Redis默认跑在6379不在放行范围里。再进一步如果条件允许给服务端做一个独立的出网代理。所有需要外呼的请求强制走代理代理上做URL校验、协议校验、目标IP校验。业务代码不用自己写防御逻辑代理统一收口。这个方案的运维成本会高一些但对有多个团队、多套系统的电商平台来说在代理层统一收口比让每个系统各自防一遍要靠谱得多。云环境还有一个需要特别注意的点云元数据服务地址169.254.169.254在网络层就要封掉别指望代码层校验。因为如果攻击者已经拿到容器或服务器上的代码执行权限代码层校验对他们来说形同虚设只有网络层才能真正拦住到元数据服务的流量。这个地址被封了之后之前很多利用云IAM临时凭证的SSRF攻击路径直接断掉收益非常大。4.3 业务侧的不变量把SSRF的杀伤半径压到最小即便SSRF拦不住也可以通过业务侧设计把危害降到最低。我在实际加固中会给业务团队提几个不变量要求内网服务一律不绑定在0.0.0.0上。Redis、数据库、内部管理系统绑定到具体的内部管理IP不要图省事监听所有网卡。这样即使SSRF打到某台机器的某个端口也只能访问到该机器上绑定对外网卡的服务内网其他服务不会被直接暴露。内网服务必须开启认证。电商内部系统之间经常默认内网就是安全的Redis不设密码、数据库用弱口令这类现象我见过太多。SSRF本身只是个请求转发器它能打到的内网服务如果都是裸奔状态那就等于攻击者拿到了一份自助餐的入场券。敏感接口要做来源校验。内网管理后台、运维API、测试环境这些系统对外部来源的请求要验证来源IP或携带内部认证头。就算SSRF打过来了没有内部凭证也做不了敏感操作。这套思路本质上是纵深防御——每一层防护都假设上一层已经被绕过丢给下一层的仍然是一个相对安全的状态。单独看每条都不是什么高深技术但组合在一起攻击者要打通整条链路就得同时突破四五道关卡这种成本足以劝退绝大多数人。5. 踩坑实录与排查心得5.1 误报处理怎么区分SSRF和正常业务请求上了防护手段之后安全团队遇到最多的就是误报。一个典型的场景配置好出站白名单第二天业务反馈商品图拉取失败了——排查下来发现是某个第三方图床的CDN IP经常变白名单里没跟上。这种问题里误伤业务往往比漏掉攻击更伤士气因为业务方会觉得安全在添乱。我的处理经验是给SSRF防护加上灰度开关和告警机制不要一上来就硬阻断。先在代码或代理层面开启检测模式命中规则就记日志、发告警但暂时不拦截。跑一两周收集足够多的命中样本人工筛选出真正的攻击流量和正常的业务流量再把规则从告警切到拦截。这样既不会误伤业务又能保证规则的有效性。另一个细节是告警里要带足上下文信息——请求来源、用户ID、完整URL、解析后的IP、命中的规则别只记一条SSRF blocked。没有上下文的告警等于没有告警出了问题根本没法排查。5.2 复现与修复验证的坑修复SSRF之后验证环节经常出现假阳性。我遇到过最典型的一种情况测试时直接往参数里填http://127.0.0.1/发现返回403就以为修好了。实际上攻击者完全可以用别的方式表达同一个地址——十进制IP、URL编码、重定向——你的修复可能只是挡掉了最直白的那一种写法。所以验证的时候要建立一个完整的绕过用例集至少覆盖以下几类明文内网IPhttp://10.0.0.1/替代格式IPhttp://2130706433/、http://0x7f000001/简写格式http://127.1/编码绕过http://%31%32%37%2e%30%2e%30%2e%31/重定向绕过搭一个302跳转到内网地址的探针服务器DNS重绑定如果条件允许用带短TTL的域名做验证端口探测http://127.0.0.1:6379/、http://127.0.0.1:22/另外修复的代码要放到真实环境里验证别只在本地测试。因为生产环境的DNS解析策略、HTTP客户端配置、网络出方向规则都和本地不一样本地测试通过的逻辑上生产之后可能因为某层代理的存在而出现新的绕过路径。5.3 应急响应的思路发现SSRF之后先做什么万一线上真的发现了SSRF漏洞应急的第一反应不应该是赶紧把漏洞修掉而要想清楚这几件事确认影响范围翻日志看这个入口被哪些IP在什么时间调用过有没有命中内网地址的异常请求。如果发现了要顺着请求串追下去看攻击者后续做了什么。封禁当前的利用通道在代码修复完成前先暂时下线这个功能入口或者在WAF层加一条匹配该URL和参数的临时规则硬阻断。评估内网暴露情况确认SSRF实际能访问到内网哪些服务。这一步要快因为如果内网已经有敏感服务被扫到攻击者可能已经在横向移动了。修完不是结束修复上线之后要持续观察一段时间确认没有新的绕过流量。我在项目里通常会把告警周期拉长到两周防止攻击者在修复后换一种手法再试。还有一个容易被忽略的细节SSRF漏洞的痕迹往往藏在应用日志里。如果日志记录了完整URL你可以从一堆http://127.0.0.1记录里看到攻击者的扫描路径。但很多系统的日志只记录被截断的URL或者不记录查询参数导致事后根本没法回溯。所以我建议给所有接收外部URL的参数都加上完整请求日志至少保留30天这在应急的时候能救命。结尾几句实在话SSRF这个漏洞我在电商系统里反反复复见过太多次它谈不上多高深但架不住业务代码里到处都是拉个URL回来的需求。防它的核心不在某个单一技巧而是把代码校验、网络管控、内网收敛这三层都做好缺一层都可能被绕。我个人在实际操作中体会最深的一点是不要把SSRF防护当成安全团队单方面的任务。代码里加几行校验很容易但真正决定漏洞能不能被利用的是你内网的服务架构成不构成一个容易被跳板打穿的状态。推动业务团队把内网服务都收口、都加上认证这个工作比写一百行过滤代码都更有价值。最后再分享一个小技巧每次评审新项目或新功能时只要看到传入URL这四个字就默认它里面有SSRF先过一遍协议白名单、IP校验、出网管控这三关再放行。这套条件反射让我在很多项目刚起步的时候就把洞堵在了摇篮里省下了后面数不清的返工时间。希望这篇文章里拆解的攻击路径和防御思路也能帮你在自己的系统里建立起同样的条件反射。
阅读完成 · 觉得有帮助?
咨询建站