ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

KubeEdge 依赖剖析:filepath-securejoin 安全路径拼接库的 API 演进与容器场景实践

KubeEdge 依赖剖析:filepath-securejoin 安全路径拼接库的 API 演进与容器场景实践 KubeEdge 依赖剖析filepath-securejoin 安全路径拼接库的 API 演进与容器场景实践【免费下载链接】kubeedgeKubernetes Native Edge Computing Framework (project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/ku/kubeedgefilepath-securejoin是一个致力于替代filepath.Join的 Go 安全路径工具库其核心目标是把路径解析结果严格限制在指定的 root 目录之内并安全地展开符号链接。本篇文章以 KubeEdge 仓库中 vendor 的 filepath-securejoin README 为主线完整梳理其旧版SecureJoinAPI 的语义与缺陷、新版OpenInRoot/MkdirAll系列的底层内核机制并结合 KubeEdge 中 keadm 工具的实际调用场景帮助读者理解如何在解压归档、挂载卷、写入容器 rootfs 等场景下防御路径穿越与 TOCTOU 攻击。一、背景为什么需要安全的 filepath.JoinGo 标准库的filepath.Join只做词法层面的路径拼接与清理它不会感知文件系统中的符号链接。在容器运行时、沙箱、包管理器这类场景中一个由外部输入例如 tar 包内的文件名、用户上传的路径驱动的拼接结果可能通过..或符号链接逃逸出预期的目录进而产生任意文件读写风险。filepath-securejoin最初就是SecureJoin的一个实现其设计目标曾被提议纳入 Go 标准库对应 golang/go 的 issue #20126作为一种比filepath.Join更安全、且能将路径查找限制在某个 root 目录之内的方案。该实现的雏形源自多个容器运行时中的既有代码在 KubeEdge 仓库中它被 vendor 在 vendor/github.com/cyphar/filepath-securejoin 目录下当前 vendor 版本为 0.3.6见 VERSION并被 keadm 工具用于防御解压归档时的路径穿越攻击。二、旧 APISecureJoin 的语义与行为保证SecureJoin以及与之配套、可注入自定义 VFS 的SecureJoinVFS见 vfs.go是库中最早的 API。文档明确给出了它在无错误返回时所提供的四条语义保证结果必须是root的子路径且结果中不会残留任何符号链接路径组件所有符号链接都会被展开展开符号链接时所有符号链接组件必须相对于给定的 root 解析这可以视为chroot(2)对文件路径语义的用户态实现。需要特别注意这些符号链接不会被词法层面展开也就是说输入不会被预先执行filepath.Clean不存在的路径组件不受SecureJoin影响与filepath.EvalSymlinks的语义一致返回的路径总是经过filepath.Clean清理因此不会包含任何..组件。其中符号链接在 root 内解析这一条是区别于普通Join的关键即便符号链接内容形如/etc/passwd解析结果也会被锚定在 root 之下等效于 chroot 后的路径视角。2.1 GNU/Linux 上的最小实现示例为了直观说明SecureJoin的语义README 给出了一个 GNU/Linux 上的平凡实现它依赖chroot加readlink --canonicalize-missing完成安全拼接 展开缺失组件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 }文档同时给出了这段代码的适用前提与局限它需要 root 特权比库内实现更加不透明并且要求readlink可执行文件位于root路径内部且可信。因此它更多是用于阐释语义的示意代码而非生产推荐写法。三、旧 API 的局限TOCTOU 竞态风险README 用相当直白的措辞指出了旧 API 的根本缺陷该 API 对能够在SecureJoin返回之后、调用方实际使用该路径之前修改路径组件的攻击者来说本质上是不安全的由此会引发相当简单的 TOCTOUtime-of-check to time-of-use攻击。典型攻击形态是SecureJoin解析并返回一个干净的路径字符串之后攻击者在调用方执行os.OpenFile等操作前把路径中的某个中间目录替换为指向敏感位置的符号链接导致最终操作落在 root 之外。也就是说字符串级别的安全检查无法对抗检查与使用之间存在时间窗口的文件系统竞态。基于这一原因README 明确建议SecureJoin与SecureJoinVFS仍然保留仅用于支持存量用户新用户应强烈避免使用SecureJoin转而使用下面的新 API。四、新 API基于 openat2 与文件描述符的纵深防御新 API 将 libpathrs 中的部分方法移植到了本库这些 API仅支持 Linux其实现会在可用时机会式地利用更新的内核能力从返回安全字符串升级为基于文件描述符的安全操作从根上消除 TOCTOU 窗口。4.1 底层内核机制openat2 系统调用Linux 5.6 及以上所有查找操作都会优先使用openat2借助其扩展属性限制对 magic-links 和 bind-mount 的穿越部分操作并使用RESOLVE_IN_ROOT在 rootfs 内高效解析符号链接fsopen 与 open_treeLinux 5.2 及以上针对恶意/proc挂载新 API 对所有用户都通过openat2来检测或规避被伪装的/proc特权用户还会额外使用fsopen与open_tree获得进一步保护防止/proc被劫持后返回伪造的文件描述符。这些机制的代码分别位于 openat2_linux.go、open_linux.go 与 procfs_linux.go实现了同一路径解析全程不依赖易受攻击的路径字符串的设计。4.2 OpenInRoot / OpenatInRoot / Reopenfunc 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调用方必须谨慎使用返回的句柄通常只适合直接对该句柄进行操作稍有不慎就容易制造安全问题。README 提到 libpathrs 提供了更多让句柄使用更安全的辅助函数但本库目前没有移植计划OpenatInRoot与OpenInRoot的区别在于 root 以*os.File形式传入从而可以确保多次OpenatInRoot或MkdirAllHandle调用操作在同一个 rootfs上避免根目录被切换的竞态。4.3 MkdirAll / MkdirAllHandlefunc MkdirAll(root, unsafePath string, mode int) error func MkdirAllHandle(root *os.File, unsafePath string, mode int) (*os.File, error)MkdirAll是下面这段旧式写法的安全替代保护范围与OpenInRoot相同path, err : securejoin.SecureJoin(root, unsafePath) err os.MkdirAll(path, mode)MkdirAllHandle则与OpenatInRoot同理root 以*os.File提供并返回最终创建目录的*os.File。该句柄保证与MkdirAllHandle实际创建的目录在事实上完全一致——这是先MkdirAll再OpenatInRoot无法确保的。4.4 与旧 API 的关键行为差异README 用两个醒目的 NOTE 强调新旧 API 在边界行为上的差异NOTE与SecureJoin不同OpenInRoot一旦遇到悬空符号链接dangling symlink或不存在路径就会立即报错。而SecureJoin会把不存在的组件当作真实目录处理并允许对悬空符号链接进行部分解析。旧行为与 Linux 对不存在路径和悬空符号链接的处理方式相悖因此新 API 不再允许这种行为。NOTEMkdirAll同样会在遇到悬空符号链接或不存在路径时立即报错这意味着MkdirAll不会通过悬空符号链接去创建其指向的不存在的目录。这一差异值得所有迁移者注意如果业务逻辑依赖自动补全缺失路径或容忍坏链接升级到新 API 后需要显式处理错误路径。五、KubeEdge 中的实际应用keadm 解压归档的路径穿越防护filepath-securejoin并非仅仅作为一个被动 vendor 的依赖存在KubeEdge 的 keadm 工具在解压 tar.gz 归档时直接调用了它的旧 API。在 keadm/cmd/keadm/app/cmd/util/common.go 的DecompressTarGz(gzFilePath, dest string)函数中逐条读取 tar 条目后代码先做了一系列前置校验entryName : strings.ReplaceAll(header.Name, \\, /) entryName path.Clean(entryName) if len(entryName) 2 entryName[1] : ((entryName[0] a entryName[0] z) || (entryName[0] A entryName[0] Z)) { return fmt.Errorf(tar entry %q attempts path traversal outside %s, header.Name, absDest) } if path.IsAbs(entryName) || entryName .. || strings.HasPrefix(entryName, ../) { return fmt.Errorf(tar entry %q attempts path traversal outside %s, header.Name, absDest) } target, err : securejoin.SecureJoin(absDest, entryName) if err ! nil { return err }可以看到即使前面已经过滤了 Windows 盘符路径、绝对路径和..前缀代码仍用securejoin.SecureJoin(absDest, entryName)将目标目录 归档条目名安全拼接为落盘路径第 276 行随后根据 tar 条目类型tar.TypeDir/tar.TypeReg执行os.MkdirAll或os.Create写入。这一层防护正是针对 zip-slip 一类攻击的典型加固即便恶意归档通过符号链接或精心构造的相对路径试图把文件写到目标目录之外SecureJoin也会把解析结果锚定在absDest之内。该示例同时印证了 README 中旧 API 仍为存量用户保留的定位keadm 在这里面对的是静态的 tar 元数据条目名在解压期间不会突变且配合了多道词法校验属于SecureJoin适用的受控场景。六、版本与许可本仓库 vendor 的版本为0.3.6见 VERSION包含旧 APIjoin.go、vfs.go与新 APIopen_linux.go、mkdir_linux.go的完整实现库的许可协议与 Go 相同为BSD 3-clause许可完整文本见 LICENSE。七、迁移建议小结综合 README 与 KubeEdge 中的实际用法可以给出如下选型建议存量代码继续使用SecureJoin/SecureJoinVFS是可接受的但要清醒认识到其 TOCTOU 边界并尽量配合词法校验、只读输入等外部约束如 keadm 的做法新代码、且运行于 Linux优先采用OpenInRoot/MkdirAllHandle这类基于文件描述符的 API并依赖内核 5.6 的openat2能力获得RESOLVE_IN_ROOT与 magic-link/bind-mount 防护特权环境下还可受益于fsopen/open_tree的/proc加固行为差异适配新 API 对悬空符号链接与不存在路径立即报错迁移时需为这类边界路径补充显式错误处理避免依赖旧 API宽松补全的语义。【免费下载链接】kubeedgeKubernetes Native Edge Computing Framework (project under CNCF)项目地址: https://gitcode.com/GitHub_Trending/ku/kubeedge创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表