Go 回调地址怎么做 SSRF 防护:域名白名单、解析结果与跳转控制
图片抓取接口上线后,最容易被忽略的参数往往就是 url:业务只想下载一张封面,服务端却可能被引到内网管理端口。Go 的 http.Client 上手很好用,但它不会替你判断目标是不是可信地址。真正稳妥的做法,是在发起请求前先解析URL,限制可用协议和端口,解析域名得到所有IP后逐个校验私网范围,同时明确关闭或重新校验重定向逻辑。
防 SSRF 不是写一个“不能包含 127.0.0.1”的黑名单,而是把“允许访问哪些目标”变成可测试的策略;解析、DNS、连接和跳转这几层缺一不可。
- 只接受
https或业务确需的http,端口采用白名单机制。 - 域名不能只做字符串匹配,必须解析 A/AAAA 记录并拒绝回环、私网、链路本地和未指定地址。
- 默认禁用自动重定向;如果业务必须跟随跳转,每一跳都要重新执行同一套校验策略。
- 把解析失败、命中禁区、重定向和响应大小全部记录到可审计的日志里。
一个“远程封面”接口为什么会越权访问内网
故障现场通常很普通:前端传来一个图片地址,后端调用 http.Get,读完响应体后存入对象存储。开发者可能顺手加了一个判断:URL 里不能出现 localhost。这个简单检查挡不住数字IP、IPv6、解析到内网的域名,也挡不住一次 302 跳转。
威胁模型先理清楚三件事:攻击者能控制什么参数、服务端拥有什么网络权限、响应内容会不会直接返回给攻击者。如果服务端能访问数据库管理面、容器元数据或内部HTTP API,那么这个下载功能就是一个“带着服务器权限发请求”的隐形代理。OWASP 也把URL解析差异、协议限制、重定向和DNS解析列为需要同时覆盖的边界校验点。

先把允许访问的目标写成固定策略
不要先写黑名单再不停补漏洞。对图片抓取这类功能,我更建议先和业务确认四个问题:只允许HTTPS吗?是否只允许443端口?目标是任意公网域名,还是有限的CDN域名?是否需要跟随跳转?答案不同,代码里的策略逻辑也应该对应调整。
下面这个策略适合“抓取公网图片”的保守起点:只接受 https,显式指定的端口只能是443;如果业务必须支持 http,就把它作为单独配置项走风险评审流程,而不是默认放开。路径和查询参数交给目标站点处理,但用户不能靠修改URL来切换协议或指定高危端口。
type FetchPolicy struct {
AllowedSchemes map[string]bool
AllowedPorts map[string]bool
FollowRedirect bool
}
var publicImagePolicy = FetchPolicy{
AllowedSchemes: map[string]bool{"https": true},
AllowedPorts: map[string]bool{"443": true},
FollowRedirect: false,
}
策略对象的价值在于可审计:测试同学可以直接确认“http协议被拒绝”“显式8443端口被拒绝”“默认不跟随跳转”,而不是依赖一串散落在处理函数里的零散条件。
URL 解析之后,再检查主机和端口
url.Parse 只负责把文本格式的地址解析成结构化URL,它本身不是安全判定器。解析结果要逐层检查scheme、Host、Hostname和Port;空scheme、带用户名密码、缺少主机名、异常端口这些情况,都应该在发请求前直接终止流程。
func checkURL(raw string, policy FetchPolicy) (*url.URL, error) {
u, err := url.Parse(raw)
if err != nil {
return nil, fmt.Errorf("url parse failed: %w", err)
}
if !policy.AllowedSchemes[u.Scheme] || u.User != nil {
return nil, errors.New("scheme or user-info is not allowed")
}
host := u.Hostname()
if host == "" {
return nil, errors.New("host is required")
}
port := u.Port()
if port == "" {
port = defaultPort(u.Scheme)
}
if !policy.AllowedPorts[port] {
return nil, errors.New("port is not allowed")
}
return u, nil
}
func defaultPort(scheme string) string {
if scheme == "https" {
return "443"
}
return "80"
}
这里不要用字符串拼接的方式去判断 u.Host。比如带方括号的IPv6、大小写不同的主机名和附带显式端口的情况,只有先交给标准解析器处理,后续的检查逻辑才能拿到稳定的输入。
DNS 解析是第二道门:所有结果都要过 IP 校验策略
域名看起来是公网域名,不代表解析结果永远是公网地址。服务端应该拿到A和AAAA解析结果,使用 net/netip 逐个校验每个地址,拒绝回环、私网、链路本地、未指定地址以及业务明确不允许的保留网段。解析失败的时候不能降级成“直接继续发起请求”。
func rejectNonPublic(ctx context.Context, host string) error {
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
if err != nil || len(ips) == 0 {
return errors.New("host cannot be resolved")
}
for _, addr := range ips {
if !isPublicTarget(addr) {
return fmt.Errorf("resolved address is not allowed: %s", addr)
}
}
return nil
}
func isPublicTarget(addr netip.Addr) bool {
return addr.IsValid() && !addr.IsLoopback() &&
!addr.IsPrivate() && !addr.IsLinkLocalUnicast() &&
!addr.IsUnspecified()
}
这一步仍然不是网络层的最终保证:DNS解析结果可能动态变化,建立连接的时候也可能触发重新解析。生产环境还应该在出口防火墙、容器网络或代理层提前阻断内网网段;应用层校验负责给出清晰的业务拒绝提示和审计日志。
重定向不要让第一跳的校验失效
Go 标准库的HTTP客户端默认会自动处理重定向。如果第一跳是公网地址,响应却把客户端跳转到内网地址,最初的校验就完全失去了意义。最简单的方案是直接关闭自动跳转,让业务只接受一个明确的目标:
client := &http.Client{
Timeout: 8 * time.Second,
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
return http.ErrUseLastResponse
},
}
若产品必须支持跟随跳转,不要只把 FollowRedirect 改成 true。应该限制最多跳转次数,并在 CheckRedirect 中对新请求的URL重新跑一遍scheme、端口、DNS和IP的全量校验策略。跨域跳转还要重新确认请求头,尤其不要把原目标的内部凭证直接传给新主机。
把请求封装成“校验后才连接”的统一边界
安全检查最好放在唯一的出站请求入口处,而不是散落在三个不同的handler里面。入口要限制响应体的最大大小、记录拒绝原因,并且避免把上游的原始响应完整回传给调用方。下面只展示边界的核心逻辑,具体的连接绑定和出口代理策略应由部署环境补齐。
func FetchImage(ctx context.Context, raw string) ([]byte, error) {
u, err := checkURL(raw, publicImagePolicy)
if err != nil {
return nil, err
}
if err := rejectNonPublic(ctx, u.Hostname()); err != nil {
return nil, err
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, err
}
resp, err := publicImageClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode = 300 {
return nil, fmt.Errorf("upstream status: %s", resp.Status)
}
return io.ReadAll(io.LimitReader(resp.Body, 5
如果业务对域名有固定名单,优先采用域名或来源白名单,并且把最终解析到的DNS地址也一并记录下来。支持任意公网地址的场景复杂度高很多,不能把“不是私网地址”误当成“就是完全可信目标”。
四组测试决定这条防线有没有用
安全代码最怕只测正常URL。至少把以下输入写成单元测试用例:合法HTTPS公网主机;回环和私网IP;解析到受限地址的域名;返回302的公网地址。再补上无scheme、带用户信息、非允许端口和IPv6地址这些边界场景。
func TestFetchPolicy(t *testing.T) {
cases := []struct {
name string
raw string
want bool
}{
{"public https", "https://cdn.example.test/a.png", true},
{"loopback", "https://127.0.0.1/a.png", false},
{"private", "https://10.0.0.8/a.png", false},
{"wrong port", "https://cdn.example.test:8443/a.png", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, err := checkURL(tc.raw, publicImagePolicy)
if (err == nil) != tc.want {
t.Fatalf("allowed=%v, err=%v", err == nil, err)
}
})
}
}
测试通过也不等于网络边界的防护已经做完。上线验收时再确认出口ACL、代理规则、超时配置、响应大小限制和日志脱敏规则;如果服务部署在云上,还要把云厂商元数据地址当作明确的拒绝对象,在网络层再加一层独立控制。

相关问题
只禁止 127.0.0.1 是否够用?
不够。还要考虑IPv6回环、私网和链路本地地址、解析到受限地址的域名,以及重定向后的新目标。
为什么不建议只用正则判断 URL?
正则很难正确覆盖scheme、用户信息、IPv6、端口和编码的各种差异。先用标准库解析,再对结构化字段做白名单判断,更容易测试和审计。
关闭重定向后,是否就完全安全了?
不是。它只能消除一条跳转路径,仍需要协议、端口、DNS/IP、响应体大小和网络出口策略共同生效。
最后的验收清单
把远程抓取接口交付前,逐项确认:输入只接受业务允许的scheme和端口;URL解析后拒绝用户信息和空主机;A/AAAA解析结果逐个检查;自动重定向默认关闭;必须跳转时每一跳重新校验;响应体有大小限制;错误日志不泄露敏感响应;出口网络还有独立的拒绝规则。
这样设计的重点不是让某个函数“看起来安全”,而是让一次出站连接在代码、测试和网络环境里都能说清楚:它为什么被允许,以及下一跳为什么仍然不能越界。
View Transition API 跨文档过渡怎么落地:同源导航、@view-transition 与降级检查
- 上一篇
- View Transition API 跨文档过渡怎么落地:同源导航、@view-transition 与降级检查
- 下一篇
- Go 接 Ollama API 做模型健康检查:版本、模型存在性与超时处理
-
- Golang · Go问答 | 2小时前 |
- Go 的 io.Copy 为什么会突然变慢:ReaderFrom、WriterTo 与包装器边界
- 251浏览 收藏
-
- Golang · Go问答 | 2小时前 | 并发 · 错误处理 · go · Context · 取消原因 WithCancelCause Go context.Cause context.Err
- Go context.Cause 怎么保留取消原因:WithCancelCause、Err 与跨层传递
- 428浏览 收藏
-
- Golang · Go问答 | 3小时前 | [] · []
- Go embed.FS 里 fs.Sub 为什么读不到文件:路径、斜杠与工作目录边界
- 449浏览 收藏
-
- Golang · Go问答 | 3小时前 |
- Go API 接收分页参数如何防整数溢出:strconv、边界值与数据库 LIMIT
- 225浏览 收藏
-
- Golang · Go问答 | 3小时前 |
- Go slices.Chunk 怎么做批量处理:空批次、底层数组与边界测试
- 127浏览 收藏
-
- Golang · Go问答 | 5小时前 | [] · []
- Go errors.Is 为什么匹配不到:%w 包装、errors.Join 与自定义错误判定
- 407浏览 收藏
-
- Golang · Go问答 | 5小时前 | 并发 · 故障排查 · Go问答 · encoding/json · 配置中心 · sync/atomic · encoding/json atomic.Value Go配置热加载 Decoder 配置复用 旧值残留
- Go 配置热加载为什么偶发读到旧值:JSON 复用、零值覆盖与原子替换
- 311浏览 收藏
-
- Golang · Go问答 | 23小时前 | WebAssembly · Go问答 · 前端交互 · 浏览器存储 · syscall/js · Go localStorage WebAssembly syscall/js js/wasm Wasm运行时脚本
- Go WebAssembly 怎么读写 localStorage:syscall/js 的边界、异常与加载检查
- 122浏览 收藏
-
- Golang · Go问答 | 1天前 | 并发 · 单元测试 · go · Context · AfterFunc · Go问答 context.AfterFunc context.CancelFunc Go回调
- Go context.AfterFunc 怎么避免重复回调:Stop 竞态窗口与测试方法
- 258浏览 收藏
-
- Golang · Go问答 | 1天前 | 标准库 · go · csv · 数据导入 · 错误定位 · Go encoding/csv FieldsPerRecord LazyQuotes ParseError
- Go encoding/csv 读文件遇到字段数不一致怎么办:FieldsPerRecord、LazyQuotes 与错误行定位
- 224浏览 收藏
-
- Golang · Go问答 | 1天前 | 字符串 · go · 工程实践 · Go 配置解析 strings.Cut
- Go strings.Cut 解析配置行:分隔符缺失、空值与旧 Split 的边界
- 207浏览 收藏
-
- Golang · Go问答 | 1天前 | HTTP · 流式处理 · Go问答 · encoding/json · 请求体 · Go DECODE eof 流式解析 json.Decoder JSON请求体
- Go json.Decoder 为什么第二次 Decode 会得到 EOF:请求体读取、空白与流式边界
- 160浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 4787次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4378次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4323次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 4563次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4507次使用
-
- 深入了解Golang网络编程Net包的使用
- 2023-01-23 215浏览
-
- Go语言WEB框架(Gin)详解
- 2023-01-07 399浏览
-
- Go语言Ratelimit服务流量限制
- 2022-12-29 458浏览
-
- Go语言session的创建和管理
- 2022-12-28 151浏览
-
- Go语言是如何使得Web工作的
- 2022-12-24 169浏览

