Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Kestra-cve-2026-53576 — End-to-end reproduction and cross-layer detection of CVE-2026-53576, the unauthenticated RCE in Kestra — taken past the base PoC to show how a common Docker-socket misconfiguration turns container-root into full host compromise. | Kitploit
Tools/GitHubGitHub/atlasvector/kestra-cve-2026-53576
Privilege EscalationContainer SecurityPersistence MechanismsVulnerability AnalysisExploitationPost-ExploitationPenetration TestingPapers & ResearchRed Teaming

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
Incident Response
Labs & Practice
GitHubatlasvector/kestra-cve-2026-53576

Kestra-cve-2026-53576

End-to-end reproduction and cross-layer detection of CVE-2026-53576, the unauthenticated RCE in Kestra — taken past the base PoC to show how a common Docker-socket misconfiguration turns container-root into full host compromise.

View Repository
2 days agoNot yet reviewed

Unauthenticated RCE in Kestra via a Trailing-Path Auth Bypass (CVE-2026-53576)

Contents

  • Executive Summary
  • 1. Summary
  • 2. Affected / Fixed Versions
  • 3. Test Environment & Pre-Flight Check
  • 4. Root Cause
  • 5. Attack Simulation
  • 6. Detection Engineering
  • 7. ATT&CK Mapping
  • 8. Remediation & Hardening
  • 9. References

Executive Summary

Kestra, an open-source workflow orchestration platform, shipped an authentication filter that decided whether a request needed credentials by checking if the URL ended with /configs - a check meant to expose one harmless public endpoint.

Because the check only looked at the tail of the string, any request whose URL happened to end that way skipped authentication entirely, including endpoints that create and run arbitrary code. The result: anyone who can reach a vulnerable Kestra instance over the network can run commands as root with zero credentials, no phishing, no password guessing, no privilege escalation step required.

In this exercise, that single gap was carried end-to-end against a self-hosted lab instance:

  • Unauthenticated code execution.
  • Discovery of an exposed Docker socket.
  • Escalation to full root access on the underlying host.
  • Two independent persistence mechanisms (an SSH backdoor key and a scheduled beacon).

Every stage was captured against defensive telemetry (network IDS, host runtime security, Linux audit, and system logs).

Business impact if unpatched: complete compromise of the host running Kestra, not just the application - and by extension anything else reachable from that host.

Fix: upgrade to Kestra 1.0.45 / 1.3.21 or later (patch alone closes the primary vulnerability; the escalation and persistence stages require a separate, independent fix - don't mount the Docker socket into application containers, see Section 8).

1. Summary

This report documents CVE-2026-53576, a CVSS 10.0 unauthenticated remote code execution vulnerability in Kestra ≤1.3.20, caused by a path-suffix authentication bypass (AuthenticationFilter.java, endsWith("/configs")).

An attacker who names a workflow's namespace and ID configs can create and execute arbitrary shell commands with no credentials, as root, inside the Kestra container. Fixed in 1.0.45 and 1.3.21. The identical bug was also reported independently under CVE-2026-49869, which is the CISA KEV-listed number tied to real in-the-wild exploitation. Both should be cited together, since public sources and scanners may reference either one.

2. Affected / Fixed Versions

VulnerableKestra ≤ 1.3.20 (and pre-1.0.45 on the 1.0.x line)
Fixed1.0.45 / 1.3.21+
CVECVE-2026-53576
Twin advisoryCVE-2026-49869 - same bug, CISA KEV-listed (in-the-wild)

Timeline

DateEvent
2026-06-02Kestra 1.3.21 released; changelog references a "potential authentication bypass in the authentication filter"
2026-06-03Kestra 1.0.45 released on the 1.0.x line
2026-09-02CVE-2026-49869 (the twin advisory) added to the CISA KEV catalog
2026-09-22 to 09-24

3. Test Environment & Pre-Flight Check

Self-hosted homelab: - Debian host (hostname docker, kernel 6.12.107+deb13-amd64), - Docker Engine 29.8.1. - Kestra deployed as the official kestra/kestra:v1.3.20 image, reachable through a Caddy reverse proxy at kestra.int.atlasvec.com. - Telemetry stack under test: Falco (runtime/eBPF, host-level), - Suricata 8.0.3 IDS (passive SPAN mirror), forwarding toward Splunk.

Before touching anything, I confirmed the target was actually vulnerable and the telemetry stack was live. I also took a snapshot of the pre-attack state so I could revert once I was done.

Kestra running the vulnerable version: 01-kestra-version-v1.3.20

The container itself, up and reachable: 06-docker-kestra-running

Falco's units loaded on the host: 02-falco-units-one-active

Falco actively emitting events (modern eBPF mode): 03-falco-modern-bpf-active-emitting

Suricata's service active: 04-suricata-systemctl-active

And its eve.json writing live events: 05-suricata-eve-json-live

4. Root Cause

This is two mistakes lining up, not one.

First, the authentication filter decides whether a request can skip auth by matching the end of the request path:

root@kitploit:~
// AuthenticationFilter.java — the vulnerable check, confirmed against the self-hosted v1.3.20 source
if (path.endsWith("/configs")) {
    // treated as a public, unauthenticated path
}

That check exists to expose a single harmless endpoint, the public instance config. Because endsWith only reads the tail of the string, it cannot tell that one safe path apart from any other path that happens to end the same way.

Second, Kestra's router still dispatches those look-alike paths to their real, sensitive handlers. /api/v1/main/flows/configs reaches the flow-create handler. /api/v1/main/executions/configs/configs reaches the execution handler. The filter waves both through as public, and the router runs them anyway. Name a flow's namespace and ID configs, and a code-executing endpoint now ends in /configs, so it inherits the public path's free pass.

The captured request pair shows it plainly. GET /api/v1/main/flows/search with no credentials returns 401. POST /api/v1/main/flows/configs with the same empty auth header returns 200 and stores the flow (see §5.1).

5. Attack Simulation

Every step below is captured with its own screenshot. All of it runs inside my own isolated homelab against a self-hosted Kestra instance behind the reverse proxy.

5.1 Stage 1 - auth bypass -> root inside the Kestra container

Control : GET /api/v1/main/flows/search with no credentials → 401 Unauthorized. Confirms auth is enforced on a normal route. 01-burp-control-401


Plant : Sending POST /api/v1/main/flows/configs with no auth header. The body is a flow YAML whose namespace and flow id are both configs, containing a Commands task that uses the Process task runner. The server returns 200 OK and stores the flow. The /configs suffix on an otherwise protected route is what triggers the bypass (see Section 4). 02-burp-plant-flow-200 Listen & Wait : Starting simple listener using nc for testing purposes, so we catch the reverse shell.

03-kali-listener-4444

Trigger / Execution POST /api/v1/main/executions/configs/configs, no auth header → 200 OK, new execution created. 04-burp-execute-200

BaaaaM : We've popped the shell, but remember - we're "isolated", root BUT inside the Kestra docker container, so we "shouldn't" be able to escape from here. "well, that's what I thought" 05-kali-revshell-container

The task's command runs as a direct child of the Kestra JVM process, confirmed via the host-side process tree, and completes successfully per Kestra's own execution log.

Kestra's Logs Dashboard 06-kestra-ui-pwn-log

Kestra's Process Tree 07-kestra-process-tree

Result: unauthenticated remote code execution as root, inside the Kestra container. This is the complete, self-contained proof of CVE-2026-53576.

5.2 Stage 2 - escalation via exposed Docker socket (homelab-specific, not part of the CVE itself)

From the Stage 1 shell, /var/run/docker.sock was found mounted into the container - a Docker-outside-of-Docker (DooD) pattern, common when Kestra's own Docker task runner is configured, since it needs a daemon to talk to.

Reaching that socket at all is equivalent to host root: the Docker API lets a client configure arbitrary host bind mounts and host PID/network namespaces for containers it spawns, and the daemon behind the socket already runs as root on the host.

This is not a kernel or container-escape exploit - it's the Docker API doing what it's designed to do, reached from somewhere it shouldn't have been reachable from.

Using the socket, I created a sibling container with the host filesystem bound in and host PID and network namespaces, then started it.

Note: a second, quieter variant also came up during testing, though I didn't screenshot it separately. Instead of always creating a new sibling container, the same socket access lets you run POST /containers/{id}/exec against a container that is already running, so there is no container-create event at all. On this Docker host I have both Kestra and Portainer, either of which I could use for the same escape. That matters for detection: any rule focused on a new privileged container being created misses this variant.

Confirming root and finding the socket sitting there, unmounted from any restriction: 08-docker-socket-present

Checking the Docker daemon is actually reachable through the socket: 09-docker-api-version

Listing what images are already local, so I don't need to pull anything: 10-docker-images-enum

First attempt: a privileged container with the host filesystem bound in: 11-docker-create-privileged

Second attempt, this time adding host PID and network namespaces: 12-docker-create-hostns

Third attempt, chrooting into the mounted host filesystem directly: 13-docker-create-chroot

Listener up and waiting on the port the escape shell will call back to: 14-host-listener-4491

Starting the container: 15-docker-container-start

Root, but this time it's the host, not the container: 16-host-root-id

Kernel version matching the real host, not a container's isolated view: 17-host-root-uname

Upgrading to a proper interactive shell for the rest of the session: 18-host-pty-upgrade

5.3 Stage 3 - persistence, confirmed firing

From the host-root shell I set up two independent persistence mechanisms,

First, an SSH key. I checked who already had access : 19-host-authkeys-enum

Then checked sshd's own config for anything that would block a new key: 20-host-sshd-config-enum

Generated a keypair on the attacker box: 21-attacker-keygen

Confirmed sshd was actually listening: 22-host-sshd-active

Dropped the public key into root's authorized_keys: 23-host-authkeys-implant

And logged in with the matching private key: 24-ssh-root-login-proof

PS: That's root access independent of the original RCE chain. Even if Kestra gets patched, this key still works.

Second, a cron beacon. I dropped a root-level callback set to run on an interval, then tested with a fresh listener to see if it actually fired on schedule: 25-host-cron-persist

It fired. A second foothold on the host, independent of both the RCE and the SSH key.

5.4 Verified kill-chain timeline.

The timeline below is different: it's reconstructed entirely from independent defender-side telemetry (network IDS + host runtime security), pulled and validated live against real Splunk data.

Primary RCE - network detects delivery, host confirms execution, 103 seconds apart:

Time (EDT)LayerEvent
02:46:09.778Network (Suricata)Public ET signature for this exact CVE fires
02:47:52.277Host (Falco)Reverse shell confirmed inside the Kestra container, exact command and container ID captured

Escalation - network and host each independently corroborate a different part of the same event:

Time (EDT)LayerEvent
03:48:55.503Host (Falco)Reverse shell from the escalation container
03:51:57.360Network (Suricata)

6. Detection Engineering

I built the detections against real captured telemetry, then tested each one against a replay. The matrix shows where coverage existed before I started and where it did not.

6.0 Detection coverage matrix

The gap is the docker-socket escalation. Falco's default rules catch the shell a compromised container spawns, but nothing in the stable default set flags a process reaching /var/run/docker.sock to drive the Docker API directly. The rules that would touch privileged-container launches, Launch Privileged Container and Launch Sensitive Mount Container, ship at incubating and sandbox maturity, so the default ruleset does not load them either. I closed the gap with an auditd correlation search (Detection 03) and a custom Falco rule (§6.3).

6.1 Splunk - five correlation searches, deployed and scheduled

All five run live in Splunk (*/5 * * * *, alert tracking enabled, per-field throttling), not just written and left as text. Full SPL, exact FP notes, severity, MITRE tags, and response runbooks for each are in detections/splunk/*.spl. The status table below is the live-tested result for each, including a real bug I found and fixed.

Detection 03's division of labor is deliberate: rather than re-implementing Suricata's own signature logic in SPL, or trying to have Falco catch a socket-level technique it wasn't built for, the SIEM layer here consumes auditd's EXECVE record of the exact curl --unix-socket calls - the telemetry source proven most reliable for this specific technique.

  1. Detection 01 - ET signature firing (suricata:eve alert):
root@kitploit:~
index=main sourcetype=suricata:eve event_type=alert
  alert.signature="ET WEB_SPECIFIC_APPS Kestra Unauthenticated Remote Code Execution (CVE-2026-53576)"
| stats count min(_time) as first_seen max(_time) as last_seen
        values(src_ip) as source_ips
  by dest_ip dest_port
| eval first_seen=strftime(first_seen,"%Y-%m-%d %H:%M:%S")
| eval last_seen=strftime(last_seen,"%Y-%m-%d %H:%M:%S")

det01-network-et-signature

  1. Detection 02 - Falco's redirect-to-network rule, broken out per container. The Source IP shifts from the Kestra container's bridge address (172.17.0.3, primary RCE on port 4489) to the host itself (172.66.66.67, escalation on port 4491) - the container-to-host escalation made visible - while the destination stays constant: the attacker's listener at 172.66.66.125: det02-falco-redirect-revshell

  2. Detection 03 - auditd EXECVE records of curl --unix-socket docker.sock: det03-auditd-docker-sock

  3. Detection 04 - journald root SSH login, key fingerprint captured: det04-ssh-root-login

  4. Detection 05 - the hidden-dotfile cron beacon (/usr/local/bin/.sysmon) running as root on an exact 5-minute cadence: 79 distinct intervals across ~6.5 hours (04:30-11:00 EDT). The distinct_intervals >= 3 threshold is what separates a periodic beacon from a one-off cron job: det05-cron-beacon

All five deployed as scheduled saved searches (*/5 * * * *): saved-searches-scheduled

Proof the cron scheduler actually runs these, not just that they exist: all five executed 54 times. Detection 03 fired once (the docker-socket abuse, 06:35 EDT) and Detection 05 fired 53 times (the still-active cron beacon). Detections 01/02/04 show 0 fires as those are one-time historical events (exploit request, RCE, SSH login) that have aged out of the -10m lookback, whereas a live or recurring instance would still alert: scheduler-triggered-alert

6.2 Sigma - portable host process-spawn rule

The /dev/tcp/ reverse-shell pattern - used in both the primary RCE and the escalation callback. SigmaHQ ships a rule for it: "Suspicious Reverse Shell Command Line" (id 738d9bcf-6999-4fdb-b4ac-3033037db8ab, by Florian Roth at Nextron Systems), and one of its keywords is bash -i >& /dev/tcp/ - exactly what showed up in our capture.

So I pulled that rule in unchanged (detections/sigma/lnx_shell_susp_rev_shells.yml) and checked its keyword against the same Falco event Detection 02 already captured. Sigma doesn't "fire" on its own - it's a portable signature, not a running engine - but I can show its keyword landing on real telemetry from this run instead of a textbook example.

Public SigmaHQ rule "Suspicious Reverse Shell Command Line" - its bash -i >& /dev/tcp/ keyword against the same real event as Detection 02:

sigma-devtcp-match-event

6.3 Falco - custom rule closing the docker-socket gap

detections/falco/docker_socket_abuse.yaml - deployed to the live Falco instance and confirmed firing on a real replay, 2026-09-24T10:27:29Z.

Falco's default ruleset doesn't cover this. Detecting a process reaching /var/run/docker.sock isn't a new idea - Sysdig's own Falco examples do it by watching for open_write on the socket path - but there's no such rule in the shipped/default set (the project has an open request for one, falcosecurity/falco #2940), and the one adjacent default rule only catches the docker/kubectl CLIs, not a raw curl --unix-socket.

The catch: that standard open_write-on-the-socket approach didn't fire in this setup - with modern_bpf and a passive mirror, the socket is reached via connect(), not open(). So I match on the process command line at spawn instead (spawned_process + proc.cmdline contains "docker.sock"), the same event class Falco already handles reliably here. It fires twice per call - the shell wrapper and the curl leaf - with full context:

root@kitploit:~
priority=Critical, container_id=d417e027d444, image=kestra/kestra:v1.3.20, user=root, cmdline="curl -s --unix-socket /var/run/docker.sock http://localhost/version", tags=[T1610, T1611]

Custom Falco rule firing - "Unexpected Process Accessing Docker Socket" (T1610/T1611): det17-falco-custom-docker-sock

Stock Falco rules that fired - the default set has no docker-socket rule, which is the gap: falco-rule-inventory

6.4 Suricata - existing public coverage, custom rule deferred

The network layer for the primary exploit is already covered by a public Emerging Threats signature, ET WEB_SPECIFIC_APPS Kestra Unauthenticated Remote Code Execution (CVE-2026-53576), which fired on real traffic during Phase 2.

Below is the same bypass seen at the network layer, and I built the query from the vulnerability pattern. Every request whose path ends in /configs on a flows or executions endpoint came back 200, while a normal route (/flows/search) returned the expected 401.

The Attacker IP (XFF) column is the real HTTP origin, read from the X-Forwarded-For header: 10.10.10.106, my Mac running Burp. Suricata's own src_ip only sees the reverse-proxy hop (172.66.66.1). That's a different machine from the 172.66.66.125 Kali box that later caught the reverse shells and made the SSH login, so the HTTP exploit and the callbacks came from separate hosts.

suricata-raw-post-http

6.5 Dashboard

cve_2026_53576_kill_chain is deployed to Splunk and contains: headline KPIs (detection layers, ATT&CK techniques, detections deployed, persistence mechanisms), an attack-timeline column chart coloured by telemetry layer, a MITRE ATT&CK kill-chain map with a live evidence count per stage, the network-and-host correlated timeline from §5.4, detection coverage by scheduled saved search, the docker-socket technique breakdown, and an indicators-of-compromise table pulled live from the logs.

  • KPIs, the attack-timeline chart, and the start of the ATT&CK map: dashboard-kill-chain-1

  • The rest of the ATT&CK map, the correlated kill-chain, detection coverage next to the docker-socket breakdown, and the IOC table: dashboard-kill-chain-2

7. ATT&CK Mapping

8. Remediation & Hardening

Primary fix. Upgrade to Kestra 1.0.45 (1.0.x line) or 1.3.21+ (1.3.x line). This alone closes the auth-bypass - Stage 1 of this exercise - and is the only fix that requires no further compensating controls once applied. See GHSA-2q47-568g-9h4f.

If immediate patching isn't possible, compensating controls, in order of impact:

  1. Reverse-proxy enforcement - since the vulnerable filter only fails on the raw path suffix, a reverse proxy in front of Kestra (Caddy, in this lab) can independently enforce auth on any path matching /api/v1/*/(flows|executions)/.* regardless of what it ends in, closing the bypass at a layer the application bug can't reach.

  2. Restrict the Docker socket mount - this closes Stages 2–3 entirely, independent of the Kestra patch. Don't bind-mount /var/run/docker.sock into the Kestra container. If the Docker task runner is genuinely required, use a scoped socket proxy (e.g. docker-socket-proxy) that allowlists specific API calls instead of granting full daemon access - full access to the socket is equivalent to host root, as demonstrated in §5.2.

  3. SSH hardening - the persistence chain's first mechanism depended on root being reachable via key-based SSH at all. PermitRootLogin no (or requiring a bastion/MFA for root) would have blocked that specific persistence path regardless of the initial RCE - worth doing independent of this CVE.

  4. Interim detection coverage - the Splunk correlation searches and the custom Falco rule, all deployed and verified during this exercise, provide the interim coverage for the stages of this specific chain while patching is scheduled.

Post-incident hygiene, if this pattern is found already exploited: treat as full host compromise, not just application compromise - rotate every credential and secret the Kestra instance had access to (KV store entries, connection credentials for downstream systems), not just Kestra's own access.

In-the-wild status. CVE-2026-49869, the twin advisory for this same bug, is listed in CISA's KEV catalog and tied to observed crypto-mining and cloud-credential-theft campaigns. CVE-2026-53576 (this report) has no KEV listing of its own, but it is the same vulnerability. Vulnerability management tooling that tracks only one of the two CVE numbers may under-report exposure.

9. References

  • Kestra Security Advisory - GHSA-2q47-568g-9h4f (CVE-2026-53576, this report's primary source)
  • Twin advisory - GHSA-5vc5-wxxq-3fjx (CVE-2026-49869, identical bug, CISA KEV-listed)
  • CVE.org - CVE-2026-53576, CVE-2026-49869
  • CISA Known Exploited Vulnerabilities Catalog - https://www.cisa.gov/known-exploited-vulnerabilities-catalog (CVE-2026-49869 entry, added 2026-09-02)
  • Fixed releases, both confirmed to exist and tagged - v1.3.21 (2026-06-02, changelog explicitly references "potential authentication bypass in the authentication filter"), v1.0.45 (2026-06-03)
  • Docker socket escalation technique (§5.2) - HackTricks: Docker Breakout / Privilege Escalation, Trail of Bits: Understanding Docker Container Escapes, MindPatch: Docker Escape
  • Sigma rule (§6.2) - the public rule I used rather than writing my own: SigmaHQ "Suspicious Reverse Shell Command Line" (id 738d9bcf-6999-4fdb-b4ac-3033037db8ab, Florian Roth / Nextron Systems), used under the Detection Rule License 1.1
  • Falco rule (§6.3) - the docker-socket gap is an open request upstream (falcosecurity/falco #2940); the custom rule follows the pattern in Sysdig's Docker + Falco examples
Download Tool
This lab reproduction, detection build, and cross-layer validation
TCP flow and a content-based alert - the literal string uid=0(root) was seen leaving the host in plaintext when id was run
StageNetwork (Suricata)Host (Falco)Host (auditd/journald)Status
Initial exploit requestPublic ET signature firesNo app-layer visibility-Covered (pre-existing)
RCE execution-Native rule, T1059-tagged-Covered (pre-existing)
Docker-socket escalation (the technique)-Gap: catches only the resulting shell, not the socket-abuse API callsEXECVE records capture the exact curl --unix-socket callsGap found, closed with auditd + a custom Falco rule
Escalation impact confirmationContent alert on the leaked id outputSame generic shell rule-Covered (pre-existing, cross-layer)
SSH persistence--journald: full PAM lifecycle plus key fingerprintCovered (host-only)
Cron persistence--journald and linux_audit, two sources, exact 5-min cadenceCovered (host-only)
#DetectionMITREStatus
01Network exploit signature (consumes the Suricata ET alert)T1190Fired - historical replay confirmed, no new events since (outside current lookback)
02Host RCE execution (Falco redirect-to-network rule)T1059Fired - historical replay confirmed
03Docker-socket abuse (auditd, the gap-closer)T1610, T1611Fired on the live scheduler itself - triggered_alert_count: 1 confirmed on the real */5 * * * * run, not just a manual test
04SSH root-login persistenceT1098.004, T1021.004Fired - historical replay confirmed
05Cron-beacon persistenceT1053.003Deployed with a bug I found and fixed live. The -10m dispatch window held only two of the three 5-minute buckets the distinct_intervals >= 3 threshold needs, so it could never fire despite the beacon still running in real data. Widened it to -30m and redeployed - and the scheduler has fired it 53 times since, so the fix holds.
TacticTechniqueEvidence
Initial AccessT1190 - Exploit Public-Facing ApplicationControl/plant/execute requests (§5.1); Suricata ET signature fire, §5.4
ExecutionT1059.004 - Command and Scripting Interpreter: Unix ShellKestra Commands task, host process tree (07-kestra-process-tree.png); Falco rule, §6.1 Detection 02
Command and ControlT1095 - Non-Application Layer ProtocolRaw /dev/tcp/ TCP reverse shell - no application-layer C2 framing used
DiscoveryT1613 - Container and Resource DiscoveryDocker image enumeration via the socket (10-docker-images-enum.png)
Privilege EscalationT1610 - Deploy ContainerSibling container create/start via the Docker API (11–15); auditd EXECVE records, §6.1 Detection 03
Privilege EscalationT1611 - Escape to HostHost-root confirmation, hostname/kernel match (16, 17)
PersistenceT1098.004 - Account Manipulation: SSH Authorized KeysKey implant to /root/.ssh/authorized_keys (23)
Lateral MovementT1021.004 - Remote Services: SSHSuccessful key-based root login (24); journald, §6.1 Detection 04
PersistenceT1053.003 - Scheduled Task/Job: CronCron beacon, confirmed firing on schedule (25); §6.1 Detection 05