云原生安全攻防|使用eBPF逃逸容器技术分析与实践

作者:pass、neargle @ 腾讯安全平台部

前言

容器安全是一个庞大且牵涉极广的话题,而容器的安全隔离往往是一套纵深防御的体系,牵扯到AppArmor、Namespace、Capabilities、Cgroup、Seccomp等多项内核技术和特性,但安全却是一处薄弱则全盘皆输的局面,一个新的内核特性可能就会让看似无懈可击的防线存在突破口。随着云原生技术的快速发展,越来越多的容器运行时组件在新版本中会默认配置AppArmor策略,原本我们在《红蓝对抗中的云原生漏洞挖掘及利用实录》介绍的多种容器逃逸手法会逐渐失效;因此我们希望能碰撞出一些攻击手法,进而突破新版本容器环境的安全能力,并使用更契合容器集群的新方式把“任意文件写”转化为“远程代码执行”,从而提前布防新战场。

结合腾讯蓝军近几年在云原生安全上的积累以及我们在WHC2021上分享的关于《多租户容器集群权限提升的攻防对抗》的议题,本文将着重探讨内核特性eBPF对容器安全性的挑战和云原生攻防场景下的实践。

使用eBPF的容器逃逸技术

eBPF简介

eBPF作为传统BPF的后继者,自内核3.17版本开始进入Linux内核。它提供了一种无需加载内核模块也能在内核里执行代码的功能,方式是在内核中实现了一个虚拟机,用于执行经过安全检查的字节码。

eBPF可以应用在安全、跟踪、性能分析、网络数据包处理、观测、监控等不同领域。

eBPF可以使用c语法的子集来编写,然后使用LLVM编译出eBPF字节码。

作为一个较新的内核特性,近些年来有许多利用这项新技术来解决一些安全问题的讨论和研究。使用eBPF我们可以使用诸如 kprobe 、 tracepoint 的跟踪技术,因此在防御的角度,可以用于实现HIDS、各种日志的监控等;而站在攻击者的角度,eBPF可以任意修改用户空间的内存,可以挂钩网络数据,这提供了很好的捷径用于编写 Rootkit ,同时作为一个新的内核特性,也给了漏洞挖掘人员一个新攻击面。

本文不过多描述eBPF的核心概念、eBPF程序如何编写,展开讲会失去文章的重点,下面给出几个文章可以帮助读者快速了解eBPF和入门知识:

•What is eBPF[1]•BPF and XDP Reference Guide[2]•The art of writing eBPF programs: a primer.[3]

新的弱点

Docker使用AppArmor来进一步限制容器,保证隔离的安全,其中有一个让很多逃逸技术失效的限制是禁用了mount(https://github.com/moby/moby/blob/4283e93e6431c5ff6d59aed2104f0942ae40c838/profiles/apparmor/template.go#L44),换言之,即使攻击者获取了一个 CAP_SYS_ADMIN 权限的容器,他也很难用一些和file system有关的逃逸手法。那有没有什么不需要和各种伪文件系统交互的方法呢?有一些,比如如果有 CAP_DAC_READ_SEARCH 权限,那么可以使用系统调用来实现逃逸至宿主机的root file system。从内核4.17版本开始,可以通过perf_event_open来创建kprobeuprobe,并且tracepoint子系统新增了一个raw_tracepoint类型,该类型也是可以通过简单的系统调用来使用,结合eBPF的使用,这就给了攻击者可乘之机。

容器逃逸分析

要想使用eBPF,需要一些权限和挂载伪文件系统,下表展示了eBPF kprobe、tracepoint使用的条件:

特性/功能 要求
bpf系统调用 拥有CAP_SYS_ADMIN; kernel 5.8开始拥有CAP_SYS_ADMIN或者CAP_BPF
Unprivileged bpf - "socket filter" like kernel.unprivileged_bpf_disabled为0或拥有上述权限
perf_event_open系统调用 拥有CAP_SYS_ADMIN; kernel 5.8开始拥有CAP_SYS_ADMIN或者CAP_PERFMON
kprobe 需要使用tracefs; kernel 4.17后可用perf_event_open创建
tracepoint 需要使用tracefs
raw_tracepoint kernel 4.17后通过bpf调用BPF_RAW_TRACEPOINT_OPEN即可

eBPF program作为附加在内核特定hook point的应用,在加载eBPF program时,并不会考虑被hook的进程是处于哪个namespace,又处于哪个cgroup,换句话说即使处在容器内,也依旧可以hook容器外的进程。

Linux kernel为eBPF程序提供了一系列固定的函数,这些函数被称为 BPF-HELPERS ,它们为eBPF程序提供了一定程度上的内核功能,可以使用 man bpf-helpers 来查看有哪些helper。而不同的eBPF program type能调用的helper也不同,关于tracing的helper里比较有意思的是下面几个:

•bpf_probe_read:安全地从内核空间读取数据•bpf_probe_write_user:尝试以一种安全的方式向用户态空间写数据•bpf_override_return:用于 error injection ,可以用于修改kprobe监控的函数返回值

这些helper提供了读写整个机器上任意进程用户态空间的功能,同时提供了内核空间的读取数据功能,当攻击者能向内核加载eBPF程序,那么有许多种办法进行权限提升或者容器逃逸:

•读取内核空间里的敏感信息,或者hook关键系统调用的返回点,获取其他进程空间里的敏感信息•修改其他高权限进程里的数据,注入shellcode或者改变进程关键执行路径执行自己的命令•其他更有想象力的方法...

需要注意的是eBPF无法改变进入Syscall时的参数,但是可以改变用户态进程空间里的内存数据。

有了上述思路,shellcode暂且不论,有什么进程或服务是linux各个发行版最常见,并且可以拿来执行命令的呢?对,那就是安全和运维的老朋友 cron 了。 cron 作为计划任务用的linux最常见服务,可以定时执行任务,甚至可以指定用户,而且由于需要及时更新配置文件,调用相关文件syscall十分频繁,用eBPF来hook再简单不过。

cron 其实有许多不同的实现,因此若从蓝军角度来看需要针对不同的cron实现进行分析,这里挑选 vixie-cron (https://github.com/vixie/cron)作为分析对象, vixie-cron 是一个较多linux发行版使用的cron实现,像 debian 、 centos 都是用的这个实现,当然不同发行版也会有一些定制修改,这个在稍后分析中会简单提及。

vixie-cron分析

vixie-cron 的整体逻辑比较简单,它有一个主循环,每次等待一段时间后都会执行任务并加载 cron 的一些配置文件,加载相关的配置文件的关键函数 load_database 位于https://github.com/vixie/cron/blob/690fc534c7316e2cf6ff16b8e83ba7734b5186d2/database.c#L47。

在正式读取配置之前,它会先获取一些文件和目录的文件信息:

load_database(cron_db *old_db) {
    // ...


    /* before we start loading any data, do a stat on SPOOL_DIR
     * so that if anything changes as of this moment (i.e., before we've
     * cached any of the database), we'll see the changes next time.
     */
    if (stat(SPOOL_DIR, &statbuf) < OK) {
        log_it("CRON", getpid(), "STAT FAILED", SPOOL_DIR);
        (void) exit(ERROR_EXIT);
    }
  // ...

SPOOL_DIR 是一个宏,代表了存放crontabs文件的目录,默认为 tabs ,但在常见的发行版中对有关路径的宏做了定制,比如下面是debian关于路径的修改:

-#define CRONDIR        "/var/cron"
+#define CRONDIR        "/var/spool/cron"
 #endif


             /* SPOOLDIR is where the crontabs live.
@@ -39,7 +39,7 @@
              * newer than they were last time around (or which
              * didn't exist last time around...)
              */
-#define SPOOL_DIR    "tabs"
+#define SPOOL_DIR    "crontabs"

因此 SPOOL_DIR 代表的就是我们熟悉的 /var/spool/cron/crontabs 目录。

然后会获取系统 crontab 的信息:

if (stat(SYSCRONTAB, &syscron_stat) < OK)  // #define SYSCRONTAB    "/etc/crontab"
        syscron_stat.st_mtim = ts_zero;

接下来是两个判断,如果判断通过,则进入

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值