go防路径遍历必须四层拦截:先url解码(query参数需手动url.pathunescape)、再filepath.join拼接与clean标准化、接着strings.hasprefix校验绝对cleanroot前缀(结尾带分隔符)、最后os.lstat防符号链接及toctou竞态。

直接拼接用户输入路径 + filepath.Clean 不防路径穿越,必须做四层拦截:解码 → Clean → 白名单前缀校验 → 符号链接/TOCTOU防护。
先对 query 参数手动 URL 解码再 Clean
Go 的 r.URL.Path 已自动解码,但 r.URL.Query().Get("file")、表单字段、JSON body 中的路径字段都未解码。不调用 url.PathUnescape,攻击者用 %2e%2e%2fetc%2fpasswd 就能绕过所有后续检查。
- 只解码一次:
url.PathUnescape本身不幂等,重复调用可能出错 - 解码后立即传给
filepath.Join,不要拼字符串(如"/data/" + raw) - Windows 下建议统一转正斜杠:
filepath.ToSlash(unescaped)再处理
clean 后必须用绝对前缀 + strings.HasPrefix 校验
filepath.Clean 只归一化路径,不拦越界。它把 "../../etc/passwd" 变成 "/etc/passwd",但不会告诉你这已跳出授权目录。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 白名单前缀必须是绝对路径,且结尾带分隔符:
cleanRoot := filepath.Clean("/var/www/uploads") + string(filepath.Separator) - 比较必须用
strings.HasPrefix(cleanedPath, cleanRoot)或cleanedPath == cleanRoot - 禁用
strings.Contains:/var/www/uploaded会被误判匹配/var/www/upload - Go 1.20+ 的
filepath.IsLocal可作轻量前置过滤,但不能替代白名单校验
打开文件前必须防符号链接和 TOCTOU 竞态
即使路径校验通过,攻击者仍可在检查后、os.Open 前瞬间把目标替换成指向 /etc/shadow 的软链——这就是 TOCTOU 漏洞。
- 优先用
os.Lstat检查:if info.Mode()&os.ModeSymlink != 0,为真则拒绝 - Linux/macOS 可用
os.OpenFile(path, os.O_RDONLY, 0)配os.O_NOFOLLOW直接阻止跟随 - 避免先
os.Stat再os.Open:两者之间存在竞态窗口 - 若业务必须支持符号链接,用
filepath.EvalSymlinks获取真实路径,再重新走一遍白名单校验
别直接用 http.ServeFile 或裸 http.FileServer
http.ServeFile 完全不校验路径,把用户可控路径直接喂进去等于裸奔。即使你做了 Clean 和前缀检查,只要没限制根目录范围,照样读任意文件。
- 绝不要写:
http.ServeFile(w, r, "/data/" + r.URL.Query().Get("file")) - 更安全的做法是自己读取:
cleaned, err := sanitizePath(r.URL.Query().Get("file"))→ 校验 →os.OpenFile→io.Copy到响应体 - 若要用
http.FileServer,必须包装http.Dir,重写Open方法,在打开前做完整路径校验 - Go 1.24 的
os.Root是新选项,但需注意:它不自动处理符号链接跳转或 Windows 设备名(如CON),初始化前仍需过滤含\x00、\、设备名的路径
路径穿越防御不是单点动作,而是解码、Clean、前缀校验、符号链接检查、原子打开五步闭环。漏掉任何一环,攻击者都有可乘之机——尤其是 os.Lstat 和 os.O_NOFOLLOW 这类容易被忽略的竞态防护。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










