Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kubeadm may log bootstrap tokens before attempting to delete them #2287

Closed
mlevesquedion opened this issue Sep 11, 2020 · 3 comments
Closed
Labels
area/security kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. priority/backlog Higher priority than priority/awaiting-more-evidence.
Milestone

Comments

@mlevesquedion
Copy link

mlevesquedion commented Sep 11, 2020

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: kubernetes/kubernetes#94727

@neolit123
Copy link
Member

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.

@neolit123 neolit123 added area/security kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. priority/backlog Higher priority than priority/awaiting-more-evidence. labels Sep 14, 2020
@neolit123 neolit123 added this to the v1.20 milestone Sep 14, 2020
@fabriziopandini
Copy link
Member

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

@neolit123
Copy link
Member

closing as kubernetes/kubernetes#94727 merged.
thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/security kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. priority/backlog Higher priority than priority/awaiting-more-evidence.
Projects
None yet
Development

No branches or pull requests

3 participants