UDN-企业互联网技术人气社区

板块导航

浏览  : 1498
回复  : 0

[资源分享] Docker 容器逃逸案例分析

[复制链接]
呵呵燕的头像 楼主
发表于 2016-9-17 21:05:38 | 显示全部楼层 |阅读模式
  0. 前言

  本文参考自《Docker 容器与容器云》

  这个容器逃逸的 case 存在于 Docker 1.0 之前的绝大多数版本。

  目前使用 Docker 1.0 之前版本的环境几乎不存在了,这篇分析的主要目的是为了加深系统安全方面的学习。

  本案例所分析的 PoC 源码地址:shocker.c

  1. 预备知识

  1.1 Linux Capability

  尝试用较为简单的话来说明 Linux 中 Capability 的概念。

  为了解决在某些场景下,普通用户需要部分 root 权限来完成工作的问题。Linux 支持将部分 root 的特权操作权限细分成具体的 Capability,如果将某个 Capability 分配给某一个可执行文件或者是进程,即使不是 root 用户,也可以执行该Capability 对应的特权操作。

  1.2 Unix 系统文件操作原理

  1.2.1 proc 与 user 结构体

  以 UNIX V6 为基础进行说明,目前主流的 Linux 版本文件系统的实现原理与 UNIX V6 差别不大。

  Unix 系统中与某一个进程密切相关的有两个结构体,它们是 proc 结构体和 user 结构体。

  proc 结构体中保存了进程状态、执行优先级等经常需要被内核访问的信息,因此由 proc 结构体构成的数据 proc[] 是常驻内存的。

  1.   /*

  2.   * Filename: proc.h

  3.   */

  4.   struct proc {

  5.   // 进程当前状态

  6.   char p_stat;

  7.   // 标识变量

  8.   char p_flag;

  9.   // 执行优先级

  10.   char p_pri;

  11.   // 接收到的信号

  12.   char p_sig;

  13.   // UID

  14.   char p_uid;

  15.   // 在内存或交换空间中存在的时间,单位秒

  16.   char p_time;

  17.   // 占用 CPU 的累积时间,单位时钟 tick 数

  18.   char p_cpu;

  19.   // 用于修正执行优先级的补正系数,默认 0

  20.   char p_nice;

  21.   // 正在操作进程的终端

  22.   int p_ttyp;

  23.   // PID

  24.   int p_pid;

  25.   // 父进程 PID

  26.   int p_ppid;

  27.   // 数据段的物理地址

  28.   int p_addr;

  29.   // 数据段长度

  30.   int p_size;

  31.   // 进程进入休眠的原因

  32.   int p_wchan;

  33.   // 使用的代码段

  34.   int *p_textp;

  35.   }
复制代码


  user 结构体中保存了进程打开的文件等信息,由于内核只需要使用当前执行进程的 user 结构体,所以当某一个进程被移至交换空间时, user 结构体也相应地会被移出内存。

  proc 结构体中的 p_addr 指向的数据段,其起始部分的内容即为 user 结构体。

  由于 user 结构体内容较多就不列出了,其中一个与文件描述符相关的属性是 u_ofile[],会在后面提到。

  1.2.2 文件描述符

  文件描述符是内核为了管理已被打开的文件所创建的索引,是一个非负的整数。

  文件描述符保存在进程对应 user 结构体的 u_ofile[] 字段中。

  通过文件描述符对文件进行操作涉及到三个关键的数据结构,原理如下图所示:

d.png


  说明1:

  当一个进程启动时,文件描述符 0 表示 stdin,1 表示 stdout,2 表示 stderr,若进程再打开其它文件,那么这个文件的文件描述符会是 3,依次递增。

  说明2:

  当两个进程打开了同一个文件时(即为图中所示情况),对应到 file[] 中是两个不同的 file 结构体,**因此各自拥有独立的文件偏移量**,不过指向的是同一个 inode 节点,所以修改的是同一个文件。

  说明3:

  存在以下几种情况(未必是所有情况,也许存在没有列出的其它情况)会导致两个进程的文件描述符指向同一个 file 结构:

  父进程 fork 出了子进程。此时父进程与子进程各自的每一个打开文件描述符共享同一个 file 结构

  使用 dup 或是 dup2 函数来复制现有的文件描述符

  其中第一种情况就是容器逃逸这个 case 的场景,我们知道 Docker Daemon 进程就是容器进程的父进程,如下图所示:

c.png


  1.3 open_by_handle_at 函数

  函数原型:

  int open_by_handle_at(int mount_fd, struct file_handle *handle, int flags);

  函数功能:

  引自 Linux 手册

  The open_by_handle_at() system call opens the file referred to by handle.

  The mount_fd argument is a file descriptor for any object (file, directory, etc.) in the mounted filesystem with respect to which handle should be interpreted.

  The caller must have the CAP_DAC_READ_SEARCH capability to invoke open_by_handle_at().

  译:

  open_by_handle_at() 用于打开 file_handle 结构体指针所描述的某一个文件

  mount_fd 参数为 file_handle 结构体指针所描述文件所在的文件系统中,任何一个文件或者是目录的文件描述符

  Linux 手册中特别提到调用 open_by_handle_at 函数需要具备 CAP_DAC_READ_SEARCH 能力

 
  1.   **Docker 1.0 版本对 `Capability` 使用黑名单管理策略,并且没有限制 `CAP_DAC_READ_SEARCH` 能力,因而造成了这个容器逃逸 case**

  2.   `file_handle` 结构体说明:

  3.  ```c

  4.   struct file_handle {

  5.   unsigned int handle_bytes; // Size of f_handle

  6.   int handle_type; // Handle type

  7.   unsigned char f_handle[0]; // File identifier

  8.   }
复制代码


  前面两个字段都好理解,关键是 f_handle[0] 字段,它一般都会是一个 8 字节的字符串,并且前 4 个字节为该文件的 inodenumber

  另外 CVE-2014-3519 这个漏洞也与 open_by_handle_at() 函数相关,有时间我再去研究一下那个 case

  3. "shocker.c" Line-by-Line Explanation

  分析 shocker.c 所需要的储备知识已经介绍完了。

  我在代码中用中文给出了比较详细的说明,下面来看下这段容器逃逸 PoC 代码。

  1.   /* shocker: Docker PoC VMM-container breakout (C) 2014 Sebastian Krahmer

  2.   *

  3.   * Demonstrates that any given Docker image someone is asking

  4.   * you to run in your Docker setup can access ANY file on your host,

  5.   * e.g. dumping hosts /etc/shadow or other sensitive info, compromising

  6.   * security of the host and any other Docker VM's on it.

  7.   *

  8.   * Docker using container based VMM: Sebarate pid and net namespace,

  9.   * stripped caps and RO bind mounts into container's /. However

  10.   * as its only a bind-mount the fs struct from the task is shared

  11.   * with the host which allows to open files by file handles

  12.   * (open_by_handle_at()). As we thankfully have dac_override and

  13.   * dac_read_search we can do this. The handle is usually a 64bit

  14.   * string with 32bit inodenumber inside (tested with ext4).

  15.   * Inode of / is always 2, so we have a starting point to walk

  16.   * the FS path and brute force the remaining 32bit until we find the

  17.   * desired file (It's probably easier, depending on the fhandle export

  18.   * function used for the FS in question: it could be a parent inode# or

  19.   * the inode generation which can be obtained via an ioctl).

  20.   * [In practise the remaining 32bit are all 0 :]

  21.   *

  22.   * tested with Docker 0.11 busybox demo image on a 3.11 kernel:

  23.   *

  24.   * Docker run -i busybox sh

  25.   *

  26.   * seems to run any program inside VMM with UID 0 (some caps stripped); if

  27.   * user argument is given, the provided Docker image still

  28.   * could contain +s binaries, just as demo busybox image does.

  29.   *

  30.   * PS: You should also seccomp kexec() syscall :)

  31.   * PPS: Might affect other container based compartments too

  32.   *

  33.   * $ cc -Wall -std=c99 -O2 shocker.c -static

  34.   */

  35.   #define _GNU_SOURCE

  36.   #include

  37.   #include

  38.   #include

  39.   #include

  40.   #include

  41.   #include

  42.   #include

  43.   #include

  44.   #include

  45.   #include

  46.   /**

  47.   * 攻击者构造的 file_handle 结构体

  48.   */

  49.   struct my_fil
复制代码


原文作者:佚名  来源:开发者头条

相关帖子

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部