cyphar/filepath-securejoin

GitHub: cyphar/filepath-securejoin

Go 语言安全路径操作库,提供防符号链接穿越和 TOCTOU 攻击的文件路径解析能力,主要服务于容器运行时等需要安全文件访问的场景。

Stars: 112 | Forks: 23

## `filepath-securejoin` [![Go 文档](https://pkg.go.dev/badge/github.com/cyphar/filepath-securejoin.svg)](https://pkg.go.dev/github.com/cyphar/filepath-securejoin) [![构建状态](https://static.pigsec.cn/wp-content/uploads/repos/cas/e0/e0aa1b784d423cd6f09b5191b4c125883d647bdec1b61fc4ce6791fcfcc38d44.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml) ### 旧版 API 该库最初只是 `SecureJoin` 的一个实现,其目的是[计划包含在 Go 标准库中][go#20126],作为一种更安全的 `filepath.Join`,它可以将路径查找限制在根目录内。 该实现基于几个容器 runtime 中已有的代码。不幸的是,该 API **从根本上是不安全的**,无法防范那些能够在 `SecureJoin` 返回之后、调用者使用该路径之前修改路径组件的攻击者,这允许发起一些相当简单的 TOCTOU 攻击。 该库仍然提供 `SecureJoin`(和 `SecureJoinVFS`)以支持旧版用户,但强烈建议新用户避免使用 `SecureJoin`,而是使用[新版 API](#new-api) 或切换到 [libpathrs][libpathrs]。 考虑到上述限制,该库保证以下几点: * 如果没有返回错误,生成的字符串**必须**是 `root` 的子路径,并且不包含任何符号链接路径组件(它们都会被展开)。 * 在展开符号链接时,所有符号链接路径组件**必须**相对于提供的根目录进行解析。特别地,这可以被视为 `chroot(2)` 如何操作文件路径的用户空间实现。请注意,这些符号链接**不会**从词法上展开(在处理之前不会对输入调用 `filepath.Clean`)。 * 不存在的路径组件不受 `SecureJoin` 的影响(类似于 `filepath.EvalSymlinks` 的语义)。 * 返回的路径将始终经过 `filepath.Clean` 处理,因此不包含任何 `..` 组件。 在 GNU/Linux 系统上,该函数的一个(简单的)实现可以通过以下方式完成(请注意,这需要 root 权限,并且比此库中的实现更加不透明,而且还要求 `readlink` 位于 `root` 路径内且是可信任的): ``` package securejoin import ( "os/exec" "path/filepath" ) func SecureJoin(root, unsafePath string) (string, error) { unsafePath = string(filepath.Separator) + unsafePath cmd := exec.Command("chroot", root, "readlink", "--canonicalize-missing", "--no-newline", unsafePath) output, err := cmd.CombinedOutput() if err != nil { return "", err } expanded := string(output) return filepath.Join(root, expanded), nil } ``` ### 新版 API 虽然我们建议用户在 [libpathrs][libpathrs] 一发布稳定版本后尽快切换,但 libpathrs 实现的一些方法已经被移植到该库中,以简化过渡过程。这些 API 仅在 Linux 上受支持。 这些 API 的实现方式使得 `filepath-securejoin` 能够视情况使用某些更新的内核 API,从而使这些操作变得安全得多。特别是: * 所有的查找操作都会在足够新的内核(Linux 5.6 或更高版本)上使用 [`openat2`][openat2.2],以限制通过 magic-links 和 bind-mounts(对于某些操作)进行查找,并利用 `RESOLVE_IN_ROOT` 高效地解析 rootfs 内的符号链接。 * 这些 API 提供了针对恶意 `/proc` 挂载的强化保护,以检测或避免被不合法的 `/proc` 欺骗。这是通过为所有用户使用 [`openat2`][openat2.2] 来完成的,而特权用户还将通过使用 [`fsopen`][fsopen.2] 和 [`open_tree`][open_tree.2](Linux 5.2 或更高版本)获得进一步的保护。 #### `OpenInRoot` ``` func OpenInRoot(root, unsafePath string) (*os.File, error) func OpenatInRoot(root *os.File, unsafePath string) (*os.File, error) func Reopen(handle *os.File, flags int) (*os.File, error) ``` `OpenInRoot` 是 ``` path, err := securejoin.SecureJoin(root, unsafePath) file, err := os.OpenFile(path, unix.O_PATH|unix.O_CLOEXEC) ``` 的一个更安全的版本,它可以防范可能导致严重安全问题(具体取决于应用程序)的各种竞态攻击。请注意,返回的 `*os.File` 是一个 `O_PATH` 文件描述符,其限制非常多。调用者可能需要使用 `Reopen` 来获取更可用的句柄(这种拆分是为了提供 PTY 生成等实用功能,并避免用户意外打开可能导致 DoS 的不良 inode)。 调用者在使用返回的 `*os.File` 时需要小心。通常,直接操作该句柄才是安全的,而且很容易引发安全问题。[libpathrs][libpathrs] 提供了更多的辅助函数来让这些句柄的使用变得更安全——目前没有计划将它们移植到 `filepath-securejoin`。 `OpenatInRoot` 类似于 `OpenInRoot`,区别在于根目录是通过 `*os.File` 提供的。这允许你确保多个 `OpenatInRoot`(或 `MkdirAllHandle`)调用是在同一个 rootfs 上操作的。 #### `MkdirAll` ``` func MkdirAll(root, unsafePath string, mode int) error func MkdirAllHandle(root *os.File, unsafePath string, mode int) (*os.File, error) ``` `MkdirAll` 是 ``` path, err := securejoin.SecureJoin(root, unsafePath) err = os.MkdirAll(path, mode) ``` 的一个更安全的版本,它可以防范 `OpenInRoot` 所防范的那些竞态问题。 `MkdirAllHandle` 类似于 `MkdirAll`,区别在于根目录是通过 `*os.File` 提供的(其原因与 `OpenatInRoot` 相同),并且会返回最终创建目录的 `*os.File`(保证该目录与 `MkdirAllHandle` 创建的目录实际上是相同的,而这仅通过在 `MkdirAll` 之后使用 `OpenatInRoot` 是无法确保的)。 ### 许可证 `SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` 本项目中的部分代码派生自 Go,并在 BSD 3-clause 许可证下授权(详见 `LICENSE.BSD`)。其他文件(其中许多派生自 [libpathrs][libpathrs])在 Mozilla Public License 2.0 版本下授权(详见 `LICENSE.MPL-2.0`)。如果你使用的是[上述“新版 API”][#new-api],你很可能正在使用根据该许可证发布的文件中的代码。 本项目中的每个源文件都有一个描述其许可证的版权头部。请检查每个文件的版权头部,以查看适用于它的许可证。 有关更多详细信息,请参阅 [COPYING.md](./COPYING.md)。
标签:EVTX分析, 日志审计