kubeadm may log bootstrap tokens before attempting to delete them

Created on 11 Sep 2020  ·  3Comments  ·  Source: kubernetes/kubeadm

What keywords did you search in kubeadm issues before filing this one?

token, error

Is this a BUG REPORT or FEATURE REQUEST?

_SECURITY_ REPORT

Summary:

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.

Kubernetes Version:

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.

Details:

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)
        },
    }

Impact:

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.

Additional information:

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

aresecurity kincleanup prioritbacklog

All 3 comments

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 format
  • tokens expire after 24 hours by default

i 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

Was this page helpful?
0 / 5 - 0 ratings