如何使用 eBPF 分析容器的安全问题

简介: 【2月更文挑战第9天】

既然容器还是共享内核的,运行在内核中的 eBPF 程序自然也能够跟踪和分析容器中的应用程序。但由于容器利用 Linux 的 namespace 机制进行了隔离,其跟踪和分析方法又跟直接运行在主机内的进程有些不同。


以跟踪恶意程序的执行为例,为了躲避安全监控,很多恶意程序并不是在容器一开始启动的时候就运行了恶意进程,而是先启动一个正常程序,之后再创建新的恶意进程。这种场景特别容易出现在容器安全漏洞被恶意软件侵入的场景。


跟踪系统调用 execve。比如,执行下面的 bpftrace 命令,就可以跟踪新创建的进程:

sudo bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%-6d %-8s", pid, comm); join(args->argv);}'


打开一个新终端,执行一条 ls 命令,然后你就会看到如下的输出:

8964   bash    ls --color=auto


启动一个 Ubuntu 容器:

# -it表示进入容器终端,--rm表示终端关闭后自动清理容器
docker run -it --rm --name bash --hostname bash ubuntu:impish


在容器中执行 ls 命令,忽略容器启动过程中的进程跟踪信息(Docker 在启动容器过程中也会执行大量的命令),你会看到跟刚才类似的输出:

9018   bash    ls --color=auto

这个输出跟刚才在主机中执行 ls 后的结果是一样的,只根据这个输出,我们显然没法区分 ls 是不是运行在容器。


虽然所有容器都是共享内核的,但不同的容器之间还是通过命名空间进行了隔离。你可以使用 lsns 命令来查询容器或者主机的命名空间。比如,在刚才的容器终端中执行 lsns 命令,就可以看到如下的输出:

NS TYPE   NPROCS PID USER COMMAND
4026531834 time        2   1 root bash
4026531835 cgroup      2   1 root bash
4026531837 user        2   1 root bash
4026532530 mnt         2   1 root bash
4026532531 uts         2   1 root bash
4026532532 ipc         2   1 root bash
4026532533 pid         2   1 root bash
4026532535 net         2   1 root bash


在内核中,进程的基本信息都保存在 task_struct 结构体中,其中也包括了包含命名空间信息的  nsproxy 结构体。nsproxy 结构体的定义如下所示:

struct nsproxy {
  atomic_t count;
  struct uts_namespace *uts_ns;
  struct ipc_namespace *ipc_ns;
  struct mnt_namespace *mnt_ns;
  struct pid_namespace *pid_ns_for_children;
  struct net        *net_ns;
  struct time_namespace *time_ns;
  struct time_namespace *time_ns_for_children;
  struct cgroup_namespace *cgroup_ns;
};


为了区分一个进程是属于容器还是主机,我们可以在跟踪结果中输出 PID 命名空间和 UTS 命名空间中的主机名。


bpftrace 内置了表示进程结构体的 curtask,因而对前面的 bpftrace 脚本,我们可以进行下面的改进:

tracepoint:syscalls:sys_enter_execve {
  /* 1. 获取task_struct结构体 */
  $task = (struct task_struct *)curtask;
  /* 2. 获取PID命名空间 */
  $pidns = $task->nsproxy->pid_ns_for_children->ns.inum;
  /* 3. 获取主机名 */
  $cname = $task->nsproxy->uts_ns->name.nodename;
  /* 4. 输出PID命名空间、主机名和进程基本信息 */
  printf("%-12ld %-8s %-6d %-6d %-8s", (uint64)$pidns, $cname, curtask->parent->pid, pid, comm); join(args->argv);
}


这段代码中的具体内容含义如下:

  • 第 1 处,把内置变量 curtask 转换为我们想要的 task_struct 结构体;
  • 第 2 处,从进程信息的 nsproxy 中读取 PID 命名空间编号;
  • 第 3 处,从进程信息的 nsproxy 中读取 UTS 命名空间的主机名(也就是在容器中执行 hostname 命令后的输出);
  • 第 4 处你已经非常熟悉了,就是把刚才获取的信息输出,以便我们观察。


在运行之前,还需要给它引入相关数据结构定义的头文件:

#include <linux/sched.h>
#include <linux/nsproxy.h>
#include <linux/utsname.h>
#include <linux/pid_namespace.h>


同时,由于输出的内容比较多,为了便于理解,你还可以在脚本运行开始的时候输出一个表头,表示每个输出的含义:

BEGIN {
  printf("%-12s %-8s %-6s %-6s %-8s %s\n", "PIDNS", "CONTAINER", "PPID", "PID", "COMM", "ARGS");
}


把头文件引入和改进后的 bpftrace 脚本保存到 execsnoop-container.bt 文件中,然后打开一个新终端,运行下面的命令来执行:

sudo bpftrace execsnoop-container.bt


接下来,分别在容器终端和主机终端中执行一个 ls 命令,就可以得到如下的输出:

PIDNS        CONTAINER PPID   PID    COMM     ARGS
# 容器ls命令跟踪结果
4026532533   bash     41046  41335  bash    ls --color=auto
# 主机ls命令跟踪结果
4026531836   ubuntu.localdomain 40958  41356  bash    ls --color=auto

在输出中,容器 ls 命令跟踪结果中的 PID 命名空间 4026532533 跟上述容器中 lsns 结果是一致的,而主机名 bash 也跟运行容器时设置的 --hostname name 一致,因而我们很容易区分这条 ls 命令的来源。


只要理解了容器的基本原理,在跟踪过程中加入容器的基本信息,容器内外进程的跟踪和分析并没有本质的区别。


实际上,用户态进程的跟踪也是一样的,唯一需要注意的就是找到容器内二进制文件的正确路径。虽然容器文件系统在不同的 mount 命令空间中,但对于每个进程来说,Linux 都在 /proc/[pid]/root 处创建了一个链接。因而,容器内的文件就可以通过 /proc/[pid]/root 在主机中访问。


可以执行下面的命令,查询容器的 PID,进而再查询 bash 的 uprobe 列表:

# 查询容器进程在主机命名空间中的PID
PID=$(docker inspect -f '{{.State.Pid}}' bash)
# 查询uprobe
sudo bpftrace -l "uprobe:/proc/$PID/root/usr/bin/bash:*"
# 跟踪bash:readline的结果
sudo bpftrace -e "uretprobe:/proc/$PID/root/usr/bin/bash:readline { printf(\"User %d executed %s in container\n\", uid, str(retval)); }"


相关文章
|
4月前
|
Kubernetes 安全 Cloud Native
云原生|kubernetes|pod或容器的安全上下文配置解析
云原生|kubernetes|pod或容器的安全上下文配置解析
112 0
|
6月前
|
运维 安全 Docker
Docker 网络模型:多角度分析容器网络的原理与应用
Docker 网络模型:多角度分析容器网络的原理与应用
55 0
|
7月前
|
Kubernetes 安全 Linux
开源Chart包安全分析发布,阿里云视角容器安全基线的重要性
云原生环境下,容器成为了软件开发过程中打包与分发的标准。
205 0
开源Chart包安全分析发布,阿里云视角容器安全基线的重要性
|
7月前
|
Kubernetes 关系型数据库 MySQL
容器 & 服务:Helm Charts配置文件分析
chart 是 Helm 的应用打包格式。chart 由一系列文件组成,这些文件描述了 K8s 部署应用时所需要的资源,比如 Service、Deployment、PersistentVolumeClaim、Secret、ConfigMap 等。 chart可繁可简,即可以只用于部署一个单独的服务,例如mysql、nginx等等,也可以用于部署整个应用,例如由HTTP服务、数据库、缓存、中间件等共同构成的复杂应用。
325 0
|
4月前
|
运维 监控 数据可视化
日志管理:收集和分析Docker容器日志
容器化技术的普及使得应用的部署和管理更加便捷,但随之而来的挑战之一是有效地管理和分析容器产生的大量日志。本文将深入探讨Docker容器日志管理的重要性,介绍常用的日志收集工具,以及如何分析和利用这些日志数据,提供更为丰富和实际的示例代码,帮助大家更好地理解和应用日志管理的关键技术。
|
11天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
15 1
|
1月前
|
Kubernetes Go 开发者
Go语言与Docker容器结合的实践应用与案例分析
【2月更文挑战第23天】本文通过分析实际案例,探讨了Go语言与Docker容器技术结合的实践应用。通过详细阐述Go语言在容器化环境中的开发优势,以及Docker容器技术在Go应用部署中的重要作用,本文旨在为读者提供Go语言与Docker容器结合的具体实现方法和实际应用场景。
|
1月前
|
存储 安全 测试技术
|
6月前
|
存储 算法 C语言
vector容器的详解与分析
vector容器的详解与分析
|
4月前
|
监控 安全 持续交付
Docker与容器化安全:漏洞扫描和安全策略
容器化技术,特别是Docker,已经成为现代应用程序开发和部署的关键工具。然而,容器化环境也面临着安全挑战。为了保障容器环境的安全性,本文将介绍如何进行漏洞扫描、制定安全策略以及采取措施来保护Docker容器。我们将提供丰富的示例代码,以帮助大家更好地理解和应对容器安全的问题。

热门文章

最新文章