译者的话
本文的作者是NCC Group检测工程(Detect Engineering)团队负责人Ben Lister和安全专家Kane Ryans。文章深入浅出地介绍了NCC Group内部基于K8s audit log的异常检测体系,具有很强的可实践性,对于国内云原生安全领域研究和落地工作有很大参考价值。
原文地址:https://research.nccgroup.com/2021/11/10/detection-engineering-for-kubernetes-clusters/
技术人员行文简约,为了方便理解,译者在翻译过程中进行了符合原意的轻微改动和语句衔接。另外,一些专业术语(如service account)在特定语境下(如云原生领域)本身就是专有名词,不再翻译。
以下为正文。
前言
本文叙述了NCC Group检测工程团队和容器化团队在落地Kubernetes检测工程的协作。另外,本文还给出了检测工程团队在新技术领域应用检测工程的一般性方法论,以及在开发针对Kubernetes环境攻击的检测时如何应用这样的方法论。
后文第一部分首先面向从事检测工程的人们介绍了Kubernetes基础知识,包括Kubernetes日志功能和在检测工程中使用这些日志时可用的选项。
第二部分则面向从事容器化工作但不清楚检测工程的人们介绍了检测工程的基础知识。
第三部分则将所有东西连在一起,也是我们的独特贡献之处。特别地,这部分讨论了我们针对Kubernetes集群内权限提升攻击创建的新型检测规则,来更好地赋能安全运营团队,实现对Kubernetes集群安全事件的监控,从而真正在现实世界对Kubernetes集群起到防护效果。
熟悉Kubernetes的人们可以直接跳过第一部分,阅读第二部分,甚至直接阅读我们在Kubernetes检测工程方面所做的工作(第三部分)。
第一部分:背景知识——Kubernetes的简单介绍
在讨论Kubernetes检测工程之前,我们先讲一下Kubernetes的基本概念、它的主要组件以及这些组件如何协同工作。
Kubernetes是什么?
Kubernetes(通常简写为K8s)是一个开源的容器编排系统,用来将计算机应用程序的部署、扩容缩容及管理工作自动化。它最初由Google设计,现在由云原生计算基金会(Cloud Native Computing Foundation)维护。Kubernetes致力于提供一个“将容器负载(container workloads)的部署、扩容缩容和运维工作自动化的平台”。它与一系列容器工具协同工作,在集群中运行容器,容器的镜像通常使用Docker构建而成[1]。
图1:一个典型的集群示意图
关于Kubernetes组件的更多信息可以在K8s官网找到。
在高水平上,集群可以被分为两部分:控制平面和工作节点。控制平面由若干管理集群的组件共同构成,如API server。后文在讨论检测工作时,我们的主要关注点就是控制平面。
在我们讨论集群的不同组件时,很重要的一点是弄清楚认证和授权是如何处理的。Kubernetes使用角色来决定一个用户或者pod是否被允许发起特定连接或调用请求。角色的有效范围可以是整个集群(ClusterRole),也可以是特定的命名空间(Role)。
角色包含一个资源列表,这个列表决定了该类角色能够访问哪些资源,以及对这些资源执行何种动作。角色首先被声明,然后被附加到RoleBindings资源上。RoleBindings资源将角色(权限)和用户及系统关联起来。大家可以在Kubernetes官方文档中找到更多关于此访问控制功能的信息。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
图2:从Kubernetes文档中摘录的策略配置示例
上述策略允许任何系统或用户检索集群default
命名空间内的所有Pods资源。
为什么使用Kubernetes?
Kubernetes为应用提供了水平和垂直扩展能力。例如,通过增加更多节点来分担工作负载,而非向已有节点来增加更多资源。
对于许多公司来说,Kubernetes是迷人的,因为它提供了记录配置、跨多服务器部署应用和基于当前需求自动化扩容缩容的配置能力。
Kubernetes日志
检测工程的效果和数据有直接关系。这就是为什么在新技术领域应用检测工程时,第一个问题就是找到可靠和持续的日志。
由于Kubernetes的承载环境十分多样,如Azure Kubernetes Service(AKS),Elastic Kubernetes Services(EKS)和Google Kubernetes Services等托管平台,甚至原生无托管的Kubernetes部署方案等,上述问题变得更难了。
最终,我们决定利用API server的审计日志。由于API server的天然特性,这些日志提供了一个很好的观察集群内部的视角。在正常操作过程中,每一个导致Kubernetes集群状态改变的请求都会通过API server。因此该审计日志是前述所有平台中唯一持续稳定的日志源。
然而,在使用这些日志时还有一些问题。首先,日志功能必须被启用,官方文档中的审计策略是一个很好的起点,也是我们曾在测试环境中使用过的。一些托管平台可能不允许你自定义审计日志,但是它仍然需要被启用。从安全的角度来看,某些错误配置,如允许对Kubelet的未认证访问,可能会导致攻击者绕过API server,在这种情况下,审计日志将毫无用处,攻击者将绕过所有基于此的检测。
值得注意的是,审计日志包含了从编排角度看到的所有正在发生的事情(部署Pod、改变配置、添加用户等等),但没有容器内部发生的事情,然而在一些场景下,容器内才是初始攻击的发生点。
一条审计策略可能会很宽泛,很容易就导致生成大量无关紧要的事件。因此,应通过测试来确保事件的吞吐在一个可接受的限度内。
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Request",
"auditID": "bd93fded-1f5a-4046-a37c-82d8909b2a80",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/default/pods/nginx-deployment-75ffc6d4d-nt8j4/exec?command=%2Fbin%2Fbash&container=nginx&stdin=true&stdout=true&tty=true",
"verb": "create",
"user": {
"username": "kubernetes-admin",
"groups": [
"system:masters",
"system:authenticated"
]
},
"sourceIPs": [
"<removed>"
],
"userAgent": "kubectl/v1.21.2 (darwin/amd64) kubernetes/092fbfb",
"objectRef": {
"resource": "pods",
"namespace": "default",
"name": "nginx-deployment-75ffc6d4d-nt8j4",
"apiVersion": "v1",
"subresource": "exec"
},
"responseStatus": {
"metadata": {},
"code": 101
},
"requestReceivedTimestamp": "2021-10-21T08:39:05.495048Z",
"stageTimestamp": "2021-10-21T08:39:08.570472Z",
"annotations": {
"authorization.k8s.io/decision": "allow",
"authorization.k8s.io/reason": ""
}
}
图3:一条审计日志的示例
第二部分:背景知识——检测工程方法
这部分将给出我们进行检测工程的一些背景知识,不局限于Kubernetes。如果你仅仅想了解Kubernetes相关的内容,可以跳过这部分,直接阅读第三部分。
通常情况下,在遇到问题时,把问题分解为若干简单的子问题是有帮助的。在检测工程中,我们通过把检测任务分为不同的类别来实现简化。这样的分类帮助我们能够透过每一类提供的独特视角去看待问题,从而建立一个均衡的策略来处理不同的检测类型。我们的检测工程中有三种不同的类别,每一类都有其优势与不足之处,三类合并起来构成了一个完整的检测策略,它们分别是:特征、行为和异常。
特征
特征是最简单的类别。它主要包括字符串和已知妥协指标(Indicator of Compromise,简称IoC)的检测。它们很容易创建,能够快速生产,通常用来针对单个攻击技术或步骤,因此产出的假阳性结果比较少,SOC分析人员理解起来也就很容易。然而,它们通常很容易被攻击者绕过,不应作为检测依赖的唯一指标。无论如何,特征是检测的第一层级,对众多攻击技术有很好的覆盖度。
行为
行为分析比特征分析更具鲁棒性。攻击技术被拆解为若干核心操作,基于此建立行为分析方法。行为分析通常基于更加不可变的数据源——在不改变技术涉及的行为本身的情况下,数据无法改变。虽然这种检测本身的质量通常更高,但相比基于特征的分析来说,行为分析产生的告警可能会更差。行为分析可能会产生更多假阳性,分析人员也更难理解一个告警产生的缘由,因为相关分析更加抽象,需要对被检测技术本身有更深入的理解。
异常
异常分析指的是这样一类检测:首先定义“正常”行为,并对所有超出正常行为范围的行为进行告警。这类检测未必是检测恶意攻击,而是所有具有显著区别的行为,因此值得我们进一步研究。单一的基于异常的检测可以有效应对许多不同的技术。然而,在和基于特征、行为的检测进行对比时,这种方法的表现也更难评估,可能在不同的现实环境之间有很大的差异。某种程度上,这可以通过基于历史数据计算阈值等技术缓解,这样一来,阈值总是贴合具体环境的。
理解你的优势所在
在落地检测工程时,还有一些有用的观念,如“弄清楚我们能在哪里获胜”。这指的是,在任何给定环境、系统或科技中,总会有防守者具有先天优势的地方。这可能是因为日志记录更胜一筹,而攻击者被迫去做一些事情,或攻击者的选项很有限。例如,在Windows环境中,防守者在检测横向移动时就有优势。因为有两倍数量的日志(源主机和目的主机的),相比其他战术(如命令执行),这种情况下只有少部分技术可用,但攻击者通常不得不在某些时候去横向移动,以达到他们的目标。
第三部分:Kubernetes异常检测
通过与NCC Group容器化团队的协作,我们识别了几种需要检测的场景用例。我们聚焦的主要场景是,攻击者在集群中是如何实现权限提升的。两种主要手段分别是滥用已有账户的权限和创建一个Pod来尝试提升权限。我们的所有检测都基于Kubernetes审计日志中的事件。为了使行文简洁,本文仅对我们的所有检测的一部分做介绍。
特征
对于基于特征的检测来说,审计日志是很有用的数据源,它们提供了有价值的信息,如请求的URI、user-agents以及镜像名称(如果存在)等。可以将数据中的这些字段与已知的可疑和恶意字符串列表进行匹配。这些字符串列表也很容易更新。例如,根据某个特征的有效性来添加新特征或删除旧特征。
RequestURI中的交互式命令执行
对于特定请求来说,审计日志中的requestURI
字段包含了被执行的命令、命名空间以及执行此命令的Pod信息。因此,它可以用来识别出命令行shell的使用,在未授权的情况下,这可能暗示着未来的进一步可疑行为。
下面列出了可以用来从requestURI
字段中检测这种行为的shell特征的样例。前文中的图3则给出了一个完整的例子来展示包含这样的字符串的审计日志。一些可用于搜索的有用特征如:
- %2Fbin%2Fbash
- %2Fbin%2Fsh
- %2Fbin%2Fash
User-Agent
user-agent是一项HTTP头,提供了操作系统类型、发出此请求的应用程序等元信息。在某些场景下,它也可以用来识别发出请求的具体工具。
在基于特征的异常检测中使用user-agent的一个实际例子是搜索包含access_matrix
的user-agents。出现该字符串意味着Rakkess(一个访问权限查询工具)的使用。这个工具是一个kubectl的插件,仅在开发环境或临时环境中常见。该工具的非预期使用可能意味着攻击者正试图执行一些后渗透操作。
"userAgent": "kubectl-access_matrix/v0.0.0 (darwin/amd64) kubernetes/$Format"
另一个例子是cURL或HTTP请求库的使用,可能意味着后渗透操作,尤其是当这些操作之前从未在当前环境中出现过时。值得注意的是,user-agent很容易被发出源所修改,因此要绕过这类检测也很容易。
镜像
这一字段包含了即将部署的镜像名以及该镜像来自哪个镜像仓库。这些镜像中的工具都有合理使用场景,但是在Kubernetes集群中任何未授权的使用都值得警惕和进一步调查。
这样的例子包括但不限于以下镜像:
- cyberark/kubiscan
- aquasec/kube–hunter
- cloudsecguy/kubestriker
- corneliusweig/rakkess
镜像这一字段也可以用于检测挖矿Pod。在攻破一个集群后部署挖矿程序是常规战术,因此任何已知的挖矿程序镜像都应作为特征被加入到已知的恶意容器镜像列表中。
行为
匿名用户登录
用户在未认证情况下执行操作对于任何系统通常都是一件不好的事情,Kubernetes也是一样。在一些广受关注的安全事件中,攻击者正是通过暴露在互联网上、允许未认证访问的API server发起初始访问。防御的首要任务是通过禁用未认证访问来缓解攻击,这可以通过在kubelet或API server启动时添加--anonymous-auth=false
标志来实现。
作为一层额外的防御,任何向API server发起的请求,只要user字段是system:anonymous
或system:unauthenticated
,都应该被告警。
攻破Service Account
Kubernetes pods通常在某个service account上下文中运行。Service accounts是一类由Kubernetes创建的用户账户,并被Kubernetes系统自身使用。它们与标准的用户账户不同,其名称以“serviceaccount”为前缀,通常具有明确定义的行为。为了认证为某一service account,所有pod都在/var/run/secrets/kubernetes.io/serviceaccount/token
文件中存储了一个bearer token,在向API server发起请求时使用。然而,如果某一pod被攻破,攻击者就能够访问到该token,从而认证为该pod使用的service account,可能导致权限提升。
对这类攻击的检测是困难的,因为我们未必能够区分正常service account的用途和攻击者对此service acount的使用。然而,我们的优势在于,service account具有明确定义的行为。一个service account不应向API server发出一个它不被授权去做的请求。另外,一个service account不应检查它自身拥有的权限。这两种动作都意味着有人在使用该service account,而非一个系统。因此,任何携带“serviceaccount”前缀的用户发起的被拒绝的请求或字段包含“selfsubjectaccessreview”的请求都应该被告警。
其他值得注意的行为
本文没有深入讨论的一大类行为是Kubernetes对基于角色的访问控制(Role-Based Access Control,RBAC)的处理。RBAC是Kubernetes中的一个复杂话题,值得写一篇单独的文章去讨论它可能被滥用的不同情况。然而,在任何系统中,赋予用户高权限的事件都值得告警,因此也应当在Kubernetes检测工程中被处理。
另一类应该被标记为恶意的行为是“短生命周期的一次性容器”。在正常使用的集群中,以tty shell方式访问容器设置为true、重启策略设置为“从不”等都是高度可疑的。不太可能有这样的真实需求,可能暗示着攻击者尝试隐藏他们的踪迹。
最后,所有审计日志事件中都给出了源IP。任何从预期IP范围外发出的请求都应该被告警,尤其是那些公共IP范围的IP,这意味着API server可能被意外地暴露在互联网上了。
异常
我们曾问过自己一个问题:怎样才能知道一个pod是出于恶意的意图创建的?有多种方法可以将一个pod配置为能够进行某种形式的权限提升操作。然而,这些配置都有合法使用场景,只有当以一种偏离正常行为的方式使用时,它们才是可疑的。
为了对此建立检测机制,我们做了以下假设:由一个公司创建的pods有相似的配置模式,偏离这些模式的pods是可疑的,应该被进一步调查。
基于聚类的异常检测
我们使用“聚类”技术来实现前述检测目标。本文不会深入讨论聚类的原理,一些SIEM(如Splunk)已经内置了这样的功能,帮助我们只需花费很少精力就能进行相关处理。聚类涉及选取对象的多个特征(在我们的场景中就是pod的配置)、将具有相似特征的对象汇聚到同一个类蔟中。描述聚类以及我们如何寻找异常的最简单的方式就是将其可视化。
上图展示了一个对象集合,其中的对象按照两类特征被分为不同的类蔟。很容易可以发现其中有基于特征相似性划分的两大类蔟:蔟1和蔟2。对此,一个假设是这两个类蔟中的对象都是正常的。异常的是红色三角形部分,因为它的特征与其他对象不符,值得进一步调查。
特征提取
重要的是正确选择用于聚类的特征。我们希望识别在正常pod创建事件中保持一致的的特征(例如,创建pod的用户和容器镜像所属的仓库等),这些特征的改变意味着可疑行为。同时,我们也想要识别出能够导致某种形式的权限提升的设置项,这些设置项的改变也可能意味着恶意pod的创建。对此,我们咨询了内部容器化团队,下面就是一个被选中特征的列表,表中也包含了对应特征字段在审计日志中的JSON路径:
特征 | 描述信息 | 审计日志中的JSON路径 |
---|---|---|
镜像仓库 | 存储镜像的仓库,在使用了私有镜像仓库时有效 | requestObject.spec.container.image |
用户 | 发起请求的用户,在用户创建pod时有效 | user.username |
特权容器 | 一旦启用,特权容器可以访问宿主机上所有设备 | requestObject.spec.container.securityContext.privileged |
挂载卷 | 列出了容器可以访问的宿主机目录,挂载宿主机根目录则允许容器访问宿主机上所有文件 | requestObject.spec.container.volumeMounts.mountPaths |
宿主机网络命名空间 | 一旦启用,容器可以访问宿主机localhost上的任何服务 | requestObject.spec.container.HostNetwork |
宿主机IPC命名空间 | 一旦启用,容器可以共享IPC命名空间,能够与宿主机上进程进行进程间通信 | requestObject.spec.container.HostIPC |
宿主机PID命名空间 | 一旦启用,容器可以共享宿主机PID命名空间,能够列出宿主机上的进程 | requestObject.spec.container.HostPID |
结合起来
Splunk包含一个聚类(cluster)命令,完美适用于本场景。通过创建特征的单一字符串并用特定字符隔开,我们可以对所有pod创建事件进行聚类。执行该命令的具体时间周期取决于检测表现和查询(query)表现的平衡。回溯时间越久,出现假阳性的概率越小,但查询时间也会越长。经过试验和试错后,我们发现14天是一个很好的平衡点,但是在小的环境中也可以提高到30天。
一旦聚类命令开始执行,我们就可以寻找那些总数目相对较小的类蔟中的事件。这些事件与其他事件具有显著区别,我们将把它们标记为可疑,并进行进一步分析。
总结
来给本文画一个圆满的句号!Kubernetes的使用正在日益增长,因此针对Kubernetes的检测工程也成为一个重要的问题。Kubernetes审计日志能够帮助我们开展检测工程,因为它们是Kubernetes集群内非常好的事件源,在所有运行Kubernetes集群的平台上保持一致,这一点十分重要。一个优秀的检测工程策略应该有多个层次,包含特征、行为和异常等多种检测手段。就Kubernetes而言,我们想要把主要精力放在对权限滥用和Pod创建的检测上。对此有很多方法可以实现,其中一些正是本文所介绍的。