filepath.clean不能防路径穿越,必须先clean再用strings.hasprefix检查是否在授权前缀内,且前缀须为绝对路径并以/结尾,同时需处理url编码、空字节、符号链接等绕过手法。

用 filepath.Clean 不能直接防路径穿越
很多人以为调用 filepath.Clean 就万事大吉,其实它只做标准化(比如把 ../、./、重复斜杠展开),但不会检查结果是否越界。比如 filepath.Clean("../etc/passwd") 返回 /etc/passwd,如果拼在根目录后,就直接逃逸了。
真正安全的做法是:先 filepath.Clean,再用 filepath.HasPrefix 或 strings.HasPrefix 检查清理后的路径是否仍在允许前缀内。
- 必须对用户输入的路径做
filepath.Clean,否则绕过检查太容易(比如foo/../../etc/passwd) - 检查时要用清理后的路径,而不是原始输入——原始路径可能含编码或空字节干扰判断
- 前缀必须是绝对路径(如
/var/www/uploads),且结尾带/,避免/var/www/uploaded被误认为匹配/var/www/upload - Windows 下注意路径分隔符,建议统一用
filepath.ToSlash转成正斜杠再比对,或用filepath.Join构造前缀
HTTP 文件服务中 http.ServeFile 的坑
http.ServeFile 本身不校验路径,传入用户可控路径等于裸奔。即使你做了 filepath.Clean,如果没限制根目录范围,照样读任意文件。
更安全的替代方案是用 http.FileServer 配合自定义 http.FileSystem,或者干脆不用内置服务,自己读取+校验+写响应。
立即学习“go语言免费学习笔记(深入)”;
- 绝不要把用户输入直接喂给
http.ServeFile,例如http.ServeFile(w, r, "/data/" + r.URL.Query().Get("file")) - 若必须用
http.FileServer,需包装http.Dir,重写Open方法,在打开前做路径白名单检查 - 注意 URL 解码时机:Go 的
r.URL.Path已解码,但查询参数(r.URL.Query().Get)未解码,需手动url.PathUnescape - 常见错误现象:
404报错但实际已读取敏感文件——因为http.ServeFile对不存在路径也返回 404,掩盖了越界成功
读取文件前必须验证路径是否在白名单目录内
核心逻辑就两步:清理 → 判断是否在授权目录下。别省略任何一步,也别用模糊匹配(如 strings.Contains)。
示例关键片段:
userPath := r.URL.Query().Get("file")
cleanPath := filepath.Clean(userPath)
fullPath := filepath.Join("/var/www/static", cleanPath)
// 必须用 Clean 后的 cleanPath 做前缀判断
if !strings.HasPrefix(filepath.ToSlash(cleanPath), "images/") &&
!strings.HasPrefix(filepath.ToSlash(cleanPath), "docs/") {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// 再检查 fullPath 是否越界
if !strings.HasPrefix(filepath.ToSlash(fullPath), "/var/www/static/") {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
- 白名单应基于路径段(如
images/),而不是完整绝对路径,方便维护 - 检查顺序很重要:先段级白名单(业务逻辑),再绝对路径越界检查(安全兜底)
- 注意
filepath.Join可能消除前缀——比如filepath.Join("/a", "../b")得到/b,所以必须在Join前就完成越界判断 - Linux 下符号链接(symlink)仍可能绕过,生产环境建议用
filepath.EvalSymlinks并再次检查
URL 编码、空字节、多编码混用是绕过高频手法
攻击者会用 %2e%2e%2f、..%c0%af、%00.jpg 等方式绕过简单字符串匹配。Go 的 net/http 默认会解码 r.URL.Path,但不会处理双解码或 UTF-8 overlong sequence。
最稳妥的做法是:对所有路径输入,强制规范编码(如只接受 ASCII 路径段)、拒绝含空字节或非打印字符的路径,并在清理后再次验证字节合法性。
- 检查
bytes.Contains([]byte(cleanPath), []byte{0}),拦截空字节 - 用
utf8.ValidString(cleanPath)排除非法 UTF-8 序列 - 拒绝包含控制字符(
unicode.IsControl)或非文件名字符(如?、*、|)的路径段 - 开发时用
curl -v "http://x/?file=%2e%2e%2fetc%2fpasswd"手动测一遍,比写单元测试更快发现漏判










