Hack.lu 2023 两道k8s题解

  ctf

0x00 前言

难得碰上能有k8s相关的题,正好也没有due要赶,因此记录一下题解,也得好好学学这中题目是如何部署的

 

0x01 Bat Kube

nc k8s.0xf.eu 8888 获得一个k8s的Account并保存在本地

 

首先看可用权限kubectl auth can-i --list  --kubeconfig config

基本上就是只能看,不能写的情况,在default 的namespace里搜索可用内容,看到secret,获取第一部分flag: flag{k8s_1s_ 同时也获得hint去namespaces看看

找到secret-namespaces,查看该底下权限

依旧是只能看不能改,看到pod有部署,查看之后看到在console端持续输出内容

查看pod的内容

根据hint通过api-resource查看可用CRD。kubectl api-resources  --kubeconfig config, 看到了flags的API,访问获得最后部分flag

 

0x02 Bat As Kube

还是先看auth

值得注意的是flagrequest可写,flag API可删,并且secret只能访问名字为docker-hub-login的对象。那么先看secret

一个docker register的账号ctf-player:f1_zZsJuSw3v6y7FXpnp,认证地址为git.k8s-ctf.de:1337。结合pod的description,下载镜像

docker login git.k8s-ctf.de:1337 && docker pull git.k8s-ctf.de:1337/root/hacklu:latest

这里直接用vesta将镜像文件导出到本地(因此暂时没有做到处镜像的功能,所以只能在scan image的时候在文件还没有被删除的时候强制停止,后期加上)。逻辑文件位于app/flag-operator.py

from kubernetes import client, config, watch
import os
import uuid
import json

def read_flag():
    flag = os.getenv("FLAG")
    return str(flag)

def check_flagrequest(obj, crds, group, version, flagprotector_plural):
    fp = crds.list_namespaced_custom_object(group, version, "flagprotector", flagprotector_plural)
    if len(fp["items"]) > 0:
        return False, "A Flagprotector is deployed somewhere in the cluster, you need to delete it first!"

    fr = json.loads(json.dumps(obj))

    if "metadata" not in fr.keys():
        return False, "Flagrequest: Missing metadata"

    if "labels" not in fr["metadata"].keys():
        return False, "Flagrequest: Missing labels"

    if "hack.lu/challenge-name" not in fr["metadata"]["labels"].keys():
        return False, "Flagrequest: Missing label hack.lu/challenge-name"

    if "give-flag" != fr["metadata"]["name"]:
        return False, "Flagrequest: I dont like the request name, it should be 'give-flag'"

    if "spec" not in fr.keys():
        return False, "Flagrequest: Missing spec"

    if "anti-bruteforce" not in fr["spec"].keys():
        return False, "Flagrequest: 'anti-bruteforce' is missing in the spec"

    if "Bi$wmX4PBTQLGe%AIKPO19$ussap4w" != fr["spec"]["anti-bruteforce"]:
        return False, "Flagrequest: Anti-bruteforce token invalid! You dont need to bruteforce! Im hiding something in the cluster, that will help you :D"

    return True, "Good Job!"

def main():
    # Define CRDs
    version = "v1"
    group = "ctf.fluxfingers.hack.lu"

    flagrequest_plural = "flagrequests"

    flagprotector_plural = "flagprotectors"

    flag_kind = "Flag"
    flag_plural = "flags"


    # Load CRDs
    crds = client.CustomObjectsApi()

    while True:
        print("Watching for flagrequests...")
        stream = watch.Watch().stream(crds.list_namespaced_custom_object, group, version, "default", flagrequest_plural)

        for event in stream:
            t = event["type"]
            flagrequest = event["object"]

            # Check if flagrequest was added
            if t == "ADDED":

                # Check if flagrequest is valid
                accepted, error = check_flagrequest(flagrequest, crds, group, version, flagprotector_plural)
                id = uuid.uuid4()
                if accepted:
                    print("Flagrequest accepted, creating flag...")
                    # Create flag
                    crds.create_namespaced_custom_object(group, version, "default", flag_plural, {
                        "apiVersion": group + "/" + version,
                        "kind": flag_kind,
                        "metadata": {
                            "name": "flag" + str(id)
                        },
                        "spec": {
                            "flag": read_flag(),
                            "error": str(error),
                        }
                    })
                else:
                    print("Flagrequest invalid")
                    # Create flag error
                    crds.create_namespaced_custom_object(group, version, "default", flag_plural, {
                        "apiVersion": group + "/" + version,
                        "kind": flag_kind,
                        "metadata": {
                            "name": "flag" + str(id)
                        },
                        "spec": {
                            "error": str(error),
                        }
                    })

if __name__ == "__main__":
    print("Starting operator...")
    try:
        config.incluster_config.load_incluster_config()
    except:
        print("Failed to load incluster config")
        exit(1)
    main()

逻辑为在创建flagrequest之后会检测内容,并且同时创建flag 类,如果满足check_flagrequest函数的所有条件,则会在flag类中给出flag。那么根据逻辑构造出yaml文件

apiVersion: ctf.fluxfingers.hack.lu/v1
kind: Flagrequest
metadata:
  name: give-flag
  namespace: default
  labels:
    hack.lu/challenge-name: give-flag
spec:
    anti-bruteforce: "Bi$wmX4PBTQLGe%AIKPO19$ussap4w"

但是创建之后get flag发现有flagprotector,需要删除他

 

Namspace有还有一个flagprotector,看可用权限之后,值得注意的是pod有执行的权限

再观察deploy的yaml文件,那么很明确了。利用/var/run/secrets/kubernetes.io/serviceaccount/token中的token去删除protector

获取token

kubectl exec flagprotector-controller-7599f788dc-nkfdv --kubeconfig config -n flagprotector -- cat //var/run/secrets/kubernetes.io/serviceaccount/token

查看该token在flagprotector的权限

 

kubectl auth can-i --list  --kubeconfig config -n flagprotector --token <token>

猜想验证,直接删除flagprotector

kubectl delete  flagprotector flag-protection  --kubeconfig config -n flagprotector --token <token>

重新创建flagrequest访问flag类获的flag

 

Vesta for overlayout extraction and qcow2 scanning will comming soon...