namespace的进一步的学习认知及docker逃逸

35C3 CTF

这里参照了https://www.secrss.com/articles/15269
题目大体意思是,利用nsjail创建沙箱nsa,映射了user到root的ns空间权限。/tmp/flag被以只读挂载到/flag, procfs挂载到读写。原因就是,子ns空间对文件的操作需要重新挂载文件,但权限只能是父进程ns的资源。也就是说,新的沙箱其实可以访问 /flag文件。 但之后有个namespace的文件,执行后会将新进程加入到nsa,并降低了权限到1。

[lier@localhost ~]$ ls /proc/pid/ns/
ipc   mnt   net   pid   user  uts  

但是缺少了net ns。也就是说,此时加入的新进程由于在沙箱内不是root权限,而无法访问/flag。
start_sandbox 会接受一个init程序,chroot 到/tmp/chroots/%ld,然后以降权的权限执行。

//start_sandbox
v0 = socketpair(1, 1, 0, fds);
  check(v0, (__int64)"socketpair");
  v8 = new_proc(v0, "socketpair");   //创建新进程
  if ( !v8 )
  {
    close(fds[0]);
    write(fds[1], "1", 2uLL);
    wait_for((unsigned int)fds[1], "2");
    puts("Please send me an init ELF.");
    v1 = load_elf();
    *(_QWORD *)s = 0LL;
    v14 = 0LL;
    memset(&v15, 0, 0xFF0uLL);
    snprintf(s, 0x1000uLL, "/tmp/chroots/%ld", v9 - sandboxes);
    printf("[*] Creating chroot dir \"%s\"\n", s);
    mk_chroot_dir(s);
    printf("[*] Chrooting to \"%s\"\n", s);
    v2 = chroot(s);
    check(v2, (__int64)"chroot");
    v3 = chdir("/");
    check(v3, (__int64)"chdir");
    puts("[*] changing group ids");
    v4 = setresgid(1u, 1u, 1u);
    check(v4, (__int64)"setresgid");
    puts("[*] changing user ids");
    v5 = setresuid(1u, 1u, 1u);
    check(v5, (__int64)"setresuid");
    write(fds[1], "3", 3uLL);
    close(fds[1]);
    puts("[*] starting init");
    v11 = "init";
    v12 = 0LL;
    execveat(v1, &unk_210F, &v11, 0LL, 4096LL);
    _exit(1);
  }
  close(fds[1]);
  wait_for((unsigned int)fds[0], "1");
  puts("[*] setgroups deny");
  write_proc(v8, "setgroups", "deny");
  puts("[*] writing uid_map");
  write_proc(v8, "uid_map", "1 1 1");
  puts("[*] writing gid_map");
  write_proc(v8, "gid_map", "1 1 1");
  write(fds[0], "2", 2uLL);
  wait_for((unsigned int)fds[0], "3");
  close(fds[0]);
  *v9 = v8;
  return __readfsqword(0x28u) ^ v16;
unsigned __int64 run_elf()
{
  __pid_t v0; // eax
  unsigned int v2; // [rsp+8h] [rbp-38h]
  unsigned int fd; // [rsp+10h] [rbp-30h]
  char *v4; // [rsp+18h] [rbp-28h]
  const char *v5; // [rsp+20h] [rbp-20h]
  __int64 v6; // [rsp+28h] [rbp-18h]
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  v4 = get_sandbox();
  v2 = *(_DWORD *)v4;
  fd = load_elf();
  v0 = fork();
  if ( !(unsigned int)check(v0, (__int64)"fork") )
  {
    change_ns(v2, (v4 - (char *)sandboxes) >> 2);  //这里会降权,并以子进程的形式出来运行。
    v5 = "bin";
    v6 = 0LL;
    execveat(fd, &unk_210F, &v5, 0LL, 4096LL);
    _exit(1);
  }
  close(fd);
  return __readfsqword(0x28u) ^ v7;
}

漏洞:
由于加入的新进程没有net ns的限制,所以,进程之间可以通过net访问。所以造成了直接和user用户进程交互,形成逃逸。
问题是找一个能读flag的进程通信,才是重点。但沙箱内进程uid==1,显然不具条件。
这里又构造了init创造了子进程,说是挂载/tmp/chroots/到/proc/init_/ns,创建了/proc/init_/ns的链接到/proc/init_fork/ns,所以,加入到init的ns,其实是加入到了init_fork进程的ns。(这里没试,不知这个子进程能不能在这里挂载和创建链接)。

最终的效果这个样子:
在这里插入图片描述

在这里插入图片描述
现在只要沙盒三中的elf被阻止降权,然后读取/tmp/flag就可以了。
(修改pid命名空间只在子进程生效。)

我感觉最大的问题在于三个沙盒运行在同一个命名空间中,导致单独的/tmp/chroot/%ld目录对于进程的隔离能力被突破。

直接看下poc吧,https://github.com/LevitatingLion/ctf-writeups/tree/master/35c3ctf/pwn_namespaces

CVE-2019-5736/

据说这个洞是由上面的题解题思路触发的,但其实还是不太一样的。
这里是想办法找到父命名空间父进程的runc的pid,之后利用/proc/${runc_pid}/exe链接覆盖该文件。当docker exec再次执行的时候,将会触发沙盒逃逸。

找到runc的pid

#!/bin/bash_original
echo "[+] Waiting for runC to be executed in the container..."

runc_pid=$(ps axf | grep /proc/self/exe | grep -v grep | awk '{print $1}')

# Wait for /proc/self/exe to be executed
while [ -z "$runc_pid" ] 
do
    runc_pid=$(ps axf | grep /proc/self/exe | grep -v grep | awk '{print $1}')
done

# Call overwrite_runc with the symlink to the runC binary
./overwrite_runc /proc/${runc_pid}/exe

首先我们不知道父进程的pid,但是docker exec会执行容器内部通过 runc exec执行容器内的/bin/bash,
根据poc,这里先 利用 echo "#!/proc/self/exec " > /bin/bash 欺骗runc,runc将会在容器内再次执行runc.

而期间容器内如果早已启动上述脚本,就可以获得runc 的pid.
找到pid后就可以复写/proc/${run_pid}/exe

根据magic_link的原理,直接打开/proc/run_pid/exe将可以覆写容器外的runc文件(因为不受mns限制),覆盖成功后,当exec再次执行,恶意runc将会被执行,实现容器逃逸。

补丁:
根本原因是magic-link不受mns限制。一旦获取将可以覆写容器外文件,修复方式是通过增加匿名文件,使runc在容器内执行之前先复制出匿名文件,利用匿名文件执行命令,那么就可以阻断上述利用方式。

参考:
https://github.com/twistlock/RunC-CVE-2019-5736
https://github.com/LevitatingLion/ctf-writeups/tree/master/35c3ctf/pwn_namespaces

二、Docker cp容器逃逸漏洞 -CVE-2019-14271

lier@lier-virtual-machine:~/attack$ sudo apt-get install docker-ce=5:19.03.03-0ubuntu-xenial docker-ce-cli=5:19.03.03-0ubuntu-xenial containerd.io

sudo apt-get install docker-ce=5:19.03.03-0ubuntu-bionic docker-ce-cli=5:19.03.03-0ubuntu-bionic containerd.io

漏洞成因是由于,docker cp 进行拷贝的时候,将 docker-tar 此进程先 chroot 到容器内,然而此时使用的 so 文件也是容器内的,而 docker-tar 进程本身没有容器化,意味着仍然拥有高权限,所以此时如果容器内的 so 被恶意篡改,那么可能造成 docker 容器逃逸

https://bestwing.me/CVE-2019-14271-docker-escape.html

lier@lier-virtual-machine:~/attack$ docker cp cve-2019-14271:/var/log ./logs
lier@lier-virtual-machine:~/attack$ ls /tmp/
hack VMwareDnD
systemd-private-e51ab0ce9040447795820a51c017ecb0-colord.service-VjVkte vmware-root
systemd-private-e51ab0ce9040447795820a51c017ecb0-rtkit-daemon.service-BQDHGf vmware-root_1715-1815481160
systemd-private-e51ab0ce9040447795820a51c017ecb0-systemd-timesyncd.service-yrCZcW
lier@lier-virtual-machine:~/attack$ cat /tmp/hack
hack by chaitin
lier@lier-virtual-machine:~/attack$

root@1ed0b3f1d5f6:/# ls
bin breakout etc host_fs lib64 mnt proc run srv tmp var
boot dev home lib media opt root sbin sys usr ‘’$’\342\200\250’
root@1ed0b3f1d5f6:/# ls host_fs/
bin cdrom etc home lib lib64 lost+found mnt proc run snap sys usr vmlinuz
boot dev hardtmp initrd.img lib32 libx32 media opt root sbin srv tmp var vmware-root
root@1ed0b3f1d5f6:/#

攻击成功

防护思路

阻止docker-tar 加载名字空间的库。

补丁的话是增加了libnss 主机库libnss的预加载。

pkg/chrootarchive/archive.go
func init() {
// initialize nss libraries in Glibc so that the dynamic libraries are loaded in the host
// environment not in the chroot from untrusted files.
_, _ = user.Lookup(“docker”)
_, _ = net.LookupHost(“localhost”)
}

// NewArchiver returns a new Archiver which uses chrootarchive.Untar
func NewArchiver(idMapping *idtools.IdentityMapping) *archive.Archiver {
if idMapping == nil {

三、CVE-2020-15257【host下利用containerd-shim API逃逸docker】

contianerd-shim 使用抽象域名套接字,但当docker使用–network=host参数共享主机网络空间时,内部进程就可以越权访问containerd服务,造成逃逸

抽象域名套接字不受mnt 命名空间限制,只受网络命名空间约束

补丁:
更改抽象域名套接字为普通文件套接字通信

内核防护方案:
限制中间组件被容器内部进程的访问

docker服务拆分成了4个独立的模块:Docker Daemon、containerd、containerd-shim、runC
https://bbs.pediy.com/thread-263955.htm#msg_header_h1_2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值