Gin 的 r.Static 默认不防路径穿越,因其依赖 http.Dir 对绝对路径的防护,而未显式调用 filepath.Abs 转换相对路径会导致工作目录变更时绕过校验;必须手动标准化路径、校验白名单前缀、防范符号链接与 TOCTOU 竞态。

为什么 Gin 的 r.Static 默认不防路径穿越?
Gin 的 r.Static 底层仍用 http.FileServer,但它直接传入相对路径(如 "./public")并依赖 http.Dir 的默认防护——而该防护只在 http.Dir 接收**绝对路径**时才可靠生效。若你没显式转绝对路径,部署时工作目录一变,../etc/passwd 就可能被拼成合法路径绕过。
必须用 filepath.Abs 构造静态根目录
别直接写 r.Static("/static", "./public")。Gin 不会帮你做路径标准化,它只是把字符串喂给 http.Dir。
- 先调
filepath.Abs("./public")得到绝对路径,比如/home/user/project/public - 再传给
r.Static("/static", absPath) - Windows 下建议补一句
filepath.ToSlash(absPath)统一分隔符,避免\导致后续校验失效
别信 http.FileServer 的“自动拦截”,要自己加白名单前缀校验
http.FileServer 确实会拒绝含 .. 的路径,但前提是它能识别出越界——而这个识别依赖于你传给 http.Dir 的路径是否为绝对路径且足够“干净”。光靠它不够,尤其当你的服务还接受 query 参数(如 /download?file=xxx)时。
- 对所有用户可控路径输入(不只是
r.URL.Path),先调url.PathUnescape解码(r.URL.Query().Get("file")不自动解码) - 再用
filepath.Clean归一化 - 然后用
strings.HasPrefix(cleaned, allowRoot)校验,其中allowRoot必须是绝对路径 + 结尾斜杠,例如"/home/user/project/public/" - 禁用
strings.Contains:否则/public/secret会被误判匹配/public
打开文件前必须防符号链接和 TOCTOU 竞态
即使路径校验通过,攻击者仍可在你 os.Stat 和 os.Open 之间把目标替换成指向 /etc/shadow 的软链。
立即学习“go语言免费学习笔记(深入)”;
- 优先用
os.Lstat检查:if info.Mode()&os.ModeSymlink != 0就拒绝 - Linux/macOS 可用
os.OpenFile(path, os.O_RDONLY, 0)配os.O_NOFOLLOW直接阻止跟随 - 若业务真要支持符号链接,必须调
filepath.EvalSymlinks后,重新走一遍白名单校验——因为真实路径可能已跳出授权范围
r.Static 这一行代码,而在你是否把路径校验逻辑下沉到了所有用户输入入口点。哪怕只漏掉一个 GET 参数或表单字段,整个防线就垮了。


















