之前入门学习了k8s集群环境的搭建和基本概念包括框架、网络模型,部署与动态扩展服务的方法:
回归安全的课题上来,云安全本质上是传统安全在云原生环境下的重新思考。作为业内事实标准,k8s可以提供服务管理、扩容等诸多功能,是提供云原生能力的基石。
因此后面计划首先学习云安全分支下的k8s安全,并同时学习docker中的常见安全问题。这篇先学习OWASP Kubernetes Top Ten的前五个。
K8S环境准备
仍然使用之前博客中搭建好的环境,一共三台CentOS 7的虚拟机,其IP和角色分别如下:
- 10.10.10.101(Master)
- 10.10.10.102(node-1)
- 10.10.10.103(node-2)
这里有一个小插曲,打开后发现我的kubelet服务没有起来,提示客户端证书过期了
用kubeadm
检查一下,还真是
解决方法:
1 | 备份旧证书 |
这时候查看kubelet
状态就恢复了:
However,这时候从节点还是g的:
这是因为这两节点的kubelet.conf
还没有更新,下面做更新操作
1 | 筛选Kube-scheduler的容器 |
然后就恢复了:
这个问题属于K8s证书过期的日常问题,这里我是手动解决的。查阅资料过程中发现也有其它自动化解决手段,什么自动证书轮转,这里暂时不继续探究。
Kubernetes goat环境搭建
安装k8s包管理器-Helm,类似apt和yum,用于向K8s中安装云原生应用
1
2使用国内的helm charts源
helm repo add appstore https://charts.grapps.cn克隆k8s-goat
1
2
3git clone https://github.com/madhuakula/kubernetes-goat.git
cd kubernetes-goat
bash setup-kubernetes-goat.sh
安装结果如下:
稍等一会儿后,可以看到当前活动的pods:
最后运行access-kubernetes-goat.sh
脚本即可完成环境搭建:
这里又出问题了,但是我自己环境的问题,这部分可以忽略。
执行后我并没有看到出现新的Pod,且端口不可访问,因为原脚本将输出重定向到了null。因此单独执行命令后发现如下问题,目前尚未解决,已发起issue提问:
考虑到这里只是想要转发一个端口,因此我自己重新写了一下这个脚本,换为expose
的方式:
1 | !/bin/bash |
上面红框中的home服务为入口:
OWASP Kubernets Top 10
K01: Insecure Workload Configurations
定义
OWASP中的定义说明如下:
Kubernetes manifests contain many different configurations that can affect the reliability, security, and scalability of a given workload. These configurations should be audited and remediated continuously.
其实类似于通常我们使用yaml
文件格式来部署deployment、service等资源,因此若这些文件中存在不安全的配置则会导致安全问题,其实感觉比较像OWASP
Top 10中的A05:2021-Security
Misconfiguration。
具体来说OWASP给出了如下例子:
应用不应运行在Root权限(Application processes should not run as root)
原因在于,如果这个容器被攻陷了,那么攻击者就拥有了root权限,可以拥有如新建进程的权限。下面是一个错误的示例文件:
1
2
3
4
5
6
7
8
9
10
11
12apiVersion: v1
kind: Pod
metadata:
name: root-user
spec:
containers:
...
securityContext:
#root user:
runAsUser: 0 # 这里将该Pod的用户权限设置为了root
#non-root user:
runAsUser: 5554应使用只读权限(Read-only filesystems should be used)
为了避免攻击者从失陷容器向主机写入文件,应该使用只读选项,如:
1
2
3
4
5
6
7
8
9
10apiVersion: v1
kind: Pod
metadata:
name: read-only-fs
spec:
containers:
securityContext:
#read-only fs explicitly defined
readOnlyRootFilesystem: true # 打开只读禁止使用提权容器(Privileged containers should be disallowed)
当容器用户为root且容器自身为提权容器会导致攻击者可以直接访问主机,若非root则会受到一定限制。因此要避免设置为提权容器。错误示例:
1
2
3
4
5
6
7
8
9
10
11
12apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
...
securityContext:
#priviliged
privileged: true
#non-privileged
privileged: false
对应k8s-goat场景:DIND (docker-in-docker) exploitation
下图为官方的攻击场景示意图,初步判断该攻击是利用主机上的docker.sock进行的。
该攻击方法从战术上属于
Initial Access
,技术上属于Application Vulnerbility
。
下面正式分析,首先访问对应端口:
这个应用对应的是health-check
,根据场景介绍,我们的目标是逃出这个容器并访问所在主机。上帝视角先看一下这个pod的情况:
位于我集群中第二个节点上。
预备知识:关于docker.sock
docker.sock
是一个socket文件,我们平常新建容器等用到的docker命令本质上是一个客户端,通过向docker
daemon发出请求以管理容器。如下图:
而docker daemon监听的方式也有两种:
- UNIX 域套接字
- TCP 端口监听
前者会在本地创建一个/var/run/docker.socket
,仅用于本机上不同进程之间的网络通信。而后者则支持其他机器的操作。默认情况下,docker使用的是UNIX域套接字,我们可以使用docker
-H参数来进行指定,如:
这两条命令结果是一样的,因为默认就是走的本地的UNIX域套接字。当然也可以走TCP,这里访问一下我屋里orangepi上的docker:
首先使用dockerd -H tcp://0.0.0.0:2375
打开一个在2375端口上tcp监听的docker
daemon
可以看到输出也提示了这样的做法是不安全的,然后我们远程连接:
证明了我们拥有了管理远程机器docker容器的能力。
利用
看一下页面,提供了一个ping的功能:
Web人说话了,命令注入白?
还真是,还是root妈诶。那下面我们就得考虑怎么从这个容器出去了,站在上帝视角上,我们再看看这个pods的详情:
在详情中我们看到了该容器挂载了所在主机的/var/run/docker.sock
。在前述预备知识下,这里就知道了要利用docker.sock的作用以及docker -H
。
回到网页,我们先用mount
命令查看挂载内容判断是否存在docker.sock
。
可以看到UNIX域套接字的socket,但是当前容器中没有安装docker客户端:
Ok点题了,所以叫DIND,我们得给这个容器里下一个客户端了。官方提供了下载列表供我们直接使用:https://download.docker.com/linux/static/stable/x86_64/
。因此分别执行命令:
1 | 下载客户端 |
然后我们就可以使用那个挂载的socket了:
到这里结束,我们就获得了运行该容器的主机上的所有运行容器,完成了一定的信息收集。并可根据此进一步去访问其他容器资源。
问题原因
从该deployment的配置文件中就可以看到,能出现这个问题本质原因就在于该容器是提权了的。此外也没有对主机文件访问做可读限制,导致了我们可以下载一个docker客户端,从而完成DIND攻击。
参考学习
- ⎈ DIND (docker-in-docker) exploitation | Kubernetes Goat (madhuakula.com)
- K01: Insecure Workload Configurations | OWASP Foundation
- Docker系列-docker.sock探究 - 狮子挽歌丿 - 博客园 (cnblogs.com)
K02: Supply Chain Vulnerabilities
定义
这个之前有听过类似的名词:镜像投毒,这个攻击也是类似的。由于容器由大量的第三方组件组成,因此这些组件若有问题,那么这个容器就存在失陷的风险。
官方给了三个类型:
镜像完整性(Image Integrity):
Software provenance has recently attracted significant attention in the media due to events such as the Solarwinds breach and a variety of tainted third-party packages. These supply chain risks can surface in various states of the container build cycle as well as at runtime inside of Kubernetes. When systems of record do not exist regarding the contents of a container image it is possible that an unexpected container may run in a cluster.
即源镜像被污染了,比如上面提到的例子,npm包中被插入了恶意代码。
镜像组成(Image Composition):
A container image consists of layers, each of which can present security implications. A properly constructed container image not only reduces attack surface, but can also increase deployment efficiency. Images with extraneous software can be leveraged to elevate privileges or exploit known vulnerabilities.
这里感觉更多说的镜像由多个不同的部分组成,比如os、软件等,组成越复杂,其攻击面、可利用的可能性越大。因此我们要尽量精简化,如使用最小化的OS,目的是降低攻击面。
已知软件漏洞(Known Software Vulnerabilities):
Due to their extensive use of third-party packages, many container images are inherently dangerous to pull into a trusted environment and run. For example, if a given layer in an image contains a version of OpenSSL that is susceptible to a known exploit it may be propagated to several workloads and unknowingly put an entire cluster at risk.
即镜像中使用了有漏洞的第三方包,比如OpenSSL有问题等。已经有了一些扫描容器镜像的工具如Clair and trivy
对应k8s-goat场景:Attacking private registry
下图为官方的攻击场景示意图,初步判断该攻击是对该容器所依赖的上游镜像进行了篡改。
该攻击方法从战术上属于
Initial Access
,技术上属于Compromised image in registry
。
下面正式分析,首先访问对应端口:
/image-20230918222854330.png)
这个应用对应的是poor-registry
,根据场景介绍,我们的目标是寻找一个k8s-goat-
开头的flag。
预备知识:关于docker registry
docker registry是用来存放docker镜像的地方,有公开的,也有私有的。公开的如Docker Hub,私人的比如阿里云控制台可以管理的个人实例。还是那个图,最右侧就是docker registry服务:
对于私有的镜像,我们也可以自己搭建一个私有的registry服务,通常运行在5000端口上。搭建完成后我们就可以通过pull
、push
等命令管理该registry上的私有镜像。
此外,该服务提供了docekr服务端提供的RESTful风格的接口,使用方法官方文档:HTTP API V2 | Docker Docs。
利用
打开页面,我们访问/v2
,返回200则说明是支持v2版本的API的:
我们可以利用API接口列出所有镜像:
我们分别读取这两个镜像的manifest
文件以收集信息:
没什么特别的信息,查看另一个:
可以看到有API_KEY
信息,即题目的flag。
问题原因
由于该私有镜像源是开放的,因而我们可以列举已有镜像的信息,从而获得一些隐私信息,如配置过程中的KEY、软件版本等等。此外,我们也可以上传一个同名的镜像以实现供应链攻击。因此我们也要保护私有镜像源的安全性,防止供应链攻击。
参考学习
- ⎈ Attacking private registry | Kubernetes Goat (madhuakula.com)
- HTTP API V2 | Docker Docs
- K02: Supply Chain Vulnerabilities | OWASP Foundation
K03: Overly Permissive RBAC
定义
其实就是过度授权,老生常谈的问题只不过是新的场景——K8S中的RBAC(Role-Based Access Control )。K8S将资源(Pod、Service、Nodes等)和动作(get、create、delete等)绑定起来,并划分得到不同的角色身份,以此来控制权限。
Configuring RBAC with least privilege enforcement is a challenge for reasons we will explore below.
那这件事困难吗?从OWASP给的解释来看,我认为困难点在于资源数量的庞大因而导致了配置的困难:
RBAC is an extremely powerful security enforcement mechanism in Kubernetes when appropriately configured but can quickly become a massive risk to the cluster and increase the blast radius in the event of a compromise.
为了了解这部分内容,首先需要了解K8S中的RBAC。K8S的RBAC定义了四种对象:
- Role
- ClusterRole
- RoleBinding
- ClusterRoleBinding
其中,前两个xxxRole
包含了“允许”执行的操作内容(可以理解为“操作白名单”)。区别从名字大概也能判断出来:
Role
必须定义在一个Namespace
中,而ClusterRole
则在整个集群中通用,因而不属于任何Namespace
。ClusterRole
可以用于定义对Namespace资源(如Pods)、集群范围资源(如Node)、endpoint(如HTTP API接口)的权限,并授予其他单个命名空间和跨命名空间的访问权限;而Role
则只作用于单个Namespace
。
如下是官方给的Role
和ClusterRole
例子:
1 | apiVersion: rbac.authorization.k8s.io/v1 |
1 | apiVersion: rbac.authorization.k8s.io/v1 |
有了两个身份后,我们还有RoleBinding
和ClusterRoleBinding
可以基于前述两个身份对象,绑定到一个或多个用户,这样他们就拥有了对应的权限。二者区别在于前者用于授权一个Namespace
范畴的权限,而后者可以授权整个集群范围内的。
官方也给出了例子:
1 | apiVersion: rbac.authorization.k8s.io/v1 |
1 | apiVersion: rbac.authorization.k8s.io/v1 |
回到问题本身。具体地,官方也给出了三个错误的案例:
不必要的最高权限
cluster-admin
(Unnecessary use of cluster-admin)如下,将默认的最高权限用户
cluster-admin
的ClusterRole
身份以ClusterRoleBinding
的方式授权给了默认命名空间内的所有资源。假设一个default空间内的pod被攻陷了,那么将直接拥有集群最高的权限。1
2
3
4
5
6
7
8
9
10
11
12apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: redacted-rbac
subjects:
- kind: ServiceAccount
name: default
namespace: default
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io不必要的
LIST
权限(Unnecessary use of LIST permission)官方在官网中给了一段场景示例,关键部分:
1
2
3
4
5
6kubectl create serviceaccount only-list-secrets-sa
kubectl create role only-list-secrets-role --verb=list --resource=secrets
kubectl create rolebinding only-list-secrets-default-ns \
--role=only-list-secrets-role --serviceaccount=default:only-list-secrets-sa
创建一个秘密
kubectl create secret generic abc --from-literal=secretAuthToken=verySecure123首先创建了一个服务账户
only-list-secrets-sa
并创建了包含LIST
权限的only-list-secrets-role
,然后创建了名为noly-olist-secrest-default-ns
的rolebingding
将only-list-secrets-role
授予了默认命名空间内的服务账户only-list-secrets-sa
。由于该服务账户没有
GET
权限,因此可以验证无法读取秘密:然而由于
LIST
权限的存在,使得可以列出所有的信息:由于创建
Role
时候增加了list
权限,从而导致攻击者可以利用该权限列出对应命名空间内所有数据:Accounts with
LIST
permission cannot get a specific item from the API, but will get all of them in full when they list.不必要的
WATCH
权限(Unnecessary use of WATCH permission)和
LIST
类似,WATCH
也可以查看所有数据:kubectl create role only-watch-secrets-role --verb=watch --resource=secrets
对应k8s-goat场景:RBAC least privileges misconfiguration
该攻击方法从战术上属于
Credential Access
,技术上包含List K8S secrets
和Container service account
。
下面正式分析,首先访问对应端口:
是一个网页版的shell,对应命名空间big-monolith
下的应用hunger-check
。根据场景介绍,我们的目标是获取k8s_goat_flag
,其位于k8svaultapikey
中。
预备知识
对于k8s环境中存在的pod,我们怎么从pod内部使用到k8s的API呢?官方提供了四种方式。
使用官方的k8s客户端:类似DIND,可以下载使用官方的k8s客户端。
直接访问RESTful API:
环境变量中存在
KUBERNETES_SERVICE_HOST
和KUBERNETES_SERVICE_PORT_HTTPS
。且k8s也在集群中部署了名为kubernetes
的服务,提供kubernetes.default.svc
到API服务器的域名解析服务。官方推荐使用
ServiceAccount
来认证使用API,如:1
2
3
4
5
6
7
8
9
10
11
12
13
14# 定义ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
...
# 使用ServiceAccont定义pod
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: build-robot若以这种方式,则k8s会在pod中创建:
/var/run/secrets/kubernetes.io/serviceaccount/token
:认证token/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
:证书/var/run/secrets/kubernetes.io/serviceaccount/namespace
:使用API时默认的namespace
这种情况下,pod内容器就可以使用配置文件定义好的
ServiceAccount
权限去访问kubernetes.default.svc
了。
使用kubectl proxy:使用该命令开启一个sidecar模式的pod,kubectl proxy在pod内开启一个监听端口并负责与API服务器进行认证,因而该pod内的所有容器就可以通过该端口访问API服务了
直接传递认证token:和第二种有点类似,只不过我们是手动传递的token,一个简单脚本如下:
1
2
3
4
5
6
7
8
9
10
11
12设置API服务器的域名,域名解析会由kubernets服务执行
APISERVER=https://kubernetes.default.svc
设置ServiceAccount文件的路径
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
获取Pod的namespace、token、证书
NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
TOKEN=$(cat ${SERVICEACCOUNT}/token)
CACERT=${SERVICEACCOUNT}/ca.crt
访问api
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api
利用
那先找k8s api的服务器地址嘛,不然怎么用API。从环境变量中可以看到:
好,可以用,但是我们怎么知道这个pod对应的服务账号呢?这里选用前述预备知识中直接传递认证token的方式。进入/var/run/
目录可以看到一个secrets
目录,其下就是token、证书和默认namespace了:
然后我们基于这三个内容以及前面环境变量中看到的地址来访问API:
可以看到访问成功,curl --cacert ${CACRT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api/v1
查看有哪些资源:
可以找到一个叫secrets
的资源,尝试访问curl --cacert ${CACRT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api/v1/secrets
:
不能直接访问,但发现该资源拥有list和watch权限,那么就想到了利用list或watch的方法来读取全部信息,这里使用list。在v1后加上命名空间的路径,即v1/namespace/${NAMESPACE}/secrets:
可以看到,我们虽然没有权限访问secrets下的某个条目,但是我们可以利用LIST直接把所有信息都列举出来,最后找到题目所需关键字并解码:
更多k8s RESTful API的使用:Kubernetes API | Kubernetes
问题原因
去看一下配置文件:
参考学习
- ⎈ RBAC least privileges misconfiguration | Kubernetes Goat (madhuakula.com)
- K03: Overly Permissive RBAC | OWASP Foundation
- Using RBAC Authorization | Kubernetes
- Role Based Access Control Good Practices | Kubernetes
- Accessing the Kubernetes API from a Pod | Kubernetes
- Kubernetes API | Kubernetes
K04: Lack of Centralized Policy Enforcement
定义
由于安全策略的管理位于一个中心实体,因而难以在资源数量(k8s集群、云服务器)过于庞大的情况下、分发和强制性执行安全策略。
针对上述问题,k8s策略强制实施机制(Kubernetes policy enforcement)可以通过少量的配置就实现在多个集群、多个云的基础设施上实现:管理(governance)、实施(compliance)和安全配置(security requireents)。
如下是一个来自gatekeeper
的拒绝来自不可信的镜像源的镜像的配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# Allowed repos
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: allowed-repos
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- "sbx"
- "prd"
parameters:
repos: # 只允许来自如下两个镜像源的镜像
- "open-policy-agent"
- "ubuntu"
此外,这类配置资源方案(其他的如Kyverno、Kubewarden等)还可以确保已经发生错误配置的pod不会被集群继续调度产生。比如Deployment。
对应k8s-goat场景:Securing Kubernetes Clusters using Kyverno Policy Engine
下图为官方的攻击场景示意图,初步判断该通过名为Kyverno
的策略引擎抵挡了攻击者对valut
命名空间内的pod的命令执行请求。
预备知识
首先需要了解什么是k8s中的准入控制器——dynamic admission controller:
An admission controller is a piece of code that intercepts requests to the Kubernetes API server prior to persistence of the object, but after the request is authenticated and authorized.——Admission Controllers Reference | Kubernetes
等于是一个拦截请求并对其进行认证的拦截器,可以拦截的请求动作包括:
- create、delete、modify或其他自定义的动词(如通过kube proxy连接pod)
但也包括一些不能够拦截的动作:
- get、watch、list
其具体的动作包括:
- validating:并行调用所有的验证的webhook,只要有一个不通过就不通过
- mutating:串行调用所有修改的webhook,挨个修改该请求
- both:混合前两者
关于Kyverno(希腊语,意味
Govern
) —— Kyverno is a policy engine designed for Kubernetes.Github: kyverno/kyverno: Kubernetes Native Policy Management (github.com)
而Kyverno可以作为k8s集群的动态准入控制器(dynamic admission controller),可以通过Helm包管理器进行安装:
1 | 添加chart源 |
安装结果:
安装结束后,就可以通过编写yaml
的格式来定义策略了。官方有详尽的指南:Policies and Rules |
Kyverno。Kyverno可以定义集群范围的策略ClusterPolicy
或指定命名空间内的策略Policy
,而具体的结构可以用下面的一张图来概括:
使用
这里主要学习如何编写Kyverno策略配置文件,禁止用户使用exec命令进入指定容器。
kubectl apply -f https://raw.githubusercontent.com/madhuakula/kubernetes-goat/master/scenarios/kyverno-namespace-exec-block/kyverno-block-pod-exec-by-namespace.yaml
文件内容如下:
1 | apiVersion: kyverno.io/v1 |
此时我们访问就会拒绝了:
此外,Kyverno官方也提供了很多这样的策略例子供我们选择,Policies | Kyverno
参考学习
- ⎈ Securing Kubernetes Clusters using Kyverno Policy Engine | Kubernetes Goat (madhuakula.com)
- K04: Policy Enforcement | OWASP Foundation
- Policies | Kyverno
K05: Inadequate Logging and Monitoring
定义
这部分也是工具的学习。在k8s中,一些事件默认是不会出现在日志中或者被监控,包括:
- 认证失败、敏感资源访问、手动删除或修改k8s资源
- 运行中负载的日志
此外,没有开启日志记录、日志有没有被集中存储且是否可以实现防篡改、报警阈值设置是否合理。上述情况或问题都可能会导致攻击者的行径没有被记录下来,从而对溯源造成了困难。因此OWASP建议如下类型日志都应开启并合理配置:
- k8s审计日志:记录API调用
- k8s事件:记录资源状态的更改和错误
- 应用&容器日志:容器内部运行的应用程序自行记录应用程序的日志
- 操作系统日志:如
journalctl
命令 - 云提供商日志
- 网络日志
对应k8s-goat场景:Cilium Tetragon - eBPF-based Security Observability and Runtime Enforcement
使用
Tetragon是一个基于eBPF技术的运行时安全检测组件,具有如下特点:
- 实时性:Tetragon is a runtime security enforcement and observability tool. What this means is Tetragon applies policy and filtering directly in eBPF in the kernel.
- 灵活性:Tetragon can hook into any function in the Linux kernel and filter on its arguments, return value, associated metadata that Tetragon collects about processes (e.g., executable names), files, and other properties.
- 内核信息可读:Tetragon, through eBPF, has access to the Linux kernel state.
安装依然直接使用helm安装:
1 | 添加软件源 |
安装结果:
具体Tetragon的使用方法、原理这里不做赘述,官方文档:Overview | Tetragon (cilium.io)。下图为Tetragon架构示意图: