Skip to content

fix(kubernetes-client-api): run kubeconfig exec args without shell - #7965

Open
GrosQuildu wants to merge 3 commits into
fabric8io:mainfrom
GrosQuildu:fix-kubeconfig-exec-args
Open

fix(kubernetes-client-api): run kubeconfig exec args without shell#7965
GrosQuildu wants to merge 3 commits into
fabric8io:mainfrom
GrosQuildu:fix-kubeconfig-exec-args

Conversation

@GrosQuildu

@GrosQuildu GrosQuildu commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Description

The kubernetes-client runs kubeconfig exec credential plugins through a shell. In getAuthenticatorCommandFromExecConfig, kubernetes-client prepends sh -c on Unix-like systems (Utils.getCommandPlatformPrefix), concatenates the configured command and all exec.args into one string, and gives that string to ProcessBuilder.

Kubeconfig exec.args are supposed to be process arguments, not shell syntax. The bug is that kubernetes-client evaluates those arguments as shell source code.

The custom shellQuote method is used to command prevent injections:

private static String shellQuote(String value) {
  if (value.contains(" ") || value.contains("\"") || value.contains("'")) {
    return "\"" + value.replace("\"", "\\\"") + "\"";
  }
  return value;
}

Two payload styles demonstrate the same root cause:

  1. No literal whitespace or quotes:

    ignored;touch${IFS}/tmp/fabric8-exec-owned
    

kubernetes-client quote helper leaves this unchanged. The shell treats ; as a command separator and expands ${IFS} to whitespace, so touch runs as a second command.

  1. Whitespace inside command substitution:

    $(touch /tmp/fabric8-exec-pwn)
    

kubernetes-client wraps this in double quotes because it contains a space, but double quotes still allow $() command substitution. The shell runs touch before the credential plugin receives the argument.

Exploit Scenario

A kubeconfig users[].user.exec with a trusted command and a malicious arg like below demonstrates the issue.

users:
- name: u
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1
      command: /usr/bin/printf
      args:
      - "$(touch /tmp/fabric8-exec-pwn)"

Threat Model

This is not a vulnerability when an attacker controls the whole kubeconfig, because kubeconfig exec plugins are executable configuration by design. The vulnerable case is a wrapper, operator, CI job, or policy system that fixes or allowlists the exec plugin command but lets a less-trusted caller influence arguments.

client-go Behavior Cross-Validation

The recommended fix matches the behavior of client-go, which is the exec-auth implementation used by kubectl.

client-go stores the kubeconfig command and args separately, then executes them with argv semantics:

  • plugin/pkg/client/auth/exec/exec.go: cmd: filepath.Clean(config.Command) and args: config.Args
  • plugin/pkg/client/auth/exec/exec.go: exec.Command(a.cmd, a.args...)

The kubeconfig API also describes exec.args as process arguments, not shell text: tools/clientcmd/api/types.go says Args are "Arguments to pass to the command when executing it." The generated Kubernetes kubeconfig reference uses the same wording for ExecConfig.args.

Local probes against client-go commit d04ac3067ff1 confirmed this behavior:

  • Arg payload ignored;touch${IFS}<marker> was passed literally to the exec plugin; the marker file was not created.
  • Arg payload $CLIENT_GO_ENV_PROBE was passed literally even when the environment contained CLIENT_GO_ENV_PROBE=expanded.

This is also consistent with Go's os/exec documentation: os/exec does not invoke a shell and does not perform shell-style expansions; environment expansion only happens if the caller explicitly uses os.ExpandEnv. No such expansion exists in the client-go exec-auth path.

With the proposed kubernetes-client patch, the same ignored;touch${IFS}<marker> probe no longer creates the marker file because ProcessBuilder receives the credential plugin command and each exec.args entry as separate argv elements.


Paweł Płatek from Trail of Bits in collaboration with OpenAI.

@GrosQuildu
GrosQuildu marked this pull request as ready for review June 26, 2026 15:28
@GrosQuildu
GrosQuildu marked this pull request as draft June 26, 2026 15:43
GrosQuildu added a commit to GrosQuildu/kubernetes-client that referenced this pull request Jun 26, 2026
@GrosQuildu
GrosQuildu marked this pull request as ready for review June 26, 2026 17:06
@GrosQuildu
GrosQuildu force-pushed the fix-kubeconfig-exec-args branch from d2462ac to 5c9d891 Compare June 26, 2026 17:14
@GrosQuildu
GrosQuildu force-pushed the fix-kubeconfig-exec-args branch from 5c9d891 to a189a42 Compare June 26, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant