如何使用 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)); }"


相关文章
|
8天前
|
供应链 安全 Cloud Native
阿里云容器服务助力企业构建云原生软件供应链安全
本文基于2024云栖大会演讲,探讨了软件供应链攻击的快速增长趋势及对企业安全的挑战。文中介绍了如何利用阿里云容器服务ACK、ACR和ASM构建云原生软件供应链安全,涵盖容器镜像的可信生产、管理和分发,以及服务网格ASM实现应用无感的零信任安全,确保企业在软件开发和部署过程中的安全性。
|
3月前
|
供应链 安全 Cloud Native
阿里云容器服务助力企业构建云原生软件供应链安全
针对软件供应链的攻击事件在以每年三位数的速度激增,其中三方或开源软件已经成为攻击者关注的重要目标,其攻击方式和技术也在不断演进。通过供应链的传播,一个底层软件包的漏洞的影响范围可以波及世界。企业亟需更加标准和完善的供应链风险洞察和防护机制。本文将结合最佳实践的形式,面向容器应用完整的生命周期展示如何基于容器服务ACK/ACR/ASM助力企业构建云原生软件供应链安全。
|
3月前
|
云安全 安全 Serverless
Serverless 安全新杀器:云安全中心护航容器安全
Serverless 安全防护能力除了支持目前既定的等保合规(漏洞扫描、入侵检测、基线检测等)、安全隔离的能力外还支持 WAF 防火墙、支持通信加密、操作审计、权限管控等能力,也正是有了这些能力的加持,SAE 才能很好的服务了金融、政企、医疗等行业的客户;Serverless(SAE)未来还计划规划更多安全能力为企业保驾护航,包括:代码安全扫描、加密、堡垒机、最小权限、身份与访问管理、以及更多的攻击防护等能力的建设。
|
4月前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
5月前
|
存储 Kubernetes 负载均衡
云端应用容器化管理:全面深入评测与分析
阿里云ACK测评:提供一键式容器应用部署,集成ALB实现高效交付,但一键部署有时故障且高级配置需专业知识。操作界面直观,文档全面但对Helm等进阶主题指导不足。适合已有Kubernetes基础的用户,对于新手可能挑战较大。推荐给寻求云上容器管理解决方案的企业。
云端应用容器化管理:全面深入评测与分析
|
4月前
|
Kubernetes 网络协议 Linux
容器跨主机通信:Flannel网络实现机制分析(二)
容器跨主机通信:Flannel网络实现机制分析(二)
99 0
|
4月前
|
存储 Linux 数据中心
容器跨主机通信:Flannel网络实现机制分析(一)
容器跨主机通信:Flannel网络实现机制分析(一)
179 0
|
6月前
|
Cloud Native 安全 Docker
云上攻防-云原生篇&Docker安全&系统内核&版本&CDK自动利用&容器逃逸
云上攻防-云原生篇&Docker安全&系统内核&版本&CDK自动利用&容器逃逸
130 5
|
6月前
|
Kubernetes 安全 Cloud Native
云上攻防-云原生篇&Kubernetes&K8s安全&API&Kubelet未授权访问&容器执行
云上攻防-云原生篇&Kubernetes&K8s安全&API&Kubelet未授权访问&容器执行
146 3
|
7月前
|
监控 安全 数据安全/隐私保护
【Docker专栏】Docker容器安全:防御与加固策略
【5月更文挑战第7天】本文探讨了Docker容器安全,指出容器化技术虽带来便利,但也存在安全隐患,如不安全的镜像、容器逃逸、网络配置不当等。建议采取使用官方镜像、镜像扫描、最小权限原则等防御措施,并通过安全的Dockerfile编写、运行时安全策略、定期更新和访问控制等加固容器安全。保持警惕并持续学习安全实践至关重要。
698 7
【Docker专栏】Docker容器安全:防御与加固策略