filepath.clean单独使用无法防止文件遍历漏洞,必须组合url解码、filepath.join拼接、clean标准化、strings.hasprefix白名单前缀校验(cleanroot结尾带分隔符)、os.lstat防符号链接及toctou、os.openfile配os.o_nofollow打开。

filepath.Clean 单独使用无法防止文件遍历漏洞——它只做路径标准化,不校验越界、不防符号链接、不处理 URL 编码,也不拦 Windows 设备名。必须组合多层检查,且顺序不能错。
先解码再拼接,别让 %2e%2e 绕过校验
HTTP 请求中 query 参数或表单字段(如 r.URL.Query().Get("file"))不会被自动解码,而 r.URL.Path 已由 net/http 解码过。攻击者传入 %2e%2e%2fetc%2fpasswd 时,若跳过解码直接 filepath.Join,Clean 后仍是 /etc/passwd,白名单校验就失效了。
- 对所有非
r.URL.Path的用户输入,必须显式调用一次url.PathUnescape - 解码后立即传给
filepath.Join(cleanRoot, userInput),禁止字符串拼接(如cleanRoot + "/" + userInput) - Windows 下统一用
filepath.ToSlash转换分隔符后再比对,避免\和/混用导致strings.HasPrefix失效
clean 后必须做前缀校验,且 cleanRoot 结尾带分隔符
filepath.Clean("../../etc/passwd") 返回 /etc/passwd,但它不告诉你这已跳出授权目录。校验必须基于 Clean 后的路径,且白名单前缀要“绝对”。
- 先算
cleanRoot := filepath.Clean("./uploads"),再补上分隔符:cleanRoot + string(filepath.Separator) - 比较用
strings.HasPrefix(cleanedPath, cleanRoot+string(filepath.Separator)) || cleanedPath == cleanRoot - 禁用
strings.Contains或模糊匹配——/var/www/upload会误匹配/var/www/uploaded - 更稳的做法是调用
filepath.Rel(cleanRoot, cleanedPath),若返回错误或结果含"..",说明越界
打开前用 os.Lstat 防符号链接和 TOCTOU 竞态
攻击者可在你校验完路径、调用 os.Open 前,把目标替换成指向 /etc/shadow 的软链——这就是 TOCTOU 漏洞。分离调用 os.Stat 和 os.Open 就是典型高危模式。
- 用
os.Lstat先查info.Mode() & os.ModeSymlink != 0,若为真,直接拒绝 - 若业务必须支持符号链接,用
filepath.EvalSymlinks获取真实路径,再重新走一遍白名单校验 - 打开时优先用
os.OpenFile(path, os.O_RDONLY, 0),Linux/macOS 可加os.O_NOFOLLOW阻止跟随 - 避免
os.Stat→os.Open分两步,竞态窗口无法消除
Go 1.24 的 os.Root 是新防线,但不是银弹
os.Root("/var/www/uploads") 提供类似 chroot 的受限视图,后续所有 Open、ReadDir 都以它为根。但它不自动解决符号链接跳转、Windows 设备名(如 CON、AUX)或空字节截断问题。
- 仍需在传入
os.Root前完成 URL 解码、Clean、前缀校验、符号链接检查 - 初始化失败时要显式处理
err,不能忽略 - 设备名和空字节(
\x00)应在 Clean 前直接拒绝,os.Root不拦截这类输入
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











