Go archive/tar 怎样防止路径穿越
来源:17golang原创
时间:2026-10-14 02:42:53 230浏览 收藏
处理用户上传的 tar 包时,真正危险的不是 tar.Reader 会不会读取,而是 Header.Name 会把文件写到哪里。安全做法是:读取每个条目后先用 filepath.IsLocal 拒绝空路径、绝对路径和包含 .. 的路径,再把结果拼到固定目标目录;目录和普通文件分别处理,符号链接、硬链接直接拒绝。这样才不会把解包目标变成攻击者控制的任意写入点。
关键原则:归档成员名只允许“目标目录内部的本地相对路径”,并且路径检查、条目类型判断和文件创建要在同一段解包逻辑里完成。
官方地址:https://pkg.go.dev/archive/tar
这篇文章用一个可落地的解包骨架说明边界。代码采用 Go 标准库接口,图片是静态结构说明图,不是运行截图或执行证据。
先把归档路径限制在目标目录内
常见错误是直接写出 filepath.Join(dst, hdr.Name),然后把“拼接成功”误认为“路径安全”。Join 会清理路径,但它不会替你决定归档成员是否应该被接受;输入仍应先经过本地相对路径判断。
package main
import (
"fmt"
"path/filepath"
)
// safeTarget 只接受目标目录内部的本地相对路径。
// filepath.IsLocal 采用词法判断,可拦截空路径、绝对路径和 .. 段。
func safeTarget(dst, name string) (string, error) {
if !filepath.IsLocal(name) {
return "", fmt.Errorf("拒绝不安全归档路径: %q", name)
}
return filepath.Join(dst, name), nil
}
例如 docs/readme.txt 可以继续处理,而 ../outside.txt、/tmp/outside.txt 和空名称都会被拒绝。这里的“本地”是词法意义上的本地,后面还要处理链接条目,因为路径字符串本身安全,并不等于文件系统访问绝对安全。

读取 Header 时不要忽略 ErrInsecurePath
tar.Reader.Next 每次返回一个条目头,并把当前条目的数据暴露为 io.Reader。在支持该行为的 Go 环境中,如果设置了 GODEBUG=tarinsecurepath=0,非本地文件名会伴随 tar.ErrInsecurePath 返回。业务解包器不应该把这个错误当成普通提示继续写入。
for {
hdr, err := tr.Next()
if err == io.EOF {
break // 归档正常结束。
}
if err != nil {
// ErrInsecurePath 表示成员名越过了本地路径边界,必须拒绝。
name := ""
if hdr != nil {
name = hdr.Name // 保留服务端日志中的归档成员名。
}
return fmt.Errorf("读取归档条目 %q: %w", name, err)
}
// hdr.Name 后面还要做 IsLocal 检查,不能只依赖 GODEBUG。
// 这里进入条目类型分派逻辑。
}
更稳妥的做法是无论运行时开关怎样,都显式调用 filepath.IsLocal。这样代码的安全边界写在应用逻辑里,不依赖部署环境是否恰好带上某个 GODEBUG 值。
目录、普通文件和链接必须分开处理
条目类型决定了写入动作。目录只需要创建;普通文件才可以创建文件并复制数据;符号链接和硬链接不能沿用普通文件分支。官方文档特别提醒,Next 的不安全路径判断只针对文件名,不会校验链接目标,因此不能只检查 hdr.Name。
func extractEntry(tr *tar.Reader, hdr *tar.Header, dst string) error {
target, err := safeTarget(dst, hdr.Name)
if err != nil {
return err
}
switch hdr.Typeflag {
case tar.TypeDir:
// 目录沿着归档路径创建,并限制为 owner 可读写可进入。
return os.MkdirAll(target, 0700)
case tar.TypeReg, tar.TypeRegA, 0:
// 普通文件先创建父目录,再复制 Header.Size 对应的数据。
if err := os.MkdirAll(filepath.Dir(target), 0700); err != nil {
return err
}
f, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
_, copyErr := io.CopyN(f, tr, hdr.Size)
closeErr := f.Close() // 关闭错误也要保留,避免静默丢数据。
if copyErr != nil {
return copyErr
}
return closeErr
case tar.TypeSymlink, tar.TypeLink:
// 链接目标可能跳出目标目录;当前策略直接拒绝。
return fmt.Errorf("拒绝链接条目: %q", hdr.Name)
default:
// 设备、FIFO 等特殊条目不在普通解包范围内。
return fmt.Errorf("拒绝特殊条目 %q,类型 %d", hdr.Name, hdr.Typeflag)
}
}
上面的分派逻辑故意不调用 os.Symlink 或 os.Link。如果业务确实需要保留链接,必须额外约束 hdr.Linkname、解析链接后的真实路径并处理竞态;在一般上传解包场景里,拒绝链接的边界更清楚。

文件创建和数据复制还要守住最后一层
安全路径判断解决的是“写到哪里”,并不自动解决“写多少”和“写完没有”。应使用 io.CopyN 按 hdr.Size 复制当前条目数据,检查复制错误和关闭错误,并在生产代码中增加单文件大小、总解包大小和条目数量上限,避免合法路径上的资源消耗型归档。
还要记住 filepath.IsLocal 是纯词法操作,不会考虑目标目录中已经存在的符号链接。如果解包目录可能被其他进程或不可信用户修改,单纯的路径拼接仍不足够,应把目标目录隔离到专用工作区,并限制该目录的写权限;更高要求的场景需要使用平台提供的目录句柄相对打开能力或沙箱。
最后可以按下面的清单复查实现:成员名先过 IsLocal;ErrInsecurePath 不被忽略;链接条目默认拒绝;父目录权限明确;普通文件限制复制长度;CopyN 和 Close 错误都返回;单文件、总大小和条目数量有上限。做到这些,archive/tar 的读取能力才不会被误用成任意路径写入器。
相关问题
- 只调用 filepath.Clean 能防止路径穿越吗?不能。Clean 只做词法清理,仍应判断结果是否为本地相对路径。
- 为什么不建议直接恢复 tar 中的符号链接?因为 Header.Name 安全不代表 Header.Linkname 安全,链接可能把写入或后续访问引向目标目录之外。
- tar.Reader 会自动限制文件大小吗?它按 Header.Size 提供当前条目数据,业务仍应设置单文件和总解包大小上限。
-
155 收藏
-
Golang · Go教程 | 29分钟前 | 性能优化 · 并发编程 · 同步 · Go教程 · Go add sync/atomic CompareAndSwap 并发计数器 atomic.Int64 Load471 收藏
-
141 收藏
-
Golang · Go教程 | 31分钟前 | select · Context · 超时控制 · Go教程 · time.NewTimer context.Done Go超时 Go select 多路等待112 收藏
-
164 收藏
-
116 收藏
-
359 收藏
-
467 收藏
-
423 收藏
-
146 收藏
-
Golang · Go教程 | 1小时前 | html · 模板 · Go教程 · html/template · Go html/template HTML转义 模板安全 template.HTML 上下文自动转义281 收藏
-
105 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习