import xml.etree.ElementTree as ET import re import base64 from ciscoconfparse import CiscoConfParse from Decipher.cisco_pwd import decrypt_cisco_type7 def sanitize_xml_content(xml_bytes): """ Sanitizes XML content by properly handling problematic binary data in attributes instead of just deleting them. """ # Robust decoding xml_content = xml_bytes.decode('utf-8', errors='ignore') def encode_binary_attr(match): attr_name = match.group(1) attr_value = match.group(2) # Check if value contains non-XML friendly characters if any(ord(c) < 32 and c not in '\n\r\t' for c in attr_value): encoded = base64.b64encode(attr_value.encode('utf-8', errors='ignore')).decode('utf-8') return f'{attr_name}="base64:{encoded}"' return match.group(0) # Find attributes that might contain binary data (like VALUE="...") xml_content = re.sub(r'(\b\w+)="([^"]*)"', encode_binary_attr, xml_content) # Ensure root tag is closed if it's a snippet if "" not in xml_content and "" in xml_content: xml_content += "" elif "" not in xml_content and "" in xml_content: xml_content += "" return xml_content def extract_structured_data(xml_path): try: with open(xml_path, 'rb') as f: xml_bytes = f.read() xml_content = sanitize_xml_content(xml_bytes) root = ET.fromstring(xml_content) except Exception as e: print(f"[-] Error parsing {xml_path}: {e}") return None data = { "version": root.findtext(".//VERSION"), "devices": [], "physical_links": [], "wireless_connections": [], "vlans": {}, "instructions": "" } # 1. Extract Devices and their properties devices_by_id = {} ssids_servers = {} # ssid -> device_name for device in root.findall(".//DEVICE"): engine = device.find("ENGINE") if engine is None: continue name = engine.findtext("NAME") dev_type = engine.findtext("TYPE") model = engine.find("TYPE").get("model") if engine.find("TYPE") is not None else "" ref_id = engine.findtext("SAVE_REF_ID") dev_info = { "name": name, "type": dev_type, "model": model, "interfaces": [], "wireless_info": None, "vlans": [] } if ref_id: devices_by_id[ref_id] = name # Extract Interfaces & IP info for port in engine.findall(".//PORT"): port_name = port.findtext("PORT_NAME") or port.findtext("NAME") if not port_name: # Try to find in parent SLOT/MODULE parent_module = port.find("..") if parent_module is not None: port_type = port.findtext("TYPE") if port_type: port_name = port_type if port_name: ip = port.findtext("IP") subnet = port.findtext("SUBNET") ipv6 = port.findtext("IPV6_LINK_LOCAL") if ip or ipv6 or port.findtext("PORT_DHCP_ENABLE") == "true": dev_info["interfaces"].append({ "name": port_name, "ip": ip if ip else "DHCP" if port.findtext("PORT_DHCP_ENABLE") == "true" else None, "subnet": subnet, "ipv6": ipv6 }) # Extract Wireless Server info (Access Points) wireless_server = engine.find("WIRELESS_SERVER") if wireless_server is not None: common = wireless_server.find("WIRELESS_COMMON") if common is not None: ssid = common.findtext("SSID") dev_info["wireless_info"] = { "role": "server", "ssid": ssid, "encryption": common.findtext("ENCRYPT_TYPE"), "key": common.find(".//KEY").text if common.find(".//KEY") is not None else None } if ssid: ssids_servers[ssid] = name # Extract Wireless Client info (PC/Laptop/Phone) wireless_client = engine.find("WIRELESS_CLIENT") if wireless_client is not None: common = wireless_client.find("WIRELESS_COMMON") if common is not None: ssid = common.findtext("SSID") dev_info["wireless_info"] = { "role": "client", "ssid": ssid, "encryption": common.findtext("ENCRYPT_TYPE") } # Extract VLANs from device-specific storage (Switches) vlan_storage = engine.findall(".//VLANS/VLAN") for v in vlan_storage: v_num = v.get("number") v_name = v.get("name") if v_num and v_num not in data["vlans"]: data["vlans"][v_num] = v_name dev_info["vlans"].append({"id": v_num, "name": v_name}) # Parse RUNNINGCONFIG using CiscoConfParse for deeper analysis config = engine.findtext("RUNNINGCONFIG") if config: parse = CiscoConfParse(config.splitlines(), factory=True) # 1. Improved VLAN assignments for intf_obj in parse.find_objects(r'^interface'): intf_name = intf_obj.text.replace('interface ', '').strip() vlan_id = None # Check for access vlan access_vlan = intf_obj.re_search_children(r'switchport access vlan (\d+)') if access_vlan: vlan_id = access_vlan[0].re_match_typed(r'switchport access vlan (\d+)') if vlan_id: # Update or add interface info found = False for i in dev_info["interfaces"]: if i["name"] == intf_name: i["vlan"] = vlan_id found = True break if not found: dev_info["interfaces"].append({"name": intf_name, "vlan": vlan_id}) # 2. L3/L4 Support: Routing Protocols (Static, OSPF) dev_info["routing"] = { "static_routes": [], "ospf": [], "nat": [], "dhcp_pools": [] } # Static Routes static_routes = parse.find_objects(r'^ip route') for route in static_routes: dev_info["routing"]["static_routes"].append(route.text) # OSPF ospf_processes = parse.find_objects(r'^router ospf') for ospf in ospf_processes: ospf_info = {"process_id": ospf.text.split()[-1], "networks": []} for network in ospf.re_search_children(r'network'): ospf_info["networks"].append(network.text.strip()) dev_info["routing"]["ospf"].append(ospf_info) # NAT nat_rules = parse.find_objects(r'^ip nat') for rule in nat_rules: dev_info["routing"]["nat"].append(rule.text) # DHCP Pools dhcp_pools = parse.find_objects(r'^ip dhcp pool') for pool in dhcp_pools: pool_info = { "name": pool.text.replace('ip dhcp pool ', '').strip(), "details": [child.text.strip() for child in pool.children] } dev_info["routing"]["dhcp_pools"].append(pool_info) # 3. Security Audit & ACLs dev_info["security"] = { "acls": [], "vulnerabilities": [], "decrypted_passwords": [] } # Decrypt Type 7 passwords type7_pwds = re.findall(r'password 7 ([0-9A-Fa-f]+)|enable password 7 ([0-9A-Fa-f]+)', config) for pwd_match in type7_pwds: encrypted = pwd_match[0] or pwd_match[1] decrypted = decrypt_cisco_type7(encrypted) if decrypted: dev_info["security"]["decrypted_passwords"].append({ "encrypted": encrypted, "decrypted": decrypted, "type": "Cisco Type 7" }) dev_info["security"]["vulnerabilities"].append(f"Weak encryption found: Decrypted password '{decrypted}'") # Standard and Extended ACLs acls = parse.find_objects(r'^access-list|ip access-list') for acl in acls: acl_info = {"name": acl.text.strip(), "rules": []} if acl.children: acl_info["rules"] = [child.text.strip() for child in acl.children] dev_info["security"]["acls"].append(acl_info) # Security Audit: Check for plaintext passwords or weak services if parse.find_objects(r'^no service password-encryption'): dev_info["security"]["vulnerabilities"].append("Plaintext password storage enabled (no service password-encryption)") if parse.find_objects(r'^line vty'): vty_lines = parse.find_objects(r'^line vty') for vty in vty_lines: if not vty.re_search_children(r'transport input ssh'): dev_info["security"]["vulnerabilities"].append(f"Insecure remote access (Telnet allowed on {vty.text.strip()})") # 4. Server Services (if applicable) dev_info["services"] = [] services_node = engine.find("SERVICES") if services_node is not None: # HTTP Service http = services_node.find("HTTP") if http is not None and http.get("enabled") == "true": dev_info["services"].append({"type": "HTTP", "port": 80}) # DNS Service dns = services_node.find("DNS") if dns is not None and dns.get("enabled") == "true": dns_info = {"type": "DNS", "records": []} for record in dns.findall("RECORD"): dns_info["records"].append({ "name": record.get("name"), "type": record.get("type"), "value": record.get("value") }) dev_info["services"].append(dns_info) data["devices"].append(dev_info) # 2. Extract Physical Links for link in root.findall(".//LINK"): cable = link.find("CABLE") if cable is not None: ports = cable.findall("PORT") from_id = cable.findtext("FROM") to_id = cable.findtext("TO") link_info = { "type": link.findtext("TYPE"), "from_device": devices_by_id.get(from_id, from_id), "to_device": devices_by_id.get(to_id, to_id), } if len(ports) >= 2: link_info["from_port"] = ports[0].text link_info["to_port"] = ports[1].text data["physical_links"].append(link_info) # 3. Establish Wireless Connections for dev in data["devices"]: if dev["wireless_info"] and dev["wireless_info"]["role"] == "client": ssid = dev["wireless_info"]["ssid"] if ssid in ssids_servers: data["wireless_connections"].append({ "from_device": dev["name"], "to_device": ssids_servers[ssid], "ssid": ssid, "type": "wireless" }) # 4. Extract Instructions instr_page = root.find(".//INSTRUCTIONS/PAGE") if instr_page is not None: data["instructions"] = instr_page.text return data