安全译文Docker环境下的事件分析和取证
Posted 平安集团安全应急响应中心
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安全译文Docker环境下的事件分析和取证相关的知识,希望对你有一定的参考价值。
译者注:随着越来越多企业选择在生产环境中部署Docker,甲方安全工作者在安全事件的应急响应和取证过程中需要考虑Docker环境的特殊性。译者把ERNW实验室发布的关于Docker环境下的事件分析和取证白皮书翻译过来,该文偏重事件分析原理的介绍,供有需要的安全爱好者学习,翻译中如有不当之处,欢迎交流。
在这篇文章中,我们描述了随着企业环境使用Docker的增多而对司法取证和事件分析所带来的影响。随着Docker使用得越来越多(Portworx Inc, 2017),docker对运行环境下法律过程和工具产生的意义几乎已经被肯定。因而我们先介绍了Docker的基础技术,并基于此,概述了与以前的数字证据取证方法的差异。具体来讲,我们关注在Docker容器下,相较于一台传统虚拟机,哪些数字证据会被需要或丢失,以及Docker本身会产生哪些新的事件痕迹和可能。
由于Docker提供得灵活快速的部署模型 (Datadog 公司, 2016) 以及基于大型生态系统的快速软件开发能力,使它在敏捷软件开发中已经有了广泛的应用,但它依旧是一种相对较新的技术。如何高流行与高适应率相结合,这意味着许多服务器已经作为Docker主机提供着高可用和高拓展性 , 已经有许多 ( 微 ) 服务在所谓的容器中运行 , 从而显著影响 IT 环境。这种影响已经从不同的IT安全视角中凸显出来(Jayanth Gummarajul, 2015) (Docker Security Team, 2016) (Theo Combe, 2016) (Jeeva Chelladhurai, 2016)。但docker在事件分析和调查取证方面尚未被考虑到,而且随着现在的docker在生产环境的不断部署,这一现象正变得越来越严重。
本文讨论了在取证分析和事故分析时, 因使用docker而引起变化所造成的问题。 我们把这个问题分为两个方面:
对于已知的取证方法和数字证据有哪些变化
Docker本身会添加哪些新证据
以前的取证方法主要集中在物理或虚拟机器上, 和在docker环境下的取证分析在实际分析步骤中没有根本不同,但传统取证分析过程缺失了对docker容器的利用。本文正是解决这一缺失。
(略,可参看原文)
本文的其余部分的结构如下:在第三章中,我们解释了Docker的基本技术,它们是理解本文的基础。第四章讨论了Docker在司法取证调查中应用。第五章总结了这项工作的内容,描述了它的结论,并对尚未完善的方面和未来的工作进行了展望。
Docker是用于管理和部署软件容器的开放源码软件。对于软件容器目前没有权威的定义,但是对这个跨操作系统边界的术语有个共识。这种理解将软件容器定义为操作系统中的运行时环境,它在使用单个公共内核时将进程或进程组隔离开来。除此之外,这种隔离涉及到进程空间的分离、进程间通信的机制、网络使用、文件系统访问和资源利用和分配。作为Docker的起源和最流行的平台,Linux是基于以下特征的Linux内核实现隔离:cgroups、命名空间和支持不同层的文件系统驱动程序。Open Container Iniative组织为Docker的容器创建提供了一套开放的标准。
在Docker的常用术语中, 术语容器指的是镜像在运行时的实例化。一份Docker镜像包含了创建一组指定特性的进程所需要的所有必要数据和信息。例如,映像包含有关所包含的应用程序提供哪些网络端口的信息,以及在实例化过程中应该执行哪个程序。镜像除了内核使用系统调用接口和核心设备,其他都是完全独立于主机系统上的的实例化实现。在Docker环境下的所有实体(例如容器和镜像,还包括网络字段)都被一个唯一标识符所标记(一般是containerID/iamgeID),然而该标识符的可由不同方式创建:镜像时唯一标识符由部分镜像内容hash生成,而容器ID是完全随机生成的。
Cgroups(Control groups的缩写) 是 Linux 内核用于限制和测量进程组资源利用率的机制。Cgroups 可以通过多种方式进行控制 (例如, 直接访问覆盖文件系统或抽象用户界面)。在对内容做取证分析时,要注意容器可被一个或多个cgroups关联复用。
Linux 内核当前支持以下命名空间: 挂载、进程 ID、网络、进程间通信、UTS和用户 ID。通过 Linux 内核, 可以创建不同的命名空间来实现各种资源的虚拟化。例如, 同一个文件系统装入点可以在不同的命名空间中使用。因此, 挂载点/mnt 可以在所有容器内以及在主机系统中使用;其他命名空间也可类似使用。
文件系统分层是基于文件系统驱动程序, 它提供了从不同层构建单个文件系统的可能性, 以便以统一和抽象的方式呈现给进程。如下图所示:
Docker镜像由一层或多层文件系统组成:各层可不同来源,可被不同用户创建。当容器被实例化时,创建一个R/w层,该层被设置为顶层。容器中的所有写访问仅在这一层中执行;基础层保持不变。
原则上,在Docker主机上进行取证分析和普通的取证过程一样:对硬盘dump,理想情况下得到内存内容。但是除非考虑到Docker容器的具体细节,否则对转储内容的分析可能遗失内容。对磁盘和内存转储的传统分析仍然主要围绕文件和进程列表, 但对容器的映射, 甚至有关某些文件是否与实时系统的实际文件系统视图的重现,这些相关的信息都不会被包括在内。以下各节提供了在进行Docker主机分析时需要考虑的各个方向,可参照来实现更加全面的取证分析。
Docker容器通过特定的文件系统驱动程序访问文件。文件系统不像传统的 (虚拟化的) 操作系统环境那样映射到块设备, 而是基于分层文件系统的结构。这种类型的文件系统访问如图2所示, 提供取证分析中必须考虑的各种特征。
Figure 2 文件系统分层
例如,在主机系统的磁盘dump中搜索目标文件,也可套用到从Docker镜像中搜寻。但是需要在Docker主机考虑以下附加问题:
哪些镜像提供了这些文件
哪些容器使用了这些文件
文件是否已经在容器层面删除(这种操作和常规文件系统删除有着潜在的不同)
要回答这些问题, 需知道一个镜像被哪几个容器通过r/w层使用。文件和容器之间的关联是 (仅) 可能通过运行时信息和 (如果可用) 特定配置文件。在运行时, docker ps命令列出所有已启动的容器,docker ps –a 列出所有尚未停止的Docker容器列表。第一行列出的内容以容器ID开始(在ContainerID缩写之后)。完整的容器ID可被用ContainerID命令来确认。如果无法在运行时候分析,可通过读取系统上的各种容器配置文件来获取容器配置信息。默认的Docker配置文件路径是/var/lib/docker。容器配置存储在/var/lib/docker/container/ContainerID
当前正在使用的容器则有两个特征标识:一方面,容器配置Config.v2.json包含了属性Running:true;另一方面,Linux文件系统中容器子目录shm的权限被设置为1777。r/w 层还有一个专用目录, 用于指示容器是否已在过去启动。
此外, 必须确定文件是否来源于容器 (即 r/w 层) 或镜像。此信息与在运行时分析文件的可见性有关。如果文件在容器中被删除, 则可能删除情况有两种:
该文件来源于 r/w 层: 在这种情况下, 文件将随底层文件系统层上的正常操作系统机制一起删除。
该文件来源于镜像层: 在这种情况下, 删除引用留在 r/w 图层中, 但该文件仍然存在于镜像层中。
因此, 必须使用不同的方法来恢复已删除的文件, 我们在下面几节中对此进行了说明。
如果删除了存储在容器的 r/w 层中的文件, 则该文件 (注意: 多为存储在主机文件系统中常规文件) 将在主机的文件系统中被删除。在文件系统中还残留了哪些元数据以及如何恢复这些文件取决于实际的主机文件系统 (如 ext3、ext4、zfs、…) 这将超出本文的范围, 但都不涉及docker的特殊性来解决。 通常我们所熟知的文件系统取证分析已经有充足的资料解释。(参见Carrier, File System Forensic Analysis, 2005)
这意味着从r/w层恢复已删除文件采用和从虚拟机(VM)的物理磁盘或者虚拟磁盘文件(例如.vmdk或vdi格式)一样的方法。应该注意的是对于传统的虚拟机环境,可直接在虚拟机中使用有root权限的取证分析工具来分析硬盘设备(例如/dev/sda)。该做法在Docker 容器中(即使已经有了root权限)不能使用,因为无法启动设备(除非启动容器),默认是采用提权的办法解决。
基本上,有两种通用的文件恢复方法可采用:
文件雕复
文件系统分析
下面几节将结合Docker上下文具体解释这两种众所周知方法的执行结果。
术语“文件雕复” (Anandabrata Pal, 2009) 通常描述堆文件卷,磁盘镜像,或者文件中字符组合(如魔数)进行线性搜索。如果不考虑文件系统,此方法可以恢复尚未被覆盖的已被覆盖和被删除的文件。但碎片文件(对于某些单文件会有一些例外)只能不完全的重建。此外,对于文件的任何元信息(如文件名,路径,时间戳或类内容)都会丢失。
由于这一限制,在Docker环境下进行取证分析会遇到问题:指定以前删除的文件,由文件雕复到特定的容器,甚至只是区分文件是否属于该docker容器,而容器或主机系统本神都需要元数据才能实现。但如前所述,被复雕还原的文件通常缺少此类信息,因而不可能进行有效的分配。只有当文件本身提供有关自身的上下文信息(例如自身包含容器ID)才可进行关联。
要解决上述的文件的来源问题,一种可行但比较毛糙的解决方法是对文件雕复所涉及到的所有文件进行排查,这些文件存储在文件系统分配中。或者用文件系统自带的还原机制(如下所述)。此外,还可以检查剩余的先前未知的已雕刻文件,可能在块组中发现各个数据块的所属。如果ext文件系统分配的新文件恰好和目标文件在同一父目录同一属主下,那么可以提供粗略的上下文。但是这些信息是非常含糊的,甚至是不准确的。索然在事件分析时可能有用,但因不确定性太强而不能用在取证调查中。
与文件雕刻相比, 文件系统分析使用存储在文件系统中的管理结构,,例如在NTFS文件系统中使用MFT(Main file table 主文件表),或在ext文件系统中使用包含组描述符的inode表。根据具体的情况, 已经删除的文件也可以根据这些信息,如使用the Sleuthkit toolset工具(Carrier, The sleuth kit, 2007)进行恢复, 具体细节见作者Brian Carrier的描述。虽然不能总是用此方法恢复已删除的文件, 但如使用恰当, 它确实提供了一些好处: 一方面已删除文件的碎片不是问题(与文件雕复不同),另一方面元数据,如文件名,绝对路径喝时间戳都可以重建。元数据(特别时文件名喝路径)可使我们确认目标文件到底来自哪个主机系统或容器(以及哪个特定的容器)。此类解析实质上是通过位于属于容器的文件路径中的容器/镜像id完成的。在这里,我们需要区分Docker所使用的两个主要文件分层系统,旧的AUFS和在当前Docker版本中使用的overlay2,他们在细节上略有不同,我们分别讨论。
在之前,我们描述了如何确认现有Docker容器ID的方法。现在我们假设已经使用文件系统分析从容器中恢复了已删除的文件。因此,文件的完整原始路径时时已知的。现在来看如何确认派生出文件的容器。在AUFS中,容器的文件存储在/var/lib/docker/aufs路径下。
考虑到已经恢复的文件可能来自不同的层。各自的层可以在他们的AufsID下发现:/var/lib/docker/aufs/layers/$AufsID。所以首先,需要从恢复路径中提取AufsID。要推断涉及该AufsID的所有容器,如上文所述,ContainerID和/var/liib/Docker/image/aufs/layerdb文件都挂载在$ContainerID/mountID 且是可读的。可通过匹配该文件中存储的ID和aufsid,如果匹配上,最初包含文件的容器就识别出来。
相比较AUFS,Overlay2使用一个叫MountID的iD。容器的MountID在容器的配置文件/var/lib/docker/containers/ContainerID/config.v2.json,容器对应的r/w层存储在路径/var/lib/docker/overlays/$MountID。在容器的启动初始化期间会创建文件夹/var/lib/docker/overlay2/$MountID-init,从而允许快速索引所有在/var/lib/Docker/overlay2下的r/w层。更深入的文件系统层存储在/var/lib/Docker/overlay2/$MountID/lower以缩写的形式降序排列。LayerID的简写还在/var/lib/Docker/overlay2/l/文件夹下用作对/var/lib/Docker/overlay2/$LayerID的符号链接。还可在/var/lib/docker/containers/$ContainerID下进一步取证分析子diff和merge子文件夹。Diff 文件夹包含在 r/w 图层中创建且尚未删除的所有文件。merge文件夹包含整个overlay2文件系统提供的所有文件系统层。当删除较低层提供的 r/w 层中的文件时, 会分配一个 inode, 标记为 Linux 字符设备, 从而指示 Overlay2 忽略此文件以查看全局视图。在 r/w 层中创建并随后删除的文件将使用正常的操作系统/文件系统功能删除。
文章最后提供了一个脚本可将Overlay2下根据输入的容器ID缩写输出所有相关的ID信息。
在这种情况下,删除应用存储在r/w层,但文件本省仍在镜像层。如下图所示:
Figure 3 文件系统层的删除
Overlay2 在 r/w 层中分配一个 inode, 它记录着已删除文件的名称, 并被文件系统标志标记为字符设备。因此, 在较低层中删除的所有文件都可以通过以下命令进行标识: find /var/lib/Docker/overlay2/$ContainerID/diff -type c。然后通过遍历层并检查相应文件是否存在。
我们想指出, 这里所述的过程和整个主机系统的取证分析是类似的。同样, 如果可以识别相应的 inodes, 可以直接从包含原始文件的底层层的目录中提取关联的文件。
Linux 命名空间会导致对取证分析的各种影响。UTS命名空间允许配置特定于容器的时区。在容器的实时分析中必须考虑潜在的时间差异, 但不影响取证分析的结果。Overlay2 在修改文件时始终使用主机系统的时间, 并在运行时动态调整每个容器的时间戳。
PID 命名空间可能导致主机上的进程和一个 (或多个) 容器中的进程接收相同的进程 ID (PID)。主机上的 PIDs 始终是唯一的, 容器内的 pid 只可能与主机上的 pid 显示相同的值。当日志文件包含 PIDs 并应用于取证分析时, 这一事实尤为重要。因为如果没有运行时信息, 不可能实现容器 pid 到主机 pid 的转换。
与 PID 命名空间类似, 用户命名空间允许将用户 id (UID) 或组id (GID) 从一个容器映射到主机上的另一个 UID。例如, 一个进程可以在具有 uid 0 的容器内运行, 但主机上的相应进程使用 uid 65000 运行。就像在 PID 命名空间中一样,如果日志文件中包含UIDs着会在时间分析时带来些问题,因为这种情况可能通过修改/etc/subuid 和/etc/subgid文件实现来将容器的UID/GID指派给主机的UID/GID。
Cgroups对取证分析的影响最小,它和对Docker主机的分析完全没关系。一般而言, cgroups 由于有描述性名称, 可用作附加证据 (例如, 对于在主机上运行的进程及其预期的使用)。cgroups 可以运行时或通过后台服务来基于配置文件动态创建。在运行时, 可以通过 sysfs 虚拟内核文件系统/cgroups/fs/cgroup 来识别,现有cgroups可通过文件/etc/cgconfig 来永久配置 。该文件包含 cgroup 名称, 并可能在取证分析上下文时提供进一步的证据
Docker管理组建(所谓的containerd)可以选择网络访问。containerd 公开一个网络端口, 它提供对Docker的整个管理功能的访问。在默认情况下, 对此网络接口的访问不需要身份验证, 因此可能导致未授权的容器启动。有鉴于此,不太可能确认是哪个用户要对特定容器的启动负责。
Containerd在网络上的暴露面可以在运行时使用包含选项I或-listen的命令行调用来定义。长期配置取决于操作系统,相应的配置文件的路径在/etc/default/Docker和/etc/Docker/daemon.json。
本文讨论了使用Docker容器对事件分析和取证过程的影响。在下面, 我们简要地总结了工作, 列出了局限, 展望了未来的工作, 来结束了文章的结论。
在第三章,我们讨论了Docker的基本技术,为第四章的讨论Docker在取证调查中的具体使用。具体来说,在4.1节,我们讨论重建已经删除的文件和与Docker容器关联的可能性,难点和方法,特别是删除文件各层之间的差异,以及AUFS和Overlay2之间的差异。我们还讨论了文件雕复技术的可能性和局限性。接下来讨论了Docker的命名空间取证相关,以及涉及到的cgroups和docker容器管理问题。
本文介绍了Docker取证,着重阐述了于物理主机和传统虚拟化技术相比,在对Docker环境下做取证和事件分析时需要考虑的情况。限于文章篇幅,对于Docker环境带来的取证分析的影响未能详尽无遗的写出。到目前位置,尚未有对Docker容器和Docker主机的现场取证的专项研究,以及未有对于Docker主机进行内存分析时遇到困难的必要调整。另一个未解决的问题是对完全被删除的容器重建和数据关联的可能性,而不是对已有容器的单个删除文件进行调查。还需要考虑未来Docker可能引入的新特性。而所有这些方面,对现有的取证工具进行针对Docker环境的插件开发是必要的,例如对内存分析工具Volatility和Rekall等。
在本文中,我们回顾了Docker在取证调查和事件分析中的各种情况,讨论了如何删除Docker容器中的文件以及如何会影响它们的可恢复性。我们区分并讨论了在r/w层删除文件的情,讨论了与底层的删除的异同。使用旧的AUFS和Docker当前使用的overlay2的异同,以及通过文件雕复和通过文件系统分析进行分析,并讨论了这些方法的问题和局限性。以及本文的局限性和在这一领域应该考虑的进一步研究方向。
该脚本可在Overlays2xia1收集所有容器相关的ID
if [ -e $1 ]; then echo "Please provide container ID as argument." Exit fi short_id="$1" docker_lib="/var/lib/Docker" docker_containers="$docker_lib/containers" docker_overlay="$docker_lib/overlay2" tmp_dir=$(echo $docker_containers/$short_id*) if [ ! -d $tmp_dir ]; then echo "No container matched the provided container ID." exit fi long_id=$(basename $tmp_dir) image_id=$(grep -Po ’Image":.*?[ˆ\\]",’ \ $docker_containers/$long_id/config.v2.json | \ grep sha256 | cut -d ":" -f "3" | cut -d ’"’ -f 1) image=$(grep -Po ’Image":.*?[ˆ\\]",’ \ $docker_containers/$long_id/config.v2.json | \ grep -v sha256 | cut -d ’"’ -f "3") mount_id=$(cat $docker_lib/image/overlay2/layerdb/mounts/$long_id/mount-id) path_to_rw_layer="$docker_overlay/$mount_id/diff" path_to_live_mount="$docker_overlay/$mount_id/merge" # list is ordered from highest layer to lowest layer layer_list=$(cat $docker_overlay/$mount_id/lower) IFS=’:’ read -r -a layer_array <<< "$layer_list" echo "=========== Container $long_id =======================" echo "|" echo "| Image: $image" echo "| ImageID: $image_id" echo "| MountID: $mount_id" echo "| Container Config: $docker_containers/$long_id" echo "|" echo "|=====================================================" echo "| Layers: for index in "${!layer_array[@]}"; do if [ $index -eq 0 ]; then continue fi ll=$(readlink $docker_overlay/${layer_array[$index]}) layer=$(echo $ll | cut -d ’/’ -f 2) echo "| $docker_overlay/$layer" echo "|------------------------------------------------" done
原文链接
https://static.ernw.de/whitepaper/ERNW_Whitepaper64_IncidentForensicDocker_signed.pdf
以上是关于安全译文Docker环境下的事件分析和取证的主要内容,如果未能解决你的问题,请参考以下文章
教程篇(5.0) 07. 威胁搜索和取证 ❀ FortiEDR ❀ Fortinet 网络安全专家 NSE 5