容器安全基础知识(纯新手向)

admin 2023年12月30日18:53:41评论27 views字数 8311阅读27分42秒阅读模式

前言

纯新手向文章,故较为啰嗦。

PDF版:https://share.weiyun.com/a9Lraigo

安装 Docker

环境:Ubuntu,官方安装脚本自动安装

curl -fsSL https://test.docker.com -o test-docker.shsudo sh test-docker.sh

容器只是进程

从操作系统的角度来看,容器是进程,就像其他在系统上运行的程序一样。使用 docker 部署一个 nginx 容器

docker run -d nginx:1.23.1

容器安全基础知识(纯新手向)

查看进程中有没有 nginx 进程,

ps -fC nginx#-f:显示进程的完整格式信息。#-C:根据给定的进程名称筛选进程。

容器安全基础知识(纯新手向)

可以看见展示了 nginx 进程现在正在机器上运行,那如何区分这个 nginx 是从 Docker 中启动的容器还是安装在机器上的 nginx 服务呢?最简单的方法:检查正在运行的容器,docker ps容器安全基础知识(纯新手向)

还可以使用 Linux 进程工具来确定 Web 服务器是否作为容器运行,在主机上运行的容器都有一个称为 shim 进程的存在,这个 shim 进程是 containerd 的一部分,由 Docker 用来管理容器内的进程。

ps -ef --forest  --forest 选项显示进程的树状结构容器安全基础知识(纯新手向)

作为进程与容器交互

Linux 中的文件系统填充了有关正在运行的系统信息,只要用户在运行 Docker 的主机上具有适当的权限,就可以访问有关主机上运行的任何容器的信息。

查看进程可得 nginx 容器的 PID 是 895577,这个数字是主机上每个进程的编号目录,容器安全基础知识(纯新手向)

/proc 目录下保存着当前运行中的系统和进程的信息,这些信息以文件的形式呈现,我们可以根据 nginx 容器的 PID 定位到该进程的目录,该目录下有关所包含进程的更多信息。容器安全基础知识(纯新手向)

我们可以通过下面命令查看 nginx 容器(进程)的根文件系统

sudo ls /proc/895577 sudo ls /proc/895577/root

容器安全基础知识(纯新手向)

还可以向 nginx 容器(进程)的根文件系统中添加文件:sudo touch /proc/895577/root/123321,然后用 docker 命令列出容器上的文件来确认它已添加:docker exec bold_panini ls /容器安全基础知识(纯新手向)

容器是进程,也就是说可以使用主机工具来终止这些进程,而无需使用容器工具:sudo kill 895577

容器是进程从安全性来说,任何有权访问底层主机的人都可以使用进程列表来查看有关正在运行的容器的信息,即使他们不能使用 docker 工具,对于入侵者而言,它们可以使用 Linux 工具访问容器的环境变量,这些变量通常存储敏感信息,有权访问主机的用户可以读取进程区域内的文件内容以查看敏感信息。

隔离和命名空间

容器中也会运行着很多进程,那到底是如何确保在一个容器中运行的进程不会轻易干扰到另一个容器和底层主机?

下面会重点介绍命名空间的作用。

命名空间

也就是 Namespaces,Linux namespaces 是 Linux 内核提供的一种机制,用于隔离系统资源,让进程拥有自己独立的资源视图,从而提供更高层次的隔离和安全性。

Linux 目前支持八种类型的命名空间,每种类型负责隔离特定类型的系统资源,包括:

  • Mount Namespace 隔离了文件系统挂载点,允许在不同的 mount namespace 中拥有独立的挂载 点,这样进程就可以拥有独立的文件系统视图。

  • PID Namespace 隔离了进程 ID(PID),在不同的 PID namespace 中,相同的进程可能会有不同的 PID。

  • Network Namespace 隔离了网络资源,包括网络设备、IP 地址、路由表等,每个 Network namespace 拥有自己独立的网络栈。

  • Cgroup Namespace 隔离了控制组(cgroup),允许不同的 cgroup namespace 拥有不同的资源控 制。

  • IPC Namespace 允许不同的 IPC namespace 拥有不同的进程间通信资源。

  • Time Namespace 用于隔离时间命名空间,允许不同的 namespace 中有不同的系统时间。

  • UTS Namespace 隔离了主机名,允许不同的 UTS namespace 拥有不同的系统标识。

  • User Namespace 隔离了用户和用户组 ID,允许在不同的 user namespace 中有不同的用户标 识, 使得非特权用户可以在其 namespace 中拥有 root 权限。

使用 sudo lsns 查看主机上的命名空间,NPROCS 字段指的是有 106 个进程正在使用此主机上的第一组命名空间容器安全基础知识(纯新手向)

当我们创建一个容器时 docker run -d nginx ,默认会使用 mnt uts ipc pid net 命名空间。容器安全基础知识(纯新手向)


Mount namespace

mount (mnt) 命名空间为进程提供了文件系统的隔离视图,它可用于确保进程不会干扰属于主机上其他进程的文件,使用 mnt 命名空间时,会为进程提供一组新的文件系统挂载,以代替默认情况下的挂载。

通过查看主机的文件系统我们可以获取容器挂载信息:cat /proc/1009124/mountinfo容器安全基础知识(纯新手向)

还可以使用 findmnt -N [PID] 命令获取易看的数据视图,通过 docker inspect -f '{{.State.Pid}}' [容器NAMES] 命令更加方便的获取容器的 PID,比用 ps -fC xxx 更方便。容器安全基础知识(纯新手向)

容器在主机的 /var/lib/docker 目录下有一个根文件系统挂载点,在这里 Docker 存储所有的镜像和容器文件系统层,所以需要确保该目录上有安全的文件系统权限设置,否则可能导致容器中敏感数据的泄露或未授权的容器操作。

我们还可以使用 nsenter 命令与 Docker 创建的命名空间进行交互,他可以进入到另一个进程的命名空间中执行命令,而无需安装或使用 Docker CLI,比如访问容器的根文件:sudo nsenter --target 1009124 --mount ls /容器安全基础知识(纯新手向)


PID namespace

PID 命名空间允许进程具有主机上运行的其他进程的隔离视图,容器使用 PID 命名空间来确保它们只能查看和影响属于所包含应用程序的进程。多个容器也可以共享同一个 PID 命名空间。

运行命令 sudo unshare --pid --fork --mount-proc /bin/bash 将为我们提供一个新的 PID 命名空间中的 bash shell,当我们进入到新的shell中时会发现进程非常少,因为这是一个新的 PID 命名空间,与原来的相互隔离,互不影响,最后用 exit 命令退回到原命名空间。容器安全基础知识(纯新手向)

运行容器时,可以使用 PID 命名空间查看在另一个容器中运行的进程,先启动一个 web 服务容器:docker run -d --name=webserver nginx,再启动一个调试容器(这个容器的镜像很大 769MB):docker run -it --name=debug --pid=container:webserver raesene/alpine-containertools /bin/bash,也就是调试前面的 web 服务容器,在调试容器中我们可以看见 web 服务容器中的进程以及调试容器中的进程。容器安全基础知识(纯新手向)

Network namespace

Network (net) 命名空间负责提供进程的网络环境,我们可以使用 nsenter 命令获取容器的网络信息,比如获取容器的 IP 信息:sudo nsenter --target 1097551 -n ip addr,有趣的是,ip addr 命令来自主机,不必存在于容器中,用于解决 没有安装常用程序的容器中 相关问题排查。容器安全基础知识(纯新手向)

我们还可以使用 Docker 共享 Network 命名空间,类似于上面共享 PID 命名空间,启动一个调试容器连接到正在运行的容器,监听的 80 端口就是那个 nginx 容器开启的端口。容器安全基础知识(纯新手向)

在 Kubernetes 环境中,对于单个 Pod 中的所有容器,通常会共享网络命名空间。尽管无法在现有的 Pod 中启动调试容器,但可以使用新的短暂容器功能将动态容器添加到 Pod 的网络命名空间中。例如,首先使用 NGINX 镜像启动一个 Pod,然后通过 kubectl debug 命令向 Pod 添加一个短暂容器,短暂容器具有对原始容器的网络命名空间的访问权限。

Cgroup namespace


Control groups (cgroups) 命名空间用于控制进程在 Linux 系统上的资源使用情况,在容器化中,防止容器之间性能的相互干扰。


IPC namespace

IPC 命名空间是 Linux 的一种隔离机制,用于隔离进程间通信(IPC)资源,包括消息队列、信号量和共享内存等。通过 IPC 命名空间,不同的进程组能够有自己独立的 IPC 资源,彼此之间不会相互干扰或者共享这些资源。


UTS namespace

通过 UTS 命名空间,每个命名空间内的进程可以拥有独立的主机名和域名标识,即使在同一台物理机上运行的进程也可以拥有不同的标识。下图,在第一个容器中,我们得到一个随机分配的主机名,在第二个容器中,我们的主机名与底层主机的主机名一样。容器安全基础知识(纯新手向)


Time namespace

用于隔离时间命名空间,允许不同的 namespace 中有不同的系统时间。


User namespace

User 命名空间允许隔离运行进程的用户帐户等内容,最重要的是,从安全角度来看,它允许进程在命名空间内是 root 用户,而不是在主机上是 root 用户,这在容器化中特别有用,因为某些应用程序需要 root 才能运行,可以使用用户命名空间来启用这些应用程序,而不会引入以主机的 root 用户身份运行包含的进程的风险。

如果我们尝试以非 root 用户身份启动新的用户命名空间,但该名称空间不起作用,则此功能可能在主机级别被阻止,因为这个功能并不安全,会导致一些安全漏洞。使用命令 sysctl kernel.unprivileged_userns_clone 查看是否开启该功能,设置为1表示启动了该功能,设置为0表示非特权用户将无法在不使用 sudo 等工具的情况下创建新的用户命名空间。容器安全基础知识(纯新手向)


最小化容器的功能集

我们创建一个乌班图容器:docker run -it ubuntu:22.04 /bin/bash,可以看见我们在容器中是 root 用户,然后尝试修改系统时间:date +%T -s "01:01:01",结果被拦截了,我们不是有容器的 root 权限吗,为什么还会提示不允许操作?因此一定有其它功能阻止了这个操作:Linux功能。容器安全基础知识(纯新手向)

Linux 功能

在传统的 Linux 系统中,通常存在着强大的 root 用户和普通非特权用户之间的差别。如果一个进程需要执行只有 root 用户才能完成的操作,它必须以 sudo 运行或者需要将二进制文件设置为 "setuid root",这意味着它可以像 root 一样执行任何操作。从安全角度来看,这很危险,因为这种方式权限设置并不精细,当发现设置为 "setuid root" 的程序存在漏洞时,会让入侵者利用该程序的漏洞直接获取系统的 root 权限。

这个问题促使引入了 Linux 权限管理 (capabilities)。权限管理将原本的 root 权限分成了 41 种 (根据发布时的情况)独立的权限,可以分别授予进程或文件。这些权限有些非常细致,例如 CAP_AUDIT_READ,控制着读取审计日志的权限;有些权限则涵盖范围较广,例如 CAP_SYS_ADMIN,它赋予了一系列特权操作,如在主机上挂载和卸载文件系统的能力。

我们可以使用 pscap 命令查看主机上的进程被授予了哪些功能,需要安装相关包 sudo apt update && sudo apt install libcap-ng-utils容器安全基础知识(纯新手向)

功能和容器

我们看看 nginx 容器(进程)有哪些功能,pscap | grep nginx,下图中可以看见我们的 nginx 进程已经被赋予了一组功能,其中显示了 Docker 默认授予容器的功能集。此集旨在使大多数工作负载能够在容器内成功运行,同时限制可能导致权限升级攻击的任何功能。容器安全基础知识(纯新手向)

最小化容器的功能集

上文中可得默认情况下,Docker 容器提供了一组功能,我们可以删除部分或全部功能,以强化容器,功能是授予 root 用户的权限。这意味着,如果某应用程序作为非 root 用户运行良好,它应该在没有任何功能的容器中正常运行。在这些情况下,可以放弃所有功能,它应该可以正常工作。

在 Linux 系统中,端口号低于 1024 是属于特权端口。通常情况下,只有具备足够权限(比如 root 用户)的进程才能绑定这些特权端口。这意味着如果尝试以非特权用户的身份绑定一个低于 1024 的端口(比如 80 端口)将会绑定失败。在创建 Web 服务器时,如果需要绑定 80 端口,就需要具备 NET_BIND_SERVICE 功能。该功能授权进程可以绑定特权端口而不需要 root 权限。sysctl net.ipv4.ip_unprivileged_port_start容器安全基础知识(纯新手向)

在容器内部,情况可能因使用的运行时环境不同而有所不同,由于某些设置可以在网络命名空间级别进行管理,Docker 利用了这一点,通过对特定容器进行更改来影响其网络参数,比如下面的启动的容器 docker run -it raesene/alpine-containertools /bin/bash ,该容器默认情况下 net.ipv4.ip_unprivileged_port_start 参数被设置为 0,这允许非特权用户绑定低端口。容器安全基础知识(纯新手向)

有一种测试容器所需功能的基本方式是一次性去除所有功能(即通过使用 cap-drop=ALL 来运行应用容器),在其运行时监控是否出现错误。这样可以帮助确认容器是否实际上需要特定的功能来完成其工作。

Cgroups

在主机上一个行为不正常的程序可能会消耗所有可用的资源,导致整个系统崩溃。为了解决这个问题,Linux 使用控制组 (cgroups) 来管理每个进程对资源 (如 CPU 和内存) 的访问。Docker 和其他容器化工具利用 cgroups 来限制容器可以使用的资源,这在使用 Kubernetes 时特别有帮助,因为来自多个应用程序的工作负载经常在同一主机上共享资源。


cgroups v1 & v2

cgroups v2 提供了管理优势,Linux 内核 4.5 版中引入,直到最近才成为某些发行版的默认版本,命令 mount | grep cgroup 能查看当前主机上的 cgroups 版本,下面是 Ubuntu 20.04,v1 和 v2 版本都安装了,但是 Ubuntu 22.04 系统上只有 cgroups v2。 容器安全基础知识(纯新手向)

基础知识

我们可以使用文件系统来查看用于特定进程的 cgroup,以 bash 进程为例,先获取 bash 进程的ID:ps -fC bash ,再通过进程ID获取它的 cgroup:cat /proc/1037572/cgroup,输出显示了进该程所属的各个控制组及其相应的子系统,每个条目都提供了控制组的信息以及相关的路径。这些控制组允许对不同类型资源进行限制和管理,比如 memory 是管理内存资源的使用,pids 是管理进程数目的限制。容器安全基础知识(纯新手向)

使用 cgroups 限制资源

主机开两个 shell 窗口,一个窗口A先启动一个乌班图容器并进入容器shell中:docker run -it ubuntu:22.04 /bin/bash;另一个窗口B安装压力测试工具 stress:sudo apt update && sudo apt install stress,然后将安装好的 stress 从主机复制到容器中:docker cp /usr/bin/stress stoic_chaplygin:/usr/bin/容器安全基础知识(纯新手向)

检查容器是否接收到压力测试工具 stress,容器安全基础知识(纯新手向)

在容器中执行命令开始进行压力测试:stress -c 2,会启动两个占用了 2 个 CPU 核心的进程,然后通过在窗口B中执行 top 命令,可以验证对主机 CPU 的影响。下图可以看见,用于容器内部进行压力测试导致宿主机的 CPU 被容器占用完了,这是十分危险的,如果入侵者获取了容器的权限,他们就可以使用主机上所有 CPU 资源来挖矿。容器安全基础知识(纯新手向)

Docker 提供了多种选项来限制容器可利用的 CPU 量,其中最简单的是 --cpus 标志,允许指定可以使用的 CPU 核心数量,再次重复上方的压力测试实验,只不过这次启动乌班图容器需要限制容器使用 0.5 个 CPU:docker run --cpus 0.5 -it ubuntu:22.04 /bin/bash,下图可以看见,容器中 stress 进程最多占有一半的 CPU(本主机配置1核1G)。容器安全基础知识(纯新手向)

Linux 系统上常见的拒绝服务攻击称为分叉炸弹,当攻击者生成大量进程,最终耗尽系统资源时,就会发生这种攻击。默认情况下,容器 (和其他 Linux 进程) 在它们可以生成多少个新进程方面不受限制,这意味着任何进程都可以创建分叉炸弹。Cgroup 能够限制可以生成的进程数量,从而有效地保护主机免受分叉炸弹攻击。

docker run -it --pids-limit 10 ubuntu:22.04 /bin/bash


AppArmor

AppArmor 通过定义不同的配置文件来实施控制,这些配置文件可以应用于在主机上运行的进程。这些配置文件可以限制对多种资源的访问,包括文件、网络和 Linux 功能等。

在安装了 AppArmor 的系统上(本环境为 Ubuntu 20.04),命令 sudo aa-status 将显示 AppArmor 配置和状态的信息。输出中可得:① AppArmor 已加载并正常工作。② 系统上定义了 13 个配置文件。③ 目前没有进程具有活动的 AppArmor 配置文件。容器安全基础知识(纯新手向)


为了便于展示进程获得活动的 AppArmor 配置文件时会发生什么,我们可以启动一个新的 Docker 容器:docker run -d nginx,这时候有两个进程处于强制模式,这意味着 AppArmor 将根据为每个进程定义的配置文件限制它们的操作。容器安全基础知识(纯新手向)

seccomp

上文中讨论了各种安全层,这些安全层不仅可以将容器与主机上的其他进程隔离开来,还可以将容器与其底层主机隔离开来,接下来是容器运行时下一道防线 seccomp 过滤器。

Seccomp 过滤器是一种限制 Linux 进程可以执行的系统调用的方式。系统调用是用户空间程序与 Linux 内核之间的接口。每当程序需要访问主机内核提供的服务 (例如打开文件或创建新进程),它都会使用系统调用来实现。

Seccomp 过滤器是由 Berkeley Packet Filter (BPF) 程序编写的,用于限制进程可以进行的系统调用,允许实现非常精细的限制。最常见的用例是在容器环境中,用户通常以 root 身份运行,因此需要阻止可能导致容器越权的危险系统调用。


容器中的 seccomp 过滤器

作为 Docker 实施的默认限制集的一部分,seccomp 过滤器将应用于任何新容器。此过滤器可以捕获其他保护 (例如功能) 允许容器中的操作的情况。

为了创建此 seccomp 过滤器,Docker 创建了一个调用允许列表,然后阻止了任何不在列表中的系统调用。这意味着我们可以避免在向 Linux 内核添加新的危险系统调用(即可用于逃逸容器的系统调用)时的风险。

利用 unshare 命令在容器上创建新的命名空间这个调用是默认被阻止的,确定一个容器:docker run -it ubuntu:22.04 /bin/bashunshare 命令被阻止,容器安全基础知识(纯新手向)

我们可以显式禁用 seccomp 过滤器:docker run --security-opt seccomp=unconfined -it ubuntu:22.04 /bin/bashunshare 命令成功执行。容器安全基础知识(纯新手向)


创建自定义 seccomp 过滤器

尽管 Docker 默认的 seccomp 配置文件提供了良好的隔离级别,但在某些情况下,需要不同程度的限制。在这些情况下,我们需要一个自定义 seccomp 配置文件。比如我们想让容器可以限制容器中的 mkdir 和 rmdir 系统调用,先需要一个 JSON 格式的配置文件 no_chmod.json

{  "defaultAction": "SCMP_ACT_ALLOW",  "architectures": [    "SCMP_ARCH_X86_64",    "SCMP_ARCH_X86",    "SCMP_ARCH_X32"  ],  "syscalls": [    {      "names": ["chmod"],      "action": "SCMP_ACT_ERRNO"    }  ]}

然后在 Docker 启动时使用 --security-opt 参数加载该配置文件,

docker run -it --security-opt seccomp=restrict_mkdir_rmdir.json ubuntu:22.04 /bin/bas

容器安全基础知识(纯新手向)


参考文章

https://securitylabs.datadoghq.com/

原文始发于微信公众号(安全小将李坦然):容器安全基础知识(纯新手向)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年12月30日18:53:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   容器安全基础知识(纯新手向)https://cn-sec.com/archives/2350091.html

发表评论

匿名网友 填写信息