资讯动态

SSRF服务端请求伪造实战:内网探测与端口扫描技巧解析

发布时间:2026/9/15 19:44:51 来源:尧图企业网站定制
SSRF服务端请求伪造这个漏洞这几年在CTF比赛里出现的频率是真的高也是真实业务环境里特别容易被低估的一类问题。写这个选题是因为我见过太多人把SSRF单纯理解成“能访问内网就行”但实际上它从成因、探测到利用有一整套判断逻辑尤其是在内网网段识别、端口扫描这块很多人卡在“请求确实出去了但不知道怎么看结果”这一步。这篇文章会把漏洞的成因、内网探测思路、端口扫描技巧一条线捋清楚同时结合CTFHub技能树SSRF模块的常见场景讲讲实战中真正能落地的判断方法新手可以顺着思路复现有基础的人也能从这里找到几个平时容易忽略的细节。1. 先搞清楚SSRF是什么从一次“发出去的请求”说起1.1 一句话定义和请求背后的信任关系SSRF全称是Server-Side Request Forgery服务端请求伪造。它的核心逻辑用一句话说就是——攻击者把服务器当成了一个“代理”让服务器代替攻击者去发起网络请求。这里的关键不在于“服务器能不能访问内网”而在于“服务器发起的请求网络层面默认是信任的”。我经常用一个打车的类比来解释这件事。你在APP上叫了一辆网约车司机接单后按导航路线行驶。正常情况下你输入的是目的地司机去的是你输入的地方。但如果APP的功能设计有缺陷你对司机说“你先往东开到了路口再听我指挥”而司机的导航系统居然不校验你说的地方是不是合法目的地那这辆车就可以被绕去任何一个地方。在SSRF场景里服务器就是这个司机它本身具有访问内网、访问云元数据地址、访问本机服务的权限而这些权限是外网攻击者不具备的所以一旦请求目标可控服务器就被劫持成了“带路党”。这个信任关系的本质是网络边界安全模型的一个天然盲区。防火墙、ACL规则往往默认信任“从服务器发出的流量”认为出站请求是业务发起的合法流量所以许多内网服务只监听在172.16.x.x或10.x.x.x这类网段不对内网请求设防。这给了SSRF一个天然的利用基础。1.2 一次SSRF请求会发生什么从用户输入到网络出口我们拆解一次最典型的SSRF请求链路。假设某网站提供了“通过URL获取远程图片缩略图”的功能前端把用户填写的图片地址直接交给后端处理# Python Flask requests 伪代码 app.route(/fetch_image) def fetch_image(): url request.args.get(url) response requests.get(url) return send_file(response.content)这段代码表面上只是“根据URL请求一张图片”但实际上它做的事是用户输入目标地址服务器向目标地址发出HTTP请求拿到内容后再返回给用户。问题就出在这里——url参数是用户完全可控的也没有任何协议、网段、域名层面的过滤用户完全可以填入http://192.168.1.1/、http://127.0.0.1:6379/等地址服务器会乖乖替用户访问这些本来无法直接访问的地址。这里的重点不是代码本身有多简单而是这类代码在真实业务中出现的频率极高。图片裁剪、剪贴板转存、网页截图、PDF生成、邮件预览生成、URL监控、Webhook回调、甚至某些CRM系统的“导入外部链接”功能全部都可能存在这种“传入URL由服务器请求”的模式。这也是为什么SSRF被称为“最容易出现在业务集成接口里的漏洞”。2. 漏洞成因拆解哪些环节最容易出问题很多人以为SSRF的成因就是“URL参数可控”但实际操作下来你会发现更隐蔽的成因藏在各种间接环节里。我把常见的成因按出现频率分成三类每一类都有不同的绕法。2.1 直接可控的URL参数最典型也最好判断直接可控的URL参数就是用户在请求里传了一个完整地址后端原样使用。比如http://example.com/ssrf.php?urlhttp://127.0.0.1:8080/admin这种情况最简单判断是否存在漏洞也非常直接——把URL改成http://127.0.0.1/看响应是否和在公网访问一个不存在的域名有明显区别或者看响应内容里是否包含本机服务的特征信息。不过即使是这种最简单的成因想利用好也有细节。不是所有服务都走HTTP协议Redis、MySQL、Memcached这些非HTTP服务协议头各不一样。如果后端用的是curl而且没有限制协议那就可以尝试gopher://协议比如通过它往Redis服务写入数据。这种利用方式要求请求层支持多种协议实际利用时我会先用一个可控的HTTP服务观察请求是否真的发出、带了什么协议头、能否控制请求方法再做后续判断。2.2 跳转导致的重定向过滤了却绕过去了很多系统做了域名白名单或IP黑名单但过滤逻辑写在了请求发出之前没有处理重定向。攻击者可以在自己可控的域名上配置一个302跳转跳转到内网地址服务器的请求库如果默认跟随重定向就会带着信任身份跳到内网。这种绕法在Python的requests、PHP的file_get_contents、Java的HttpURLConnection里都有对应场景因为这些库默认都会跟随重定向。还有一个类似的变体是URL解析差异。比如http://public.com127.0.0.1/这种写法某些语言解析时取的host是public.com而真正建立TCP连接的却是127.0.0.1。如果过滤逻辑只匹配了字符串里的域名没有做连接地址判定就会被绕过。2.3 DNS解析造成的差异代码里看到的和实际访问的完全不一样这类成因最隐蔽排查起来也最花时间。核心逻辑是后端只验证了URL里写出来的域名没有验证DNS解析后实际连接的IP。域名可以绑定任意IP攻击者先让域名解析到一个公网IP看起来人畜无害在请求过程中把DNS记录切换到内网IP也就是DNS Rebinding。如果是内网测试环境还可以直接用某个公网域名解析到127.0.0.1把过滤直接绕过。WSGI服务器和HTTP客户端之间还存在重复解析的问题。如果WSGI层解析了一次域名做校验应用层再解析一次域名建立连接两次解析结果可能不同。这类问题在Go和Java的某些实现里也出现过。实际排序一下这三类成因的隐蔽程度是递增的直接可控的参数一眼能看到重定向绕过多半需要抓包确认DNS解析差异则往往要打日志才能定位。3. 通过SSRF探测内网从外网到内部的“地图”3.1 怎么确定漏洞存在响应差异是最核心的依据拿到一个疑似SSRF的点第一件事不是急着扫端口先确认两件事一是请求确实从服务器发出了二是请求结果能返回到客户端或者至少能通过响应差异推断出结果。我把这几个判断维度整理成了一张表判断维度可控URL返回200可控URL返回404/500本机服务特征外部HTTP服务收到请求说明目标服务存在并可访问目标存在但路径不对返回内容含服务指纹自己搭建的观察端收到请求判断价值高中高高具体操作上可以先在公网VPS上起一个HTTP服务用nc -lvp 8080监听然后把SSRF的URL填成http://你的VPS:8080/test。如果VPS收到了请求说明服务器确实按照你的目标地址发出了请求。下一步测回显把URL填成http://127.0.0.1/看看响应内容里有没有像Apache、Nginx这类默认页面特征词有就说明结果回显到客户端了。3.2 内网网段探测方法不要盲扫按网段特征走确定能回显或能判断响应差异后就要开始探测内网。这一步最忌讳的就是拿一个网段从头扫到尾。内网地址通常落在几个标准私有网段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16。但这只是理论范围实际业务内网通常只用了其中一小段盲目扫描会浪费大量时间。我的做法是分三步先通过服务器自身信息判断网段特征比如http://127.0.0.1/如果返回了一些管理页面页面里可能引用了内网IP比如http://172.16.10.3/assets/logo.png这就能确定内网实际网段。如果没有任何回显信息就按默认网关地址来猜比如192.168.1.1、10.0.0.1、172.16.0.1先用网关地址判断该网段是否存活。确定存活网段后用“网关地址低段位主机地址”的组合来探测常见主机不用全段扫描。一个/24网段255个地址里真正存活的主机可能只有几台与其全扫不如优先扫1-20、100-110、200-210这些常用段位节省请求次数和时间。3.3 探测内网时怎么判断响应差异超时、状态码、内容三位一体内网主机探测和公网不一样很多内网服务不会返回标准HTTP状态码甚至根本不监听HTTP。判断一台主机是否存活我会同时看三个维度时间维度请求内网存活主机IP通常能在1到3秒内返回响应即使拒绝连接也会快速返回。如果请求一个不存在的IP往往要等到超时比如5秒、10秒甚至更久。状态码维度存活的Web服务一般会返回特定的状态码如200、403、404而拒绝连接的情况通常表现为请求失败、连接重置等错误。内容维度回显的内容里如果包含特定的HTML标题、Banner信息那基本就能确定是某类服务。比如用http://127.0.0.1:8080/探测时如果返回内容里出现了Jetty或者Tomcat的默认页面说明本机8080端口存在Java中间件。用http://192.168.1.1/探测时如果返回了路由器的登录页说明这台设备可能是网关。这套判断逻辑在任何SSRF场景里都是通用的只是响应差异的具体表现会因目标服务不同而变化。实际的漏洞利用用的是自动化脚本但判断原理和手工探测完全一致只是把“看响应”的过程程序化而已。4. 端口扫描利用把SSRF当成“端口扫描器”用4.1 为什么SSRF可以替代传统端口扫描器传统端口扫描是攻击者直接向目标IP的各个端口发起TCP连接或发送探测包适用于外网直接可达的目标。但内网环境下攻击者无法直接连接到内网IP就只能通过SSRF让服务器代替发起连接本质上是把一个“从公网发起扫描”的动作转换成了“从内网服务器发起扫描”从而绕过网络隔离。SSRF端口扫描的核心原理是让服务器请求http://目标IP:端口/然后根据响应结果判断端口开放状态http://example.com/ssrf.php?urlhttp://192.168.1.10:22/ http://example.com/ssrf.php?urlhttp://192.168.1.10:80/ http://example.com/ssrf.php?urlhttp://192.168.1.10:3306/这种方式有一个天然优势请求来源是内网服务器许多内网服务只做了基于IP的访问控制并不校验端口扫描行为检测系统也很难将这类流量标记为端口扫描因为从服务器日志看这只是正常的业务请求。4.2 基于回显差异的端口判断技巧端口开放与否在SSRF响应上通常会表现出明显的差异。我把常见情况整理成了一张表方便对照判断端口状态响应表现判断依据端口开放且为HTTP服务返回HTML内容、状态码200/403等内容明显、响应时间短端口开放但非HTTP服务返回协议错误、连接后立即被RST有连接行为但无HTTP响应端口关闭连接超时或被拒绝响应时间明显变长或报错端口被防火墙过滤请求长时间无响应接近超时值实际测试时我会先测几个已知的明确端口来做参照。比如先请求http://127.0.0.1:22/再请求http://127.0.0.1:65534/前者会立刻产生SSH Banner相关的连接行为后者要么连接不上要么超时。确定了这两种基准差异后再去扫其他端口就能准确判断状态。这里有一个细节很容易忽略不是所有开放端口都会返回内容有的服务在收到HTTP请求后会直接断开连接这时候看响应时间也很有用。比如Redis监听在6379端口收到HTTP请求后会返回一段-ERR wrong number of arguments开头的错误并关闭连接这个响应特征就非常明显。4.3 更高阶的响应差异判断结合协议与内容特征端口扫描的终极判断是内容而不是端口本身。同样一个8080端口可能是Spring Boot的默认端口也可能是某个内网管理后台的端口。在CTF比赛和真实业务里我会直接用SSRF去请求http://目标IP:端口/把返回内容里的标题、关键字抓出来一步到位判断服务类型而不是扫完端口再做指纹识别。举个例子请求http://192.168.1.20:7001/时如果返回的页面里包含WebLogic Server字样基本可以确定这是WebLogic中间件后续可以继续寻找对应版本的漏洞。如果请求http://192.168.1.20:9200/返回的是JSON格式的节点信息那这就是Elasticsearch服务。这种“端口扫描指纹识别一体化”的做法效率很高也符合实际利用时的判断逻辑。在CTFHub的技能树SSRF模块里很多题目不会直接给出内网目标的端口而是让选手通过判断响应差异一步步锁定目标服务考验的正是这种综合判断能力。4.4 协议层利用当HTTP不够用时用gopher扩展攻击面HTTP协议只能处理Web服务但内网里大量有价值的目标是非HTTP协议Redis、Memcached、MySQL、FastCGI这些服务接收的是自定义协议这时候需要用到gopher协议。gopher协议的核心价值是可以构造任意的TCP数据流相当于把服务器变成一台原始TCP客户端。用gopher访问Redis的6379端口可以往Redis里写入命令构造出反弹Shell或写入Crontab的效果访问FastCGI的9000端口可以构造出FastCGI协议数据包进而实现任意文件读取甚至RCE。但gopher协议也有明显的限制。PHP的curl扩展和Python的requests库对gopher协议的支持程度不一样很多情况下需要结合响应判断协议是否可用。在CTF场景里题目通常会预先设定了curl的协议白名单这些细节需要根据题目提示和响应特征来判断。我的经验是先用http://确认目标可达再尝试gopher://构造数据包如果返回的时间明显更长或者服务端产生了预期的响应变化说明gopher可用。5. 常见问题与排查技巧实录5.1 踩过的坑URL编码、超时与盲区做SSRF练习时最容易踩的坑首当其冲是URL编码问题。许多框架和中间件会先对URL做一次解码再做请求这就导致原始payload里的特殊字符经过两层处理之后完全变形比如http://127.0.0.1:6379/的冒号在某些场景下需要编码成%3A而框架解码一次后拿到了正确的URL但另一层又处理了一次导致请求失败。排查这类问题时我会先看请求日志确认服务器实际发出的目标URL和原始URL的差异再针对性地调整编码层数。第二个坑是超时设置。很多真实业务的请求库默认超时时间很长比如10秒或30秒在扫内网端口时如果以“响应时间明显变长”作为端口关闭的判断依据就要特别小心超时设置是否合理。我曾经在一台配置不高的测试机上做内网探测默认超时是30秒扫20个端口花了将近10分钟效率极低。后来把超时调到5秒同时配合并发的思路才把时间压下来。第三个坑更隐蔽某些内网服务返回内容为空状态码也是204或者无响应这种情况下无论端口是否开放响应看起来都差不多判断变得极其困难。我的经验是换一个请求路径试试比如在URL后加一个不存在的路径/xxx.html有的服务会返回404说明端口确实存活只是默认路径没有内容。还有的场景需要改用CONNECT方法或者POST请求才能触发响应这些都得多试。5.2 排查思路从失败里找规律遇到SSRF探测失败时我一般按下面的顺序排查先确认目标IP和端口本身是否可达。可以用本机直接访问试试如果本机也访问不了那问题不一定出在SSRF上。再确认过滤规则去了哪一层。是框架层的URL校验拦截了还是WAF层直接掐断了流量通过构造特殊编码和对比请求日志就能定位。接着确认协议支持情况。用http://失败试着改用https://有些后端的白名单只区分了协议前缀没校验端口换一个协议就绕过去了。最后确认回显链路。有时候SSRF请求已经成功了但响应没有返回到客户端这时候需要用DNS外带或者延迟时间来判断而不是只看页面上的内容。CTFHub技能树SSRF模块里就有这类需要综合判断的题目第一关往往是最基础的“URL直接请求本机”后面几关就会加入协议限制、跳转、DNS解析差异等过滤条件每一关其实都是一次完整的漏洞成因复习。5.3 防御思路从修复到监测一套带走前面聊了这么多攻击侧的思路必须补一段防御内容。SSRF的修复不是加一个IP黑名单那么简单而是要从根源上消除“服务器代发请求”的能力风险。最核心的修复方式协议白名单域名白名单IP白名单。协议只允许http和https域名放入预置白名单少用动态URL参数。发起请求前先解析域名对解析得到的IP做私网地址段、保留地址段、回环地址的判断发现有这些地址则直接拒绝。禁用重定向跟随或者对重定向进行二次校验。如果业务需要跟随重定向必须在每次跳转时都重新校验目标地址不能只校验第一层。这个点很多SDK默认没做需要在业务代码里显式配置。严格控制出站流量。网络层设置服务器的出方向ACL只允许访问业务必需的公网地址和端口避免服务器具备访问全部内网的能力。这是最坏情况下的兜底方案即使应用层被绕过网络层也能挡住大部分探测。统一封装出站请求SDK。把所有外发请求的逻辑收敛到一个公共组件里在这个组件里统一完成地址校验、DNS反解析校验、返回值清洗等操作避免每个业务模块自己写一套。我在实际开发中一直用这个思路既统一了安全策略也减少了重复代码。监测角度也有一些可操作的手段。对服务器出站请求日志做记录对异常目标如内网地址、保留地址的访问触发告警对请求头的Host和Referer字段做关联分析识别出“用户输入URL触发服务器访问”的行为模式。这些措施不需要重改业务架构落地成本低效果却很明显。6. 从CTF到真实授权测试SSRF学习路径上的最后一步接触SSRF有一段时间之后我的体会是这样的CTFHub技能树上的SSRF题目像是一个浓缩的实验室把漏洞成因、探测思路、协议利用拆成了一个个可复现的小步骤非常适合建立对SSRF的系统性认知。但真实业务环境要复杂得多生产环境的请求链路上可能有网关、有WAF、有复杂的DNS解析逻辑还有各种SDK内部对URL的二次处理这些因素会让简单的漏洞变得蜿蜒曲折。如果你打算更深入地研究和测试SSRF我建议你在自己搭建的本地环境中多做实验同时养成写脚本的习惯。我自己练习时用的是一套轻量脚本接收一个URL批量替换成内网网段的IP然后用不同的超时时间和请求方式跑一轮把响应结果和耗时都记录下来再人工分析差异。这样反复做的过程中你会慢慢形成一种直觉看到某个请求超时0.5秒就知道这个端口大概率存在看到某个返回内容里有一小段特殊字符串就能判断出内网跑的是什么服务。最后再分享一个小小的技巧在判断某个SSRF点是否适合做端口扫描时先测一个本机肯定开放的端口再测一个肯定关闭的端口把两组响应特征记录下来作为后续批量扫描的判断基准这比直接盲目扫内网高效得多。很多教练讲SSRF的时候都会绕开这个细节但它恰恰决定了你对端口状态的判断准不准。SSRF的价值不在于“发现了这个漏洞”而在于你能不能沿着一条请求链路把外网和看似安全的内网之间打通一条逻辑通路。你越理解数字世界里“请求默认被信任”这个前提你对漏洞成因的判断以及方案选择的精准度就会越高。

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

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

免费获取报价