token
, error
_SECURITY_ REPORT
kubeadm
's delete
command takes as input either a bootstrap token ID, or a full token. Before determining whether the input is just an id or a full token, kubeadm
logs the input using klog
. If the deletion fails, the token would remain valid. An attacker who has access to the logs could use it to perform actions that require a bootstrap token, such as creating a cluster or joining nodes to an existing cluster.
The vulnerable code is present in kubernetes 1.19. The specific line that contains the call to klog
was last edited on 2019-03-24.
The vulnerable code is in the github.com/kubernetes
repository, in the file kubernetes/cmd/kubeadm/app/cmd/token.go
, at line 423. Here is the whole function:
// RunDeleteTokens removes a bootstrap tokens from the server.
func RunDeleteTokens(out io.Writer, client clientset.Interface, tokenIDsOrTokens []string) error {
for _, tokenIDOrToken := range tokenIDsOrTokens {
// Assume this is a token id and try to parse it
tokenID := tokenIDOrToken
klog.V(1).Infof("[token] parsing token %q", tokenIDOrToken) // POTENTIAL LEAK HERE
if !bootstraputil.IsValidBootstrapTokenID(tokenIDOrToken) {
// Okay, the full token with both id and secret was probably passed. Parse it and extract the ID only
bts, err := kubeadmapiv1beta2.NewBootstrapTokenString(tokenIDOrToken)
if err != nil {
return errors.Errorf("given token %q didn't match pattern %q or %q",
tokenIDOrToken, bootstrapapi.BootstrapTokenIDPattern, bootstrapapi.BootstrapTokenIDPattern)
}
tokenID = bts.ID
}
tokenSecretName := bootstraputil.BootstrapTokenSecretName(tokenID)
klog.V(1).Infof("[token] deleting token %q", tokenID)
if err := client.CoreV1().Secrets(metav1.NamespaceSystem).Delete(context.TODO(), tokenSecretName, metav1.DeleteOptions{}); err != nil {
return errors.Wrapf(err, "failed to delete bootstrap token %q", tokenID)
}
fmt.Fprintf(out, "bootstrap token %q deleted\n", tokenID)
}
return nil
}
And here's the definition of the kubeadm command that calls that function (in the same file):
deleteCmd := &cobra.Command{
Use: "delete [token-value] ...",
DisableFlagsInUseLine: true,
Short: "Delete bootstrap tokens on the server",
Long: dedent.Dedent(`
This command will delete a list of bootstrap tokens for you.
The [token-value] is the full Token of the form "[a-z0-9]{6}.[a-z0-9]{16}" or the
Token ID of the form "[a-z0-9]{6}" to delete.
`),
RunE: func(tokenCmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.Errorf("missing subcommand; 'token delete' is missing token of form %q", bootstrapapi.BootstrapTokenIDPattern)
}
kubeConfigFile = cmdutil.GetKubeConfigPath(kubeConfigFile)
client, err := getClientset(kubeConfigFile, dryRun)
if err != nil {
return err
}
return RunDeleteTokens(out, client, args)
},
}
An attacker who obtains a bootstrap token from the logs could use it to authenticate with kubeadm
and create a new cluster or join nodes to an existing cluster, e.g. to use computing resources. An attacker could also perform other actions using kubeadm
, e.g. listing or deleting other tokens.
I have reported this vulnerability to HackerOne and they have informed me that based on the high attack complexity and low severity, they think this can be reported and fixed publicly.
I have opened a PR on kubernetes implementing a fix: https://github.com/kubernetes/kubernetes/pull/94727
kubeadm's delete command takes as input either a bootstrap token ID, or a full token. Before determining whether the input is just an id or a full token, kubeadm logs the input using klog. If the deletion fails, the token would remain valid. An attacker who has access to the logs could use it to perform actions that require a bootstrap token, such as creating a cluster or joining nodes to an existing cluster.
hi, and thanks for logging the issue. to be able to read such logs one would need to have the right privileges and i'd assume the logs would be either under root access or given to a specific group that has higher access than bootstrap tokens already.
also:
--v=>1
has to be enabled during kubeadm token delete
execution if the token is in valid formati think the improvement in the PR is mostly fine, but i don't think we should backport to older releases (<1.20) due to the complexity of such an attack.
I agree with @neolit123 to not backport (unless there is really specific needs)
WRT to the fix, I'm +1 to remove the TokenID from the logs
closing as https://github.com/kubernetes/kubernetes/pull/94727 merged.
thanks