File size: 12,574 Bytes
55e2289
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
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 "</PACKETTRACER5>" not in xml_content and "<PACKETTRACER5>" in xml_content:
        xml_content += "</PACKETTRACER5>"
    elif "</PACKETTRACER5_ACTIVITY>" not in xml_content and "<PACKETTRACER5_ACTIVITY>" in xml_content:
        xml_content += "</PACKETTRACER5_ACTIVITY>"
        
    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