go1.24: 新的标准库 os.Root

Golang 1.24 引入了 `os.Root`,通过防止目录遍历攻击来增强文件操作的安全性。它确保操作仅限于指定目录。#Golang #安全性 #osRoot

 

man drawing on dry-erase board

Backlink | |Photo by Kaleidico on Unsplash

Golang 1.24 已经进入冻结期了,目前很多特性我们在Go 1.24 Release Notes 能够看到,接下来一段时间我会学习Golang1.24将会添加的新功能以及修改点。如果您也想学习Go最新的进展,请关注我,
这篇文章,我们将学习一下os.Root这个新增的Standard library。

Proposal

目录遍历漏洞是一类常见的漏洞,攻击者会诱骗程序打开其不希望的文件。这些攻击通常采用提供相对路径名的形式,例如"../../../etc/passwd" ,这会导致在预期位置之外进行访问。 CVE-2024-3400是最近的一个真实的目录遍历示例,导致远程代码执行漏洞被主动利用。

在其他语言以及操作系统中,已经都有类似的实现, 比如:

  • Python 的 chroot:通过 os.chroot() 将根目录限制为特定目录。
  • Linux 的文件系统命名空间:通过 mount 和 chroot 限制进程的视图。
    我们可以写一个demo 测试一下。
    首先构造一个 “机密”文件
1
echo 'password123' >> /tmp/password

然后写一个Golang函数, 原义是打开当前目录的文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {  
    fileName := os.Args[1]  
    //readFile  
    localFilePath := "."  
    filePath := fmt.Sprintf("%s/%s", localFilePath, fileName)  
    content, err := os.ReadFile(filePath)  
    if err != nil {  
       fmt.Printf("Error reading file %s: %s\n", fileName, err)  
       return  
    }  
    fmt.Printf("File %s opened successfully. file content %s\n", fileName, content)  
}

但是因为传入了不可靠的参数,代码能够访问到 权限范围之外的地方。

1
2
3
4
5
6

➜  os_root git:(main)pwd
/Users/hxzhouh/workspace/github/me/blog-example/go/go1.24/os_root
➜  os_root git:(main) ✗ ./main ../../../../../../../../../tmp/password
./../../../../../../../../../tmp/password
File ../../../../../../../../../tmp/password opened successfully. file content password123

在 Go1.24 中,新增了 os.Root 的类型,其提供了在特定目录中执行文件系统操作的能力。整个体系是围绕着这个新类型进行的。 对应的核心函数是 os.OpenRoot,函数打开一个目录并返回一个 os.Root
os.Root 上的方法仅允许在目录内操作,不允许指向目录外部位置的路径,包括遵循目录外符号链接的路径。(防御住了前面提案背景提到的攻击范围)
我们用Go1.24 的方式修改代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
func main() {  
    fileName := os.Args[1]  
    root, err := os.OpenRoot(".")  
    if err != nil {  
       panic(err)  
    }  
    file, err := root.Open(fileName)  
    if err != nil {  
       fmt.Println(fmt.Sprintf("Error opening file %s: %s\n", fileName, err.Error()))  
       return  
    }  
    content := make([]byte, 1024)  
    c, err := file.Read(content)  
    if err != nil {  
       panic(err)  
    }  
    content = content[:c]  
    fmt.Printf("File %s opened successfully. file content %s\n", fileName, content)  
}

再次运行

1
2
3
4
5
➜  os_root git:(main) ✗ gotip version 
go version devel go1.24-fafd447 Wed Dec 11 15:57:34 2024 -0800 darwin/arm64
➜  os_root git:(main) ✗ gotip build -o go1.24  main.go                      
➜  os_root git:(main) ✗ ./go1.24 ../../../../../../../../../tmp/password 
Error opening file ../../../../../../../../../tmp/password: openat ../../../../../../../../../tmp/password: path escapes from parent

如果文件夹脱离了parent 层级,就会报错。
更多的APi接口请参考官方文档 。1.24正式发布后,估计很多第三方库都会第一时间适配把,毕竟其他语言基本上都有这种功能了。

Licensed under CC BY-NC-SA 4.0
最后更新于 Dec 10, 2024 19:23 CST
使用 Hugo 构建
主题 StackJimmy 设计