有次项目中通过漏洞反弹Shell就没有了后续操作,这很容易遗漏掉重要系统。
根据后来对系统的了解是采用了Docker来部署Jira、Jenkins等服务,但之前对于Docker的了解只在于如何使用拉取镜像,没有考虑到Docker相关的安全问题并且利用扩大渗透成果,例如,如何判断获取主机是否为Docker,有哪些工具可以提高效率,怎么通过Docker逃逸控制宿主机等后渗透攻击手法。
临时去查阅资料研究怎么逃逸,需要什么条件下才能触发,有哪些漏洞可以利用都已经晚了,而且随意操作有可能导致系统崩溃,为了后续遇到Docker可以成功利用,对Docker容器安全进行调研。

主要参考荷兰大学论文:
A Methodology for Penetration Testing Docker Systems
根据论文将所有攻击手法梳理出来:

Docker简介

Docker 使用Google公司推出的 Go 语言进行开发实现,基于Linux内核的cgroup、namespace,以及AUFS类的Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。

虚拟化是一种资源管理技术,将计算机实体资源,如服务器、网络、内存等予以抽象转换后呈现出来,在同一主机上同时运行多个系统从而提高系统资源的利用率,降低成本方便管理和容错等好处。
传统方式是在硬件层面实现虚拟化,需要额外的虚拟机管理应用和虚拟机操作系统层,Docker容器是在操作系统层面上实现虚拟化,直接复用本地主机操作系统,因此更加轻量级。

Docker引擎由如下主要组件构成:Docker客户端(Docker Client)、Docker守护进程(Docker daemon)、containerd以及runc。它们共同负责容器的创建和运行。

Docker Client

Docker Client是和Docker Daemon建立通信客户端,Docker Client可以通过三种方式和Daemon建立通信:

  • tcp://host:port
  • unix://path_to_socket
  • fd://socketfd
  • Docker daemon

    Docker daemon在宿主机后运行,作为服务端接受来自客户端的请求,主要功能包括镜像管理、镜像构建、REST API、身份验证、安全、核心网络以及编排。

    Docker daemon通过位于/var/run/docker.sock的本地IPC/Unix socket来实现Docker远程API,默认非TLS网络端口为2375,TLS默认端口为2376。

    Docker Images

    运行容器前需要本地存在对应的镜像,如果不存在默认会从Docker Hub镜像仓库下载。
    镜像由多个层组成,每层叠加之后外部来看就如一个独立的对象,镜像内部是一个精简的操作系统同时包含应用运行所必须的文件和依赖包。

    Docker Containers

    容器是操作系统虚拟化将系统资源划分为虚拟资源。容器并不是完整的操作系统,启动要远比虚拟机快,容器内部并不需要内核,也就没有定位、解压以及初始化的过程。

    Docker client选择合适的API调用Docker daemon,daemon接收到命令并搜索Docker本地缓存,观察是否有命令所请示的镜像。Linux默认安装时,客户端与daemon之间的通信是通过本地IPC/UNIX Socket完成的(/var/run/docker.sock)

    runc是一个轻量级的针对Libcontainer进行了馐的命令行交互工具,取代了早期Docker架构中的LXC。
    runc只有一个作用,创建容器。

    容器执行流程

    docker container run --name ctrl -it alpine:latest sh
    

    当执行如上命令时,Docker Client会将其转换为合适的API格式,并发送到正确的API端点。

    一旦daemon接收到创建新容器的命令,会向containerd发出调用。

  • 虽然名字叫containerd但是它不负责创建新容器,而是指挥runc去做。containerd将Docker镜像转换为OCI bundle,并让runc基于此创建一个新的容器。
  • daemon使用一种CRUD风格的API,通过gRPC与containerd进行通信。

    然后runc与操作系统内核接口进行通信,基于所有必要的工具(Namespace、CGroup等)来创建容器。容器进程作为runc的子进程启动,启动完后runc将会退出。

    shim是实现无daemon的容器

  • containerd指挥runc创建新容器,实际上创建容器时都会fork一个新的runc实例。
  • 一旦容器进程的父进程runc退出,相关联的containerd-shim就会成为容器的父进程。
  • shim职责是保持所有STDIN和STDOUT流是开启状态,从而当daemon重启的时候,容器不会因为PIPE的关闭而终止,并且将容器退出状态反馈给daemon。
  • 虚拟机和容器最大的区别是容器更快并且更轻量级,与虚拟机运行在完整的操作系统相比,容器会共享其所在主机的操作系统、内核。

    Data Persistence

    Docker的数据主要分为两类:持久化和非持久化数据,默认情况下非持久化存储是自动创建生命周期与容器相同,删除容器也会删除非持久化数据,非持久化数据默认存储于Linux:/var/lib/docker//之下。

    如果希望将容器数据保留下来,需要将数据存储于卷上,默认情况下,容器的所有存储都使用本地存储。

    Docker network

    Docker网络架构源自一种叫作容器网络模型的方案,Libnetwork是Docker对CNM的一种实现,提供了Docker核心网络架构的全部功能。

    Docker网络架构由3个主要部分构成:

  • Libnetwork
  • Libnetwork通过Go语言编写,并实现了CNM中列举的核心组件。
    驱动通过实现特定网络拓扑的方式来拓展该模型的能力。

    Docker安全机制

    利用了大部分Linux通用安全技术,这些技术包括了命名空间(Namespace)、控制组(CGroup)、系统权限(Capability)、强制访问控制(MAC)等。

    Docker Swarm模式

  • 默认是开启安全功能,就可以获得加密节点ID、双向认证、自动化CA配置、自动证书更新、加密集群存储、加密网络等安全功能。
  • Docker内容信任(Docker Content Trust,DCT)

  • 允许用户对镜像签名,并且拉取镜像的完整度和发布者进行验证。
  • Docker安全扫描(Docker Security Scanning)

  • 分析Docker镜像,检查已知缺陷,并提供对应的详细报告。
  • Docker密钥

  • 密钥存储在加密集群存储中,在容器传输过程中实时解密,并运行了一个最小权限模型。
  • Namespace

    内核命名空间属于容器非常核心的一部分,能够将操作系统进行拆分,使一个系统看起来像多个互相独立的操作系统一样。

    Docker容器是由各种命名空间组合而成的,本质就是命名空间的有组织集合。

    每个容器都由自己的PID、NET、MNT、IPC、UTS构成。

    Control Group

    命名空间用于隔离,那么控制组就是用于限额。
    容器之间是互相隔离的,但却共享OS资源,比如CPU、RAM以及IO。CGroup允许用户设置限制,这样单个容器就不能占用主机的CPU、RAM等资源了。

    Capability

    以root身份运行容器不是很安全,root拥有全部的权限,因此很危险,如果以非root身份运行容器那么将处处受,所以需要一种技术,能选择容器运行所需的root用户权限。

    在底层,Linux root由许多能力组成,包括以下几点:

  • CAP_CHOWN - 允许用户修改文件所有权
  • CAP_NET_BIND_SERVICE - 允许用户将socket绑定到系统端口号
  • CAP_SETUID - 允许用户提升进程优先级
  • CAP_SYS_BOOT - 允许用户重启系统
  • Docker采用Capability机制来实现用户在以root身份运行容器的同时,还能移除非必须的root能力。

    Seccomp
    Docker使用过滤模式下的Seccomp来限制容器对宿主机内核发起的系统调用。
    每个新容器都会设置默认的Seccomp配置。

    Attacker Models

    整个容器环境的潜在攻击面和攻击对象,由四类威胁:
    但实际最为重点的是,容器逃逸和针对Docker守护进程的攻击。

    要判断目标环境是不是容器,才谈得上容器逃逸,为后续渗透做更多准备。

    容器环境许多常用命令不存在,例如ping、ipconfig、netstat等都没有;

    成熟的业务不会直接通过容器进行部署,而是采用Kubernetes编排统一编排和调度,其中K8s每个Pod是一台逻辑主机,目标容器所在的Pod可能存在其它的容器。

    容器依赖了什么镜像,镜像是否包含有漏洞?

    Docker的API端口是否有未授权和暴露在外面?

    目标是k8s等集群系统,是否会有更多的宿主机节点存在相同安全问题?如何在这样环境中横向移动?

    .dockerenv

    判断根目录下 /.dockerenv 文件
    ls -alh /.dockerenv
    /.dockerenv是所有容器中都会存在这个文件,这个文件曾是LCX用于环境变量加载到容器中,现在容器不再使用LCX所以内容为空,通过这种方式来识别当前环境是否在容器中。

    Control Group

    为了限制容器的资源,Docker为每个容器创建了一个控制组以及一个名为docker的父控制组,如果某个进程在Docker容器中启动,则该进程将必须在该容器控制中,所以通过查看初始进程的cgroup来验证是否为容器。

    检查/proc/1/cgroup内是否包含docker等字符串
    cat /proc/1/cgroup |grep "docker"

    当前环境内进程是否少于5个?
    ps aux

    在系统进程中有一个根进行,ID为1,而且在容器中不会看到关于init或者systemd进程,而容器仅运行一个进程而不是完整的操作系统进程,所以可以通过系统进程数来判断是否为容器。

    Docker的镜像会尽可能的小,只保留一些必要的库,而一些像常用的命令、gcc库都会被去除保证最小化,那么可以通过最基本的ping命令都不存在,可能是在Docker环境中。

    当前环境是否常用的命令都不存在?

    ping、sudo、ifconfig、ssh、vi、gcc

    mount命令查看

    Docker容器环境检测方法【代码】