VYPR
High severity7.5NVD Advisory· Published Mar 27, 2026· Updated Apr 8, 2026

CVE-2026-32241

CVE-2026-32241

Description

Flannel is a network fabric for containers, designed for Kubernetes. The Flannel project includes an experimental Extension backend that allows users to easily prototype new backend types. In versions of Flannel prior to 0.28.2, this Extension backend is vulnerable to a command injection that allows an attacker who can set Kubernetes Node annotations to achieve root-level arbitrary command execution on every flannel node in the cluster. The Extension backend's SubnetAddCommand and SubnetRemoveCommand receive attacker-controlled data via stdin (from the flannel.alpha.coreos.com/backend-data Node annotation). The content of this annotation is unmarshalled and piped directly to a shell command without checks. Kubernetes clusters using Flannel with the Extension backend are affected by this vulnerability. Other backends such as vxlan and wireguard are unaffected. The vulnerability is fixed in version v0.28.2. As a workaround, use Flannel with another backend such as vxlan or wireguard.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/flannel-io/flannelGo
< 0.28.20.28.2

Affected products

1
  • cpe:2.3:a:flannel-io:flannel:*:*:*:*:*:kubernetes:*:*
    Range: <0.28.2

Patches

1
08bc9a4c990a

Don't use shell invocations in extensions backend.

https://github.com/flannel-io/flannelThomas FerrandizMar 9, 2026via ghsa
2 files changed · +38 4
  • pkg/backend/extension/extension.go+33 2 modified
    @@ -80,7 +80,8 @@ func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGr
     
     	data := []byte{}
     	if len(n.preStartupCommand) > 0 {
    -		cmd_output, err := runCmd([]string{}, "", "sh", "-c", n.preStartupCommand)
    +		preArgs := strings.Fields(n.preStartupCommand)
    +		cmd_output, err := runCmd([]string{}, "", preArgs[0], preArgs[1:]...)
     		if err != nil {
     			return nil, fmt.Errorf("failed to run command: %s Err: %v Output: %s", n.preStartupCommand, err, cmd_output)
     		} else {
    @@ -121,13 +122,14 @@ func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGr
     	}
     
     	if len(n.postStartupCommand) > 0 {
    +		postArgs := strings.Fields(n.postStartupCommand)
     		cmd_output, err := runCmd([]string{
     			fmt.Sprintf("NETWORK=%s", config.Network),
     			fmt.Sprintf("SUBNET=%s", lease.Subnet),
     			fmt.Sprintf("IPV6SUBNET=%s", lease.IPv6Subnet),
     			fmt.Sprintf("PUBLIC_IP=%s", attrs.PublicIP),
     			fmt.Sprintf("PUBLIC_IPV6=%s", attrs.PublicIPv6)},
    -			"", "sh", "-c", n.postStartupCommand)
    +			"", postArgs[0], postArgs[1:]...)
     		if err != nil {
     			return nil, fmt.Errorf("failed to run command: %s Err: %v Output: %s", n.postStartupCommand, err, cmd_output)
     		} else {
    @@ -140,8 +142,37 @@ func (be *ExtensionBackend) RegisterNetwork(ctx context.Context, wg *sync.WaitGr
     	return n, nil
     }
     
    +// buildEnvMap merges os.Environ() with the provided env slice into a lookup map.
    +func buildEnvMap(env []string) map[string]string {
    +	m := make(map[string]string)
    +	for _, e := range os.Environ() {
    +		k, v, _ := strings.Cut(e, "=")
    +		m[k] = v
    +	}
    +	for _, e := range env {
    +		k, v, _ := strings.Cut(e, "=")
    +		m[k] = v
    +	}
    +	return m
    +}
    +
    +// expandVars expands $VAR / ${VAR} references in each string using the provided map.
    +// Because exec.Command is used (no shell), the expanded values are passed as literal
    +// arguments — shell metacharacters in variable values cannot cause injection.
    +func expandVars(envMap map[string]string, args []string) []string {
    +	expanded := make([]string, len(args))
    +	for i, a := range args {
    +		expanded[i] = os.Expand(a, func(key string) string { return envMap[key] })
    +	}
    +	return expanded
    +}
    +
     // Run a cmd, returning a combined stdout and stderr.
     func runCmd(env []string, stdin string, name string, arg ...string) (string, error) {
    +	envMap := buildEnvMap(env)
    +	expanded := expandVars(envMap, append([]string{name}, arg...))
    +	name, arg = expanded[0], expanded[1:]
    +
     	cmd := exec.Command(name, arg...)
     	cmd.Env = append(os.Environ(), env...)
     
    
  • pkg/backend/extension/extension_network.go+5 2 modified
    @@ -18,6 +18,7 @@ import (
     	"context"
     	"encoding/json"
     	"fmt"
    +	"strings"
     	"sync"
     
     	"github.com/flannel-io/flannel/pkg/backend"
    @@ -88,11 +89,12 @@ func (n *network) handleSubnetEvents(batch []lease.Event) {
     					}
     				}
     
    +				addArgs := strings.Fields(n.subnetAddCommand)
     				cmd_output, err := runCmd([]string{
     					fmt.Sprintf("SUBNET=%s", evt.Lease.Subnet),
     					fmt.Sprintf("PUBLIC_IP=%s", evt.Lease.Attrs.PublicIP)},
     					backendData,
    -					"sh", "-c", n.subnetAddCommand)
    +					addArgs[0], addArgs[1:]...)
     
     				if err != nil {
     					log.Errorf("failed to run command: %s Err: %v Output: %s", n.subnetAddCommand, err, cmd_output)
    @@ -118,11 +120,12 @@ func (n *network) handleSubnetEvents(batch []lease.Event) {
     						continue
     					}
     				}
    +				removeArgs := strings.Fields(n.subnetRemoveCommand)
     				cmd_output, err := runCmd([]string{
     					fmt.Sprintf("SUBNET=%s", evt.Lease.Subnet),
     					fmt.Sprintf("PUBLIC_IP=%s", evt.Lease.Attrs.PublicIP)},
     					backendData,
    -					"sh", "-c", n.subnetRemoveCommand)
    +					removeArgs[0], removeArgs[1:]...)
     
     				if err != nil {
     					log.Errorf("failed to run command: %s Err: %v Output: %s", n.subnetRemoveCommand, err, cmd_output)
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.