Lucene search
+L

Linux Gather VLAN Profiles

🗓️ 10 Jul 2026 11:31:42Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

Linux Gather VLAN Profiles collects VLAN, trunk, and bridge configurations.

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

"""Gather VLAN, trunk, and bridge configuration from a Linux shell session."""

from __future__ import annotations

import json
import os
import re
from typing import Dict, List

from kittysploit import *
from lib.post.linux.system import System
from lib.post.linux.session import LinuxSessionMixin


class Module(Post, System, LinuxSessionMixin):
    __info__ = {
        "name": "Linux Gather VLAN Profiles",
        "description": (
            "Collect VLAN subinterfaces, trunk/bridge settings, and network manager "
            "profiles from a compromised Linux host to map virtual LAN segmentation "
            "and pivot opportunities."
        ),
        "platform": Platform.LINUX,
        "author": ["KittySploit Team"],
        "session_type": [
            SessionType.SHELL,
            SessionType.METERPRETER,
            SessionType.SSH,
        ],
        "references": [
            "https://attack.mitre.org/techniques/T1016/",
        ],
        "agent": {
            "risk": "passive",
            "effects": ["discovery"],
            "expected_requests": 4,
            "reversible": True,
            "approval_required": False,
            "produces": ["tech_hints", "risk_signals", "evidence"],
            "cost": 1.0,
            "noise": 0.2,
            "value": 1.2,
            "requires": {
                "min_endpoints": 0,
                "min_params": 0,
                "tech_hints_any": [],
                "tech_hints_all": [],
                "specializations_any": [],
                "risk_signals_any": [],
                "auth_session": False,
                "capabilities_any": ["shell"],
                "capabilities_all": [],
                "confidence_min": {},
                "confidence_min_any": {},
                "endpoint_pattern_any": [],
                "param_any": [],
                "api_surface_ready": False,
            },
            "chain": {
                "produces_capabilities": [{"capability": "vlan_segment", "from_detail": "vlan_id"}],
                "consumes_capabilities": [{"capability": "shell", "from_detail": ""}],
                "option_bindings": {},
                "suggested_followups": ["auxiliary/scanner/vlan/id_scan"],
            },
        },
    }

    save_loot = OptBool(True, "Save collected VLAN profile data under ./loot", required=False)

    _CONFIG_PATHS = (
        "/etc/network/interfaces",
        "/etc/netplan",
        "/etc/NetworkManager/system-connections",
        "/etc/sysconfig/network-scripts",
        "/etc/systemd/network",
    )

    def check(self):
        session_id_value = (
            self.session_id.value if hasattr(self.session_id, "value") else str(self.session_id)
        )
        if not session_id_value or not str(session_id_value).strip():
            print_error("Session ID not set")
            return False
        if not self.framework or not getattr(self.framework, "session_manager", None):
            print_error("Framework or session manager not available")
            return False
        session = self.framework.session_manager.get_session(str(session_id_value).strip())
        if not session:
            print_error(f"Session {session_id_value} not found")
            return False
        return True

    def run(self):

        if not self.linux_require_linux():
            return False

        print_status("Gathering Linux VLAN and trunk configuration...")
        report: Dict[str, object] = {
            "vlan_interfaces": [],
            "bridge_vlans": [],
            "proc_vlan_config": "",
            "ip_link_details": "",
            "config_files": {},
            "nmcli": "",
            "discovered_vlan_ids": [],
        }

        report["vlan_interfaces"] = self._collect_vlan_interfaces()
        report["bridge_vlans"] = self._collect_bridge_vlans()
        report["proc_vlan_config"] = self._read_text("/proc/net/vlan/config")
        report["ip_link_details"] = self._cmd("ip -d link show 2>/dev/null")
        report["config_files"] = self._collect_config_files()
        report["nmcli"] = self._collect_nmcli()
        report["discovered_vlan_ids"] = self._extract_vlan_ids(report)

        self._print_summary(report)

        if bool(self.save_loot):
            hostname = self._clean(self._cmd("hostname 2>/dev/null")) or "linux"
            safe_host = re.sub(r"[^a-zA-Z0-9._-]+", "_", hostname)
            out_rel = os.path.join("loot", f"vlan_profiles_{safe_host}.json")
            if self.write_out_dir(out_rel, json.dumps(report, indent=2)):
                print_success(f"Saved VLAN profile loot to {out_rel}")
            else:
                print_warning("Could not save loot output")

        # Gather modules succeed when collection completes — empty results are valid.
        return True

    def _cmd(self, command: str) -> str:
        output = self.linux_execute(command)
        return output if output else ""

    def _clean(self, text: str) -> str:
        if not text:
            return ""
        cleaned = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", str(text))
        lines = [line.strip() for line in cleaned.replace("\r", "\n").splitlines() if line.strip()]
        return lines[-1] if lines else ""

    def _read_text(self, path: str) -> str:
        if not self.file_exist(path):
            return ""
        try:
            content = self.read_file(path)
        except Exception:
            return ""
        return str(content or "")

    def _collect_vlan_interfaces(self) -> List[Dict[str, str]]:
        entries: List[Dict[str, str]] = []
        listing = self._cmd("ls -1 /sys/class/net 2>/dev/null")
        if not listing:
            return entries

        for iface in listing.splitlines():
            iface = iface.strip()
            if not iface:
                continue
            parent = self._read_text(f"/sys/class/net/{iface}/parent").strip()
            if not parent:
                continue
            vid = self._read_text(f"/sys/class/net/{iface}/vlanid").strip()
            flags = self._read_text(f"/sys/class/net/{iface}/flags").strip()
            addr = self._clean(self._cmd(f"cat /sys/class/net/{iface}/address 2>/dev/null"))
            entry = {
                "interface": iface,
                "parent": parent,
                "vlan_id": vid,
                "flags": flags,
                "mac": addr,
            }
            entries.append(entry)
            print_success(f"VLAN iface {iface} on {parent} (id={vid or '?'})")
        return entries

    def _collect_bridge_vlans(self) -> List[str]:
        lines: List[str] = []
        if not self.command_exists("bridge"):
            return lines
        output = self._cmd("bridge vlan show 2>/dev/null")
        if not output:
            return lines
        for line in output.splitlines():
            stripped = line.strip()
            if not stripped:
                continue
            lines.append(stripped)
            if "vlan" in stripped.lower():
                print_info(f"  {stripped}")
        return lines

    def _collect_config_files(self) -> Dict[str, str]:
        collected: Dict[str, str] = {}
        for path in self._CONFIG_PATHS:
            if self.file_exist(path):
                if path.endswith("netplan") or path.endswith("system-connections") or path.endswith("network-scripts") or path.endswith("network"):
                    listing = self._cmd(f'find "{path}" -maxdepth 2 -type f 2>/dev/null | head -40')
                    if listing:
                        for item in listing.splitlines():
                            item = item.strip()
                            if not item:
                                continue
                            content = self._read_text(item)
                            if content:
                                collected[item] = content[:8000]
                else:
                    content = self._read_text(path)
                    if content:
                        collected[path] = content[:8000]
                        print_info(f"Collected config: {path}")
        return collected

    def _collect_nmcli(self) -> str:
        if not self.command_exists("nmcli"):
            return ""
        output = self._cmd("nmcli -f NAME,UUID,TYPE,DEVICE connection show 2>/dev/null")
        if output:
            print_info("NetworkManager connections:")
            for line in output.splitlines()[:20]:
                if line.strip():
                    print_info(f"  {line.strip()}")
        detail = self._cmd("nmcli -t -f NAME,802-3-ethernet.cloned-mac-address,ipv4.method,connection.interface-name connection show 2>/dev/null")
        return (output + "\n" + detail).strip()

    def _extract_vlan_ids(self, report: Dict[str, object]) -> List[int]:
        ids: List[int] = []
        for entry in report.get("vlan_interfaces", []):
            raw = str(entry.get("vlan_id", "")).strip()
            if raw.isdigit():
                ids.append(int(raw))

        patterns = (
            r"\bvlan\s*(\d{1,4})\b",
            r"\bid\s*(\d{1,4})\b",
            r"\bVID:\s*(\d{1,4})\b",
            r"\b802-1q\s*(\d{1,4})\b",
        )
        blobs = [
            str(report.get("proc_vlan_config", "")),
            str(report.get("ip_link_details", "")),
            str(report.get("nmcli", "")),
        ]
        blobs.extend(str(content) for content in report.get("config_files", {}).values())

        for blob in blobs:
            for pattern in patterns:
                for match in re.finditer(pattern, blob, flags=re.IGNORECASE):
                    value = int(match.group(1))
                    if 1 <= value <= 4094:
                        ids.append(value)

        unique = sorted(set(ids))
        if unique:
            print_success(f"Discovered VLAN IDs: {', '.join(str(v) for v in unique)}")
        else:
            print_warning("No explicit VLAN IDs found on this host")
        return unique

    def _print_summary(self, report: Dict[str, object]) -> None:
        print_info("=" * 72)
        print_status("VLAN profile summary")
        vlan_ifaces = report.get("vlan_interfaces", [])
        print_info(f"VLAN subinterfaces: {len(vlan_ifaces)}")
        bridge_vlans = report.get("bridge_vlans", [])
        if bridge_vlans:
            print_info(f"Bridge VLAN entries: {len(bridge_vlans)}")
        config_files = report.get("config_files", {})
        if config_files:
            print_info(f"Config files collected: {len(config_files)}")
        proc_cfg = str(report.get("proc_vlan_config", "")).strip()
        if proc_cfg:
            print_info("Kernel VLAN config (/proc/net/vlan/config):")
            for line in proc_cfg.splitlines()[:20]:
                if line.strip():
                    print_info(f"  {line.strip()}")

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

10 Jul 2026 11:31Current
5.9Medium risk
Vulners AI Score5.9
1