diff --git a/README.md b/README.md index 02421f6..5f1caf4 100644 --- a/README.md +++ b/README.md @@ -1,187 +1,103 @@ -# Vex: RouterOS Security Inspector +# Sara: RouterOS Security Inspector - -Autonomous RouterOS configuration analyzer to find security issues. No networking required, only read configurations. - -![](/banner/banner.png) +It is a autonomous RouterOS configuration analyzer for finding security issues on MikroTik hardware. ``` -Vex: RouterOS Security Inspector -Designed for security engineers + _____ + / ____| + | (___ __ _ _ __ __ _ + \___ \ / _` | '__/ _` | + ____) | (_| | | | (_| | + |_____/ \__,_|_| \__,_| v1.0 -Author: Magama Bazarov, -Pseudonym: Caster -Version: 1.1 + RouterOS Security Inspector. Designed for Security Professionals + + Author: Magama Bazarov, ``` -# Disclaimer +# Mechanism -The tool is intended solely for analyzing the security of RouterOS hardware. The author is not responsible for any damage caused by using this tool +This tool is written in Python 3 and uses regular expressions to look for specific values in configurations to detect a problem. As of v1.0, the tool performs 20 security checks, including: -------------- -# Operating +1. **SMB Service Detection**: Identifies if the SMB service is enabled, which may expose the device to vulnerabilities like CVE-2018-7445; -It is written in Python 3 and its work is based on looking for certain elements in configurations that may indicate RouterOS network security issues. The search for suspicious elements is performed using regular expressions. +2. **RMI Services Analysis**: Examines active Remote Management Interface (RMI) services such as Telnet, FTP, SSH, and others. The tool warns about unsafe services and provides recommendations for securing them; -The tool performs 18 tests: - -``` -1. Displays information about RouterOS version, device model, serial number -2. Checks the settings of neighbor discovery protocols -3. Checks the status of the Bandwidth Server -4. Checks DNS & DDNS settings -5. Checking the UPnP status -6. Checking SSH status -7. Checking for SOCKS -8. Checking the status of ROMON -9. Check MAC Telnet Server -10. Check MAC Winbox Server -11. Check MAC Ping Server -12. Verifying VRRP authentication -13. Checking SNMP settings -14. OSPF Security check -15. Checking password requirements settings -16. Checking the PoE status -17. Checking SMB activity -18. Checking RMI interfaces -``` - -> Warning: For a complete RouterOS check, it is recommended to export the configuration using `export verbose` to unload the entire configuration - --------- +3. **UPnP Status Check**: Detects if Universal Plug and Play (UPnP) is enabled, which can open up the network to unauthorized access; +4. **WiFi Configuration Review**: Analyzes WiFi settings for vulnerabilities, including insecure authentication methods, enabled WPS, and PMKID exposure; +5. **DNS Configuration Review**: Checks DNS settings, looking for remote DNS requests being allowed and the absence of DNS over HTTPS (DoH); +6. **Dynamic DNS (DDNS) Status**: Identifies if DDNS is enabled, which might expose your network to unnecessary risks; +7. **Power over Ethernet (PoE) Settings Review**: Analyzes PoE configurations to ensure power management does not pose risks to connected devices; +8. **Protected RouterBOOT Check**: Ensures that Protected RouterBOOT is enabled, preventing unauthorized changes to the bootloader settings; +9. **SOCKS Proxy Detection**: Identifies if a SOCKS proxy is enabled, which could indicate a compromised device; +10. **Bandwidth Server Check**: Detects if the Bandwidth Server is enabled, which could lead to unwanted traffic on the network; +11. **OSPF Interface Analysis**: Examines OSPF interface settings for missing passive mode and authentication, both of which are crucial for securing OSPF communications; +12. **VRRP Interface Analysis**: Checks for VRRP interfaces that lack proper authentication, potentially exposing the network to Man-in-the-Middle (MITM) attacks; +13. **Discovery Protocols Configuration**: Reviews the settings for network discovery protocols, ensuring they are limited to trusted interfaces; +14. **User Password Policy Check**: Analyzes user password policies to ensure they meet security best practices; +15. **SSH Strong Crypto Detection**: Detects if SSH is configured with weak cryptography, providing advice on how to secure it; +16. **Connection Tracking Status**: Reviews the connection tracking settings, advising on when it might be beneficial to disable it; +17. **RoMON Status Check**: Detects if RoMON is enabled, highlighting the need for careful management to prevent unauthorized access to other RouterOS devices; +18. **MAC Server Settings Review**: Analyzes MAC Server and MAC Winbox settings, recommending restrictions to enhance security; +19. **SNMP Analysis**: Identifies the use of default or weak SNMP community strings, which could lead to information gathering attacks; +20. **Port Forwarding Rules Check**: Detects port forwarding rules (dst-nat), warning about potential exposure of internal services to the internet. # Usage -```bash -caster@kali:~$ sudo apt install git python3-colorama -caster@kali:~$ git clone https://github.com/casterbyte/Vex -caster@kali:~$ cd Vex/ -caster@kali:~/Vex$ sudo python3 setup.py install -caster@kali:~$ vex -``` - -``` -sage: vex.py [-h] --config CONFIG - -Vex: RouterOS Security Inspector - -options: - -h, --help show this help message and exit - --config CONFIG Path to the RouterOS configuration file -``` - -To perform a configuration analysis, you must supply the RouterOS configuration file as input. This is done with the `--config` argument: +To install Sara: ```bash -caster@kali:~$ vex --config routeros.conf +caster@kali:~$ sudo apt install python3-colorama git +caster@kali:~$ git clone https://github.com/casterbyte/Sara +caster@kali:~/Sara$ sudo python3 setup.py install +caster@kali:~$ sara + + _____ + / ____| + | (___ __ _ _ __ __ _ + \___ \ / _` | '__/ _` | + ____) | (_| | | | (_| | + |_____/ \__,_|_| \__,_| v1.0 + + RouterOS Security Inspector. Designed for Security Professionals + + Author: Magama Bazarov, + + It's recommended to provide a configuration file exported using the 'export verbose' command + +usage: sara [-h] --config-file CONFIG_FILE +sara: error: the following arguments are required: --config-file ``` -Here is an example of the analyzed config: +Sara uses just one argument, it is the name/path to the RouterOS configuration file: -``` -[*] Config Analyzing... ------------------------------- -[+] Device Information: -[*] Software ID: BGM1-F15F -[*] Model: C52iG-5HaxD2HaxD -[*] Serial Number: XGB15HBGP01 ------------------------------- -[+] Discovery Protocols: -[!] Warning: Discovery protocols are enabled on all interfaces -[*] Impact: Information Gathering ------------------------------- -[+] Bandwidth Server: -[!] Warning: Bandwidth Server is enabled -[*] Impact: Potential misuse for traffic analysis and network performance degradation ------------------------------- -[+] DNS Settings: -[!] Warning: Router is configured as a DNS server -[*] Impact: DNS Flood -[*] Recommendation: Consider closing this port from the internet to avoid unwanted traffic ------------------------------- -[+] DDNS Settings: -[!] Warning: Dynamic DNS is enabled -[*] Impact: Exposure to dynamic IP changes and potential unauthorized access ------------------------------- -[+] UPnP Settings: -[!] Warning: UPnP is enabled -[*] Impact: Potential unauthorized port forwarding and security risks ------------------------------- -[+] SSH Strong Crypto: -[!] Warning: SSH strong crypto is disabled (strong-crypto=no) -[*] Impact: Less secure SSH connections -[*] Recommendation: Enable strong crypto (strong-crypto=yes) for enhanced security. This will use stronger encryption, HMAC algorithms, larger DH primes, and disallow weaker ones ------------------------------- -[+] SOCKS Settings: -[!] Warning: SOCKS proxy is enabled -[*] Impact: Potential unauthorized access and misuse of network resources -[*] Recommendation: Disable SOCKS proxy or ensure it is properly secured. SOCKS can be used maliciously if RouterOS is compromised ------------------------------- -[+] ROMON Settings: -[!] Warning: ROMON is enabled -[*] Impact: ROMON can be a jump point to other MikroTik devices and should be monitored carefully -[*] Recommendation: Monitor ROMON activities and ensure proper security measures are in place ------------------------------- -[+] MAC Ping Server Settings: -[!] Warning: MAC Ping Server is enabled -[*] Impact: Possible unwanted traffic ------------------------------- -[+] VRRP Authentication Settings: -[!] Warning: VRRP interface 'vrrp1' has no authentication -[*] Impact: Potential unauthorized access and manipulation of VRRP settings -[*] Recommendation: Configure authentication for VRRP interfaces to prevent unauthorized access -[!] Warning: VRRP interface 'vrrp3' has no authentication -[*] Impact: Potential unauthorized access and manipulation of VRRP settings -[*] Recommendation: Configure authentication for VRRP interfaces to prevent unauthorized access ------------------------------- -[+] SNMP: -[!] Warning: SNMP community 'public' is in use -[*] Impact: Information Gathering -[*] Recommendation: Change the community name to something more secure -[!] Warning: SNMP community 'private' is in use -[*] Impact: Information Gathering -[*] Recommendation: Change the community name to something more secure ------------------------------- -[+] OSPF Interface Templates Check: -[!] Warning: OSPF interface 'home' is not set to passive -[!] Warning: OSPF interface 'home' has no authentication -[*] Impact: Potential unauthorized access and network disruption -[*] Recommendation: Configure authentication and passive mode for OSPF interfaces to enhance security -[!] Warning: OSPF interface 'ether1' is not set to passive -[!] Warning: OSPF interface 'ether1' has no authentication -[*] Impact: Potential unauthorized access and network disruption -[*] Recommendation: Configure authentication and passive mode for OSPF interfaces to enhance security -[!] Warning: OSPF interface 'ether3' is not set to passive -[!] Warning: OSPF interface 'ether3' has no authentication -[*] Impact: Potential unauthorized access and network disruption -[*] Recommendation: Configure authentication and passive mode for OSPF interfaces to enhance security ------------------------------- -[+] Password Strength Requirements: -[!] Warning: No minimum password complexity or length requirements -[*] Recommendation: Set minimum password complexity and length requirements to enhance security ------------------------------- -[+] PoE Settings: -[!] Warning: PoE is set to auto-on -[*] Impact: There is a risk of damaging connected devices by unexpectedly supplying power to the port -[*] Recommendation: Review and set PoE settings appropriately ------------------------------- -[+] RMI Interfaces Status: -[*] Telnet is enabled - Consider disabling for security reasons -[*] FTP is enabled - Consider disabling for security reasons -[*] WWW (HTTP) is enabled -[*] SSH is enabled -[*] WWW-SSL (HTTPS) is enabled -[*] API is enabled - Consider disabling for security reasons -[*] Winbox is enabled -[*] API-SSL is enabled - Consider disabling for security reasons -[!] Recommendation: Restrict access to RMI only from trusted subnets +```bash +caster@kali:~$ sara --config-file routeros.txt + + _____ + / ____| + | (___ __ _ _ __ __ _ + \___ \ / _` | '__/ _` | + ____) | (_| | | | (_| | + |_____/ \__,_|_| \__,_| v1.0 + + RouterOS Security Inspector. Designed for Security Professionals + + Author: Magama Bazarov, + + It's recommended to provide a configuration file exported using the 'export verbose' command + +[*] Analyzing the configuration file: /mnt/hgfs/Development/Sara/routeros.txt (36.38 KB) + +[+] Device Information + [*] RouterOS Version: X.XX.X + [*] Model: XXXX-XXXXXXXXXX + [*] Serial Number: XXXXXXXXXXX ``` # Outro -The tool is updated and maintained, suggestions: caster@exploit.org - - +Sara will be maintained and updated, suggestions: caster@exploit.org diff --git a/banner/banner.png b/banner/banner.png deleted file mode 100644 index 4257e7b..0000000 Binary files a/banner/banner.png and /dev/null differ diff --git a/sara.py b/sara.py new file mode 100644 index 0000000..063ab49 --- /dev/null +++ b/sara.py @@ -0,0 +1,424 @@ +#!/usr/bin/env python3 + +import argparse +import re +import colorama +from colorama import Fore, Style + +# Colorama +colorama.init(autoreset=True) + +def banner(): + banner_text = r""" + _____ + / ____| + | (___ __ _ _ __ __ _ + \___ \ / _` | '__/ _` | + ____) | (_| | | | (_| | + |_____/ \__,_|_| \__,_| v1.0 +""" + print(banner_text) + print(" " + Fore.YELLOW + "RouterOS Security Inspector. Designed for Security Professionals\n") + print(" " + Fore.YELLOW + "Author: " + Style.RESET_ALL + "Magama Bazarov, \n") + print(" " + "It's recommended to provide a configuration file exported using the 'export verbose' command") + print() + +def parse_arguments(): + parser = argparse.ArgumentParser() + parser.add_argument('--config-file', required=True, help='Path to the RouterOS configuration file') + return parser.parse_args() + +def extract_device_info(config_data): + version_pattern = r"#.*by RouterOS (\S+)" + model_pattern = r"# model = (\S+)" + serial_pattern = r"# serial number = (\S+)" + + version_match = re.search(version_pattern, config_data) + model_match = re.search(model_pattern, config_data) + serial_match = re.search(serial_pattern, config_data) + + if version_match and model_match and serial_match: + routeros_version = version_match.group(1) + model = model_match.group(1) + serial_number = serial_match.group(1) + + print(f"{Style.BRIGHT}[+] Device Information{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [*] RouterOS Version: {routeros_version}{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [*] Model: {model}{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [*] Serial Number: {serial_number}{Style.RESET_ALL}") + print() + +def check_smb_enabled(config_data): + smb_pattern = r"/ip smb\s+set.*enabled=yes" + match = re.search(smb_pattern, config_data) + if match: + print(f"{Style.BRIGHT}[+] Checking SMB{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: SMB service is enabled. Are you sure you want to do this? Also possible CVE-2018-7445{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Turn off SMB or if you need it, filter access to it{Style.RESET_ALL}") + print() + +def check_rmi_services(config_data): + rmi_services = { + 'telnet': r"set telnet address=.* disabled=(no|yes)", + 'ftp': r"set ftp address=.* disabled=(no|yes)", + 'www': r"set www address=.* disabled=(no|yes)", + 'ssh': r"set ssh address=.* disabled=(no|yes)", + 'www-ssl': r"set www-ssl address=.* disabled=(no|yes)", + 'api': r"set api address=.* disabled=(no|yes)", + 'winbox': r"set winbox address=.* disabled=(no|yes)", + 'api-ssl': r"set api-ssl address=.* disabled=(no|yes)", + } + + active_services = [] + unsafe_services = ['ftp', 'www', 'telnet'] + caution_services = ['www-ssl', 'winbox', 'ssh'] + + for service, pattern in rmi_services.items(): + match = re.search(pattern, config_data) + if match: + disabled_status = re.search(r"disabled=(no|yes)", match.group(0)).group(1) + if disabled_status == 'no': + active_services.append(service) + + if active_services: + print(f"{Style.BRIGHT}[+] Checking RMI Services{Style.RESET_ALL}") + + unsafe_active_services = [s for s in active_services if s in unsafe_services] + if unsafe_active_services: + print(f"{Fore.RED} [!] Warning: The following RMI services are enabled and may be unsafe: {', '.join(unsafe_active_services)}.{Style.RESET_ALL}") + + caution_active_services = [s for s in active_services if s in caution_services] + if caution_active_services: + print(f"{Fore.YELLOW} [!] Caution: The following RMI services are enabled: {', '.join(caution_active_services)}.{Style.RESET_ALL}") + + api_active_services = [s for s in active_services if s in ['api', 'api-ssl']] + if api_active_services: + print(f"{Fore.YELLOW} [!] Note: The following RMI services are enabled and might be susceptible to brute force attacks: {', '.join(api_active_services)}.{Style.RESET_ALL}") + + if unsafe_active_services or caution_active_services or api_active_services: + print(f"{Fore.GREEN} [*] Solution: Disable the above RMI services if they are not required for security.{Style.RESET_ALL}") + print(f"{Fore.CYAN} [*] Tip: Restrict access to enabled services to trusted subnets only.{Style.RESET_ALL}") + print() + +def check_upnp_enabled(config_data): + upnp_pattern = r"/ip upnp\s+set.*enabled=yes" + match = re.search(upnp_pattern, config_data) + if match: + print(f"{Style.BRIGHT}[+] Checking UPnP{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: UPnP is enabled. This can expose your network to various security risks, including unauthorized access.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Disable UPnP unless absolutely necessary, and ensure your firewall is properly configured.{Style.RESET_ALL}") + print() + +def check_wifi_settings(config_data): + wifi_patterns = [ + r"/interface wifi\s+set \[ find default-name=(.*?) \]\s+(.*?)(?=set \[ find default-name=|\Z)", + r"/interface wireless\s+set \[ find default-name=(.*?) \]\s+(.*?)(?=set \[ find default-name=|\Z)", + r"/interface wifiwave2\s+set \[ find default-name=(.*?) \]\s+(.*?)(?=set \[ find default-name=|\Z)" + ] + wps_pattern = r"wps=(push-button|disable)" + pmkid_pattern = r"disable-pmkid=(no|yes)" + auth_pattern = r"security\.authentication-types=(wpa-psk|wpa2-psk)" + + for wifi_pattern in wifi_patterns: + wifi_matches = re.findall(wifi_pattern, config_data, re.DOTALL) + + if wifi_matches: + print(f"{Style.BRIGHT}[+] Checking WiFi Settings{Style.RESET_ALL}") + + for interface, settings in wifi_matches: + wps_match = re.search(wps_pattern, settings) + pmkid_match = re.search(pmkid_pattern, settings) + auth_match = re.search(auth_pattern, settings) + + if wps_match and wps_match.group(1) == 'push-button': + print(f"{Fore.YELLOW} [!] Warning: WPS is enabled on interface {interface}. WPS Pin code can be cracked, brute-forced.{Style.RESET_ALL}") + + if pmkid_match and pmkid_match.group(1) == 'no': + print(f"{Fore.YELLOW} [!] Warning: PMKID is enabled on interface {interface}. PMKID is easy to bruteforce.{Style.RESET_ALL}") + + if auth_match: + auth_type = auth_match.group(1) + if auth_type in ['wpa-psk', 'wpa2-psk']: + print(f"{Fore.YELLOW} [!] Warning: Interface {interface} is using insecure authentication method '{auth_type}'. WPA/WPA2-PSK are long gone, use WPA2-E, WPA3.{Style.RESET_ALL}") + + print() + +def check_dns_settings(config_data): + dns_pattern = r"/ip dns\s+set\s+(.*?)\s+(?=\/ip dns|\Z)" + allow_remote_requests_pattern = r"allow-remote-requests=(yes|no)" + use_doh_server_pattern = r"use-doh-server=\"(.*?)\"" + + dns_matches = re.findall(dns_pattern, config_data, re.DOTALL) + + if dns_matches: + print(f"{Style.BRIGHT}[+] Checking DNS Settings{Style.RESET_ALL}") + + for settings in dns_matches: + allow_remote_requests_match = re.search(allow_remote_requests_pattern, settings) + if allow_remote_requests_match and allow_remote_requests_match.group(1) == 'yes': + print(f"{Fore.YELLOW} [!] Warning: Router is configured to allow remote DNS requests. Close the DNS UDP/53 port from the Internet.{Style.RESET_ALL}") + + use_doh_server_match = re.search(use_doh_server_pattern, settings) + if use_doh_server_match and use_doh_server_match.group(1) == '': + print(f"{Fore.YELLOW} [!] Note: DNS over HTTPS (DoH) is not configured. Consider configuring a DoH server for improved privacy.{Style.RESET_ALL}") + + print() + +def check_ddns_enabled(config_data): + pattern = r"/ip cloud\s+set.*ddns-enabled=yes" + match = re.search(pattern, config_data) + + if match: + print(f"{Style.BRIGHT}[+] Checking DDNS Configuration{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: DDNS is enabled. Are you sure you need it?{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Disable DDNS if it is not required.{Style.RESET_ALL}") + print() + +def check_poe_settings(config_data): + poe_pattern = r"/interface ethernet\s+set \[ find default-name=(.*?) \]\s+(.*?)(?=set \[ find default-name=|\Z)" + poe_status_pattern = r"poe-out=(auto-on|forced-on)" + + poe_matches = re.findall(poe_pattern, config_data, re.DOTALL) + + found_poe = False + + for interface, settings in poe_matches: + poe_status_match = re.search(poe_status_pattern, settings) + if poe_status_match: + if not found_poe: + print(f"{Style.BRIGHT}[+] Checking PoE Settings{Style.RESET_ALL}") + found_poe = True + poe_status = poe_status_match.group(1) + if poe_status in ['auto-on', 'forced-on']: + print(f"{Fore.YELLOW} [!] Warning: PoE is enabled on interface {interface} with setting '{poe_status}'. This could supply power to connected devices and potentially damage them if not properly managed.{Style.RESET_ALL}") + + if found_poe: + print() + +def check_protected_routerboot(config_data): + pattern = r"protected-routerboot=(enabled|disabled)" + match = re.search(pattern, config_data) + + if match and match.group(1) == "disabled": + print(f"{Style.BRIGHT}[+] Checking Protected RouterBOOT{Style.RESET_ALL}") + print(f"{Fore.RED} [!] Warning: Protected RouterBOOT is disabled. This may allow unauthorized changes to the bootloader settings.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Enable Protected RouterBOOT to prevent unauthorized access to the bootloader.{Style.RESET_ALL}") + print() + +def check_socks_enabled(config_data): + pattern = r"/ip socks\s+set.*enabled=yes" + match = re.search(pattern, config_data) + + if match: + print(f"{Style.BRIGHT}[+] Checking SOCKS Proxy{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: SOCKS Proxy is enabled. The presence of SOCKS may indicate that the device has been compromised.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Disable SOCKS Proxy if it is not required.{Style.RESET_ALL}") + print() + +def check_bandwidth_server_enabled(config_data): + pattern = r"/tool bandwidth-server\s+set.*enabled=yes" + match = re.search(pattern, config_data) + + if match: + print(f"{Style.BRIGHT}[+] Checking Bandwidth Server{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: Bandwidth Server is enabled. Possible unwanted traffic.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Disable the Bandwidth Server if it is not required.{Style.RESET_ALL}") + print() + +def check_ospf_interfaces(config_data): + ospf_pattern = r'add\s[^\n]*?interfaces=([\w-]+)[^\n]*' + matches = re.findall(ospf_pattern, config_data) + + if matches: + print(f"{Style.BRIGHT}[+] Checking OSPF Interfaces{Style.RESET_ALL}") + for match in matches: + interface_block = re.search(rf'add[^\n]*?interfaces={match}[^\n]*', config_data).group(0) + missing_passive = 'passive' not in interface_block + missing_auth = 'auth=' not in interface_block + + if missing_passive: + print(f"{Fore.YELLOW} [!] Warning: OSPF interface '{match}' is not passive. Without passive interfaces, an attacker can hear an OSPF Hello on the air and connect to an OSPF network.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Consider configuring the interface '{match}' as passive to limit OSPF traffic only to necessary interfaces.{Style.RESET_ALL}") + + if missing_auth: + print(f"{Fore.YELLOW} [!] Warning: OSPF interface '{match}' does not have authentication configured. Without authentication, an attacker can connect to an OSPF network.{Style.RESET_ALL}") + print(f"{Fore.CYAN} [*] Tip: When configuring OSPF authentication, use strong passwords, as OSPF password bruteforcing is still possible.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Configure authentication on the interface '{match}' to secure OSPF.{Style.RESET_ALL}") + print() + +def check_vrrp_interfaces(config_data): + vrrp_pattern = r'add\s[^\n]*?name=([\w-]+)[^\n]*' + matches = re.findall(vrrp_pattern, config_data) + + if matches: + found_issue = False + for match in matches: + interface_block = re.search(rf'add[^\n]*?name={match}[^\n]*', config_data).group(0) + missing_auth = 'authentication=none' in interface_block + + if missing_auth: + if not found_issue: + print(f"{Style.BRIGHT}[+] Checking VRRP Interfaces{Style.RESET_ALL}") + found_issue = True + print(f"{Fore.YELLOW} [!] Warning: VRRP interface '{match}' does not have proper authentication configured (authentication=none). An attacker can spoof the VRRP and conduct MITM.{Style.RESET_ALL}") + print(f"{Fore.CYAN} [*] Fact: Only the 2 version of VRRP supports authentication configuration. If you use AH - it uses HMAC-MD5.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Configure authentication on the interface '{match}' to secure VRRP.{Style.RESET_ALL}") + + if found_issue: + print() + +def check_discovery_protocols(config_data): + pattern = r"/ip neighbor discovery-settings\s+set.*discover-interface-list=all.*protocol=([\w,]+)" + match = re.search(pattern, config_data) + + if match: + active_protocols = match.group(1) + print(f"{Style.BRIGHT}[+] Checking Discovery Protocols{Style.RESET_ALL}") + print(f"{Fore.RED} [!] Warning: Discovery Protocols are enabled on all interfaces (discover-interface-list=all). This could expose detailed information about your device to the network.{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Active protocols: {active_protocols}{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Limit the discovery protocols to specific interfaces or disable them if not required to enhance security.{Style.RESET_ALL}") + print() + +def check_user_password_policies(config_data): + pattern = r"/user settings\s+set.*minimum-categories=0.*minimum-password-length=0" + match = re.search(pattern, config_data) + + if match: + print(f"{Style.BRIGHT}[+] Checking User Password Policies{Style.RESET_ALL}") + print(f"{Fore.RED} [!] Warning: Password policies are not properly configured. Both minimum password categories and minimum password length are set to 0.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Set a higher minimum password length and require at least one or more character categories (e.g., uppercase, lowercase, numbers, special characters) for better security.{Style.RESET_ALL}") + print() + +def check_ssh_strong_crypto(config_data): + pattern = r"/ip ssh\s+set.*strong-crypto=no" + match = re.search(pattern, config_data) + + if match: + print(f"{Style.BRIGHT}[+] Checking SSH Strong Crypto{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: SSH is configured with 'strong-crypto=no'. This reduces the security of SSH connections by allowing weaker encryption algorithms.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Solution: Set 'strong-crypto=yes' to enhance security. This will: {Style.RESET_ALL}") + print(f"{Fore.YELLOW} - Use stronger encryption, HMAC algorithms, and larger DH primes while disallowing weaker ones.{Style.RESET_ALL}") + print(f"{Fore.YELLOW} - Prefer 256-bit and 192-bit encryption instead of 128 bits.{Style.RESET_ALL}") + print(f"{Fore.YELLOW} - Disable null encryption.{Style.RESET_ALL}") + print(f"{Fore.YELLOW} - Prefer sha256 for hashing instead of sha1.{Style.RESET_ALL}") + print(f"{Fore.YELLOW} - Disable md5.{Style.RESET_ALL}") + print(f"{Fore.YELLOW} - Use a 2048-bit prime for Diffie Hellman exchange instead of 1024-bit.{Style.RESET_ALL}") + print() + +def check_connection_tracking(config_data): + pattern = r"/ip firewall connection tracking\s+set.*enabled=(auto|yes)" + match = re.search(pattern, config_data) + + if match: + enabled_value = match.group(1) + print(f"{Style.BRIGHT}[+] Checking Connection Tracking{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Connection Tracking is currently set to '{enabled_value}'.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Advice: If this device is being used as a transit router, you might consider disabling Connection Tracking to improve performance. However, proceed with caution as it can affect certain network features.{Style.RESET_ALL}") + print() + + +def check_romon_enabled(config_data): + pattern = r"/tool romon\s+set.*enabled=yes" + match = re.search(pattern, config_data) + + if match: + print(f"{Style.BRIGHT}[+] Checking RoMON Settings{Style.RESET_ALL}") + print(f"{Fore.YELLOW} [!] Warning: RoMON is enabled. If you are using RoMON, you should carefully manage its settings, as an attacker might use it to gain access to other RouterOS devices.{Style.RESET_ALL}") + print(f"{Fore.GREEN} [*] Advice: Regularly review RoMON configurations and ensure that only authorized devices can use RoMON.{Style.RESET_ALL}") + print() + +def check_mac_server_settings(config_data): + mac_server_pattern = r"/tool mac-server\s+set.*allowed-interface-list=all" + mac_winbox_pattern = r"/tool mac-server mac-winbox\s+set.*allowed-interface-list=all" + mac_ping_pattern = r"/tool mac-server ping\s+set.*enabled=yes" + + mac_server_match = re.search(mac_server_pattern, config_data) + mac_winbox_match = re.search(mac_winbox_pattern, config_data) + mac_ping_match = re.search(mac_ping_pattern, config_data) + + if mac_server_match or mac_winbox_match or mac_ping_match: + print(f"{Style.BRIGHT}[+] Checking MAC Server Settings{Style.RESET_ALL}") + + if mac_server_match: + print(f"{Fore.YELLOW} [!] Warning: MAC Server is allowed on all interfaces (allowed-interface-list=all). This compromises the security of the Winbox interface.{Style.RESET_ALL}") + + if mac_winbox_match: + print(f"{Fore.YELLOW} [!] Warning: MAC Winbox is allowed on all interfaces (allowed-interface-list=all). This compromises the security of the Winbox interface.{Style.RESET_ALL}") + + if mac_ping_match: + print(f"{Fore.YELLOW} [!] Warning: MAC Ping is enabled. Possible unwanted traffic.{Style.RESET_ALL}") + + print(f"{Fore.GREEN} [*] Solution: Limit MAC server and MAC Winbox to specific trusted interfaces, and disable MAC Ping if it is not required.{Style.RESET_ALL}") + print() + +def check_snmp_communities(config_data): + public_pattern = r'/snmp community[\s\S]*?(set|add)[\s\S]*?name=public' + private_pattern = r'/snmp community[\s\S]*?(set|add)[\s\S]*?name=private' + + public_match = re.search(public_pattern, config_data) + private_match = re.search(private_pattern, config_data) + + if public_match or private_match: + print(f"{Style.BRIGHT}[+] Checking SNMP Communities{Style.RESET_ALL}") + + if public_match: + print(f"{Fore.YELLOW} [!] Warning: SNMP community 'public' is in use. Possible Information Gathering attack vector by bruteforcing community string.{Style.RESET_ALL}") + + if private_match: + print(f"{Fore.YELLOW} [!] Warning: SNMP community 'private' is in use. Possible Information Gathering attack vector by bruteforcing community string.{Style.RESET_ALL}") + + print(f"{Fore.GREEN} [*] Solution: Change the SNMP community names to something more secure, and restrict SNMP access to trusted IP addresses only.{Style.RESET_ALL}") + print() + +def check_port_forwarding_rules(config_data): + nat_pattern = r"add\s+action=dst-nat(?:\s+\S+)*\s+to-addresses=\S+(?:\s+\S+)*\s+to-ports=\S+" + matches = re.findall(nat_pattern, config_data, re.DOTALL) + + if matches: + print(f"{Style.BRIGHT}[+] Checking Port Forwarding (dst-nat){Style.RESET_ALL}") + for match in matches: + print(f"{Fore.YELLOW} [!] Warning: Port forwarding detected:{Style.RESET_ALL} {match.strip()}. This may expose your internal network to the internet.") + print(f"{Fore.CYAN} [!] Risk: Using port forwarding reduces the level of network security. A device exposed to the internet via port forwarding can be hacked, putting the internal infrastructure at risk.") + print(f"{Fore.GREEN} [*] Solution: It's better to avoid port forwarding in favor of VPN servers for accessing the internal infrastructure from outside.{Style.RESET_ALL}") + print() + + +def main(): + banner() + args = parse_arguments() + config_file = args.config_file + + try: + with open(config_file, 'r') as file: + config_data = file.read() + print(f"{Style.BRIGHT}[*] Analyzing the configuration file: {config_file} ({round(len(config_data)/1024, 2)} KB){Style.RESET_ALL}\n") + extract_device_info(config_data) + check_smb_enabled(config_data) + check_rmi_services(config_data) + check_upnp_enabled(config_data) + check_wifi_settings(config_data) + check_dns_settings(config_data) + check_ddns_enabled(config_data) + check_poe_settings(config_data) + check_protected_routerboot(config_data) + check_socks_enabled(config_data) + check_bandwidth_server_enabled(config_data) + check_ospf_interfaces(config_data) + check_vrrp_interfaces(config_data) + check_discovery_protocols(config_data) + check_user_password_policies(config_data) + check_ssh_strong_crypto(config_data) + check_connection_tracking(config_data) + check_romon_enabled(config_data) + check_mac_server_settings(config_data) + check_snmp_communities(config_data) + check_port_forwarding_rules(config_data) + + except FileNotFoundError: + print(f"{Fore.RED}Error: The file '{config_file}' was not found.{Style.RESET_ALL}") + except Exception as e: + print(f"{Fore.RED}Error: An unexpected error occurred: {e}{Style.RESET_ALL}") + +# Проверка на запуск скрипта +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 41a6675..7b69ce7 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,23 @@ from setuptools import setup, find_packages setup( - name="vex", - version="1.1", - url="https://github.com/casterbyte/vex", + name="sara", + version="1.0", + url="https://github.com/casterbyte/sara", author="Magama Bazarov", author_email="caster@exploit.org", - scripts=['vex.py'], + scripts=['sara.py'], description="RouterOS Security Inspector", long_description=open('README.md').read(), long_description_content_type='text/markdown', license="Apache-2.0", - keywords=['network security', 'mikrotik', 'routeros'], + keywords=['mikrotik', 'routeros', 'config analyzer'], packages=find_packages(), install_requires=[ 'colorama', ], entry_points={ - "console_scripts": ["vex = vex:main"], + "console_scripts": ["sara = sara:main"], }, python_requires='>=3.11', ) \ No newline at end of file diff --git a/vex.py b/vex.py deleted file mode 100644 index f33b7a2..0000000 --- a/vex.py +++ /dev/null @@ -1,488 +0,0 @@ -#!/usr/bin/env python3 - -import re -import argparse -from colorama import init, Fore, Style - -# Colorama -init(autoreset=True) - -# Banner -banner = ''' - .%. .%. - :@@@: .%@@- - :@@+@@: .%@*@@- - :@@- -@@:.@@= .@@= - :@@: :@@@@= .@@- - :@@: =@@+ .@@= - :@@: :@@@@- .@@= - -@@: -@@-:@@= .%@+ - -@@: =@@- :@@= .%@+ - =@@@@@@@@@@@@@@@@@@@@@@@@@@@@= - @@% @@* - *@# #@# - #@# *@# - #@* +@% - %@@@@@@@@@@@@@@@@@@@@% -''' - -print(banner) -print(" Vex: RouterOS Security Inspector") -print(" Designed for security engineers\n") -print(" For documentation visit: " + "https://github.com/casterbyte/Vex\n") -print(" " + Fore.YELLOW + "Author: " + Style.RESET_ALL + "Magama Bazarov, ") -print(" " + Fore.YELLOW + "Pseudonym: " + Style.RESET_ALL + "Caster") -print(" " + Fore.YELLOW + "Version: " + Style.RESET_ALL + "1.1") -print(" " + Fore.WHITE + "CAUTION: " + Fore.YELLOW + "For the tool to work correctly, use the RouterOS configuration from using the" + Fore.WHITE + " export verbose" + Fore.YELLOW + " command\n") - -# Device Info -def extract_info(config_content): - info_found = False - info = [] - - version_pattern = r'# .* by RouterOS ([\d.]+)' - software_id_pattern = r'# software id = (\S+)' - model_pattern = r'# model = (\S+)' - serial_number_pattern = r'# serial number = (\S+)' - - version = re.search(version_pattern, config_content) - software_id = re.search(software_id_pattern, config_content) - model = re.search(model_pattern, config_content) - serial_number = re.search(serial_number_pattern, config_content) - - if version: - info.append(f"{Style.BRIGHT + Fore.WHITE}[*] RouterOS Version: {Style.BRIGHT + Fore.YELLOW}{version.group(1)}{Style.RESET_ALL}") - info_found = True - if software_id: - info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Software ID: {Style.BRIGHT + Fore.YELLOW}{software_id.group(1)}{Style.RESET_ALL}") - info_found = True - if model: - info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Model: {Style.BRIGHT + Fore.YELLOW}{model.group(1)}{Style.RESET_ALL}") - info_found = True - if serial_number: - info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Serial Number: {Style.BRIGHT + Fore.YELLOW}{serial_number.group(1)}{Style.RESET_ALL}") - info_found = True - - if info_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] Device Information:{Style.RESET_ALL}") - for line in info: - print(line) - -# Discovery -def check_discovery_settings(config_content): - discovery_found = False - discovery_info = [] - - discovery_pattern = r'/ip neighbor discovery-settings[\s\S]*?set discover-interface-list=all' - if re.search(discovery_pattern, config_content): - discovery_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}Discovery protocols are enabled on all interfaces{Style.RESET_ALL}") - discovery_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Information Gathering{Style.RESET_ALL}") - discovery_found = True - - if discovery_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] Discovery Protocols:{Style.RESET_ALL}") - for line in discovery_info: - print(line) - -# Bandwidth Server -def check_bandwidth_server(config_content): - bandwidth_found = False - bandwidth_info = [] - - bandwidth_pattern = r'/tool bandwidth-server[\s\S]*?set[\s\S]*?enabled=yes' - if re.search(bandwidth_pattern, config_content): - bandwidth_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}Bandwidth Server is enabled{Style.RESET_ALL}") - bandwidth_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Potential misuse for traffic analysis and network performance degradation{Style.RESET_ALL}") - bandwidth_found = True - - if bandwidth_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] Bandwidth Server:{Style.RESET_ALL}") - for line in bandwidth_info: - print(line) - -# DNS Check -def check_dns_settings(config_content): - dns_found = False - dns_info = [] - - dns_pattern = r'/ip dns[\s\S]*?set[\s\S]*?allow-remote-requests=yes' - if re.search(dns_pattern, config_content): - dns_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}Router is configured as a DNS server{Style.RESET_ALL}") - dns_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}DNS Flood{Style.RESET_ALL}") - dns_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Consider closing this port from the internet to avoid unwanted traffic{Style.RESET_ALL}") - dns_found = True - - if dns_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] DNS Settings:{Style.RESET_ALL}") - for line in dns_info: - print(line) - -# UPnP Check -def check_upnp_settings(config_content): - upnp_found = False - upnp_info = [] - - upnp_pattern = r'/ip upnp[\s\S]*?set[\s\S]*?enabled=(\w+)' - match = re.search(upnp_pattern, config_content) - if match and match.group(1) == 'yes': - upnp_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}UPnP is enabled{Style.RESET_ALL}") - upnp_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Potential unauthorized port forwarding and security risks{Style.RESET_ALL}") - upnp_found = True - else: - upnp_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] UPnP is not enabled{Style.RESET_ALL}") - - if upnp_found or not upnp_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] UPnP Settings:{Style.RESET_ALL}") - for line in upnp_info: - print(line) - -# DDNS Check -def check_ddns_settings(config_content): - ddns_found = False - ddns_info = [] - - ddns_pattern = r'/ip cloud[\s\S]*?set[\s\S]*?ddns-enabled=yes' - if re.search(ddns_pattern, config_content): - ddns_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}Dynamic DNS is enabled{Style.RESET_ALL}") - ddns_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Exposure to dynamic IP changes and potential unauthorized access{Style.RESET_ALL}") - ddns_found = True - - if ddns_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] DDNS Settings:{Style.RESET_ALL}") - for line in ddns_info: - print(line) - -# SSH Strong Crypto -def check_ssh_settings(config_content): - ssh_found = False - ssh_info = [] - - ssh_pattern = r'/ip ssh[\s\S]*?set[\s\S]*?strong-crypto=no' - if re.search(ssh_pattern, config_content): - ssh_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}SSH strong crypto is disabled (strong-crypto=no){Style.RESET_ALL}") - ssh_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Less secure SSH connections{Style.RESET_ALL}") - ssh_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Enable strong crypto (strong-crypto=yes) for enhanced security. This will use stronger encryption, HMAC algorithms, larger DH primes, and disallow weaker ones{Style.RESET_ALL}") - ssh_found = True - - if ssh_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] SSH Strong Crypto:{Style.RESET_ALL}") - for line in ssh_info: - print(line) - -# SOCKS Check -def check_socks_settings(config_content): - socks_found = False - socks_info = [] - - socks_pattern = r'/ip socks[\s\S]*?set[\s\S]*?enabled=(\w+)' - match = re.search(socks_pattern, config_content) - if match and match.group(1) == 'yes': - socks_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}SOCKS proxy is enabled{Style.RESET_ALL}") - socks_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Potential unauthorized access and misuse of network resources{Style.RESET_ALL}") - socks_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Disable SOCKS proxy or ensure it is properly secured. SOCKS can be used maliciously if RouterOS is compromised{Style.RESET_ALL}") - socks_found = True - else: - socks_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] SOCKS proxy is not enabled{Style.RESET_ALL}") - - if socks_found or not socks_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] SOCKS Settings:{Style.RESET_ALL}") - for line in socks_info: - print(line) - -# VRRP -def check_vrrp_authentication(config_content): - vrrp_found = False - vrrp_info = [] - - vrrp_pattern = r'add\s[^\n]*?authentication=none[^\n]*?name=([\w-]+)' - matches = re.findall(vrrp_pattern, config_content) - - if matches: - for interface_name in matches: - vrrp_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}VRRP interface '{interface_name}' has no authentication{Style.RESET_ALL}") - vrrp_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Potential unauthorized access and manipulation of VRRP settings{Style.RESET_ALL}") - vrrp_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Configure authentication for VRRP interfaces to prevent unauthorized access{Style.RESET_ALL}") - vrrp_found = True - - if vrrp_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] VRRP Authentication Settings:{Style.RESET_ALL}") - for line in vrrp_info: - print(line) -# ROMON -def check_romon(config_content): - romon_info = [] - - romon_pattern = r'/tool romon[\s\S]*?set[\s\S]*?enabled=yes' - if re.search(romon_pattern, config_content): - romon_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}ROMON is enabled{Style.RESET_ALL}") - romon_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}ROMON can be a jump point to other MikroTik devices and should be monitored carefully{Style.RESET_ALL}") - romon_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Monitor ROMON activities and ensure proper security measures are in place{Style.RESET_ALL}") - else: - romon_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] ROMON is not enabled{Style.RESET_ALL}") - - if romon_info: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] ROMON Settings:{Style.RESET_ALL}") - for line in romon_info: - print(line) - -# MAC Telnet Server -def check_mac_server(config_content): - mac_server_found = False - mac_server_info = [] - - mac_server_pattern = r'/tool mac-server[\s\S]*?set[\s\S]*?allowed-interface-list=all' - if re.search(mac_server_pattern, config_content): - mac_server_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}MAC Telnet server is active on all interfaces{Style.RESET_ALL}") - mac_server_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}This reduces the security of the Winbox interface. Filter access{Style.RESET_ALL}") - mac_server_found = True - - if mac_server_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] MAC Server Settings:{Style.RESET_ALL}") - for line in mac_server_info: - print(line) - -# MAC Winbox Server -def check_mac_winbox_server(config_content): - mac_winbox_found = False - mac_winbox_info = [] - - mac_winbox_pattern = r'/tool mac-server mac-winbox[\s\S]*?set[\s\S]*?allowed-interface-list=all' - if re.search(mac_winbox_pattern, config_content): - mac_winbox_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}MAC Winbox Server is accessible on all interfaces{Style.RESET_ALL}") - mac_winbox_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}This reduces the security of the Winbox interface. Filter access{Style.RESET_ALL}") - mac_winbox_found = True - - if mac_winbox_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] MAC Winbox Server Settings:{Style.RESET_ALL}") - for line in mac_winbox_info: - print(line) - -# MAC Ping Server -def check_mac_ping_server(config_content): - mac_ping_found = False - mac_ping_info = [] - - mac_ping_pattern = r'/tool mac-server ping[\s\S]*?set[\s\S]*?enabled=yes' - if re.search(mac_ping_pattern, config_content): - mac_ping_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}MAC Ping Server is enabled{Style.RESET_ALL}") - mac_ping_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Possible unwanted traffic{Style.RESET_ALL}") - mac_ping_found = True - - if mac_ping_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] MAC Ping Server Settings:{Style.RESET_ALL}") - for line in mac_ping_info: - print(line) - - - -# SNMP Community Check -def check_snmp_community(config_content): - snmp_found = False - snmp_info = [] - - public_pattern = r'/snmp community[\s\S]*?set[\s\S]*?name=public' - private_pattern = r'/snmp community[\s\S]*?set[\s\S]*?name=private' - - public_match = re.search(public_pattern, config_content) - private_match = re.search(private_pattern, config_content) - - if public_match: - snmp_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}SNMP community 'public' is in use{Style.RESET_ALL}") - snmp_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Information Gathering{Style.RESET_ALL}") - snmp_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Change the community name to something more secure{Style.RESET_ALL}") - snmp_found = True - - if private_match: - snmp_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}SNMP community 'private' is in use{Style.RESET_ALL}") - snmp_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Information Gathering{Style.RESET_ALL}") - snmp_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Change the community name to something more secure{Style.RESET_ALL}") - snmp_found = True - - if snmp_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] SNMP:{Style.RESET_ALL}") - for line in snmp_info: - print(line) - -# OSPF Check -def check_ospf_templates(config_content): - ospf_found = False - ospf_info = [] - - ospf_pattern = r'add\s[^\n]*?interfaces=([\w-]+)[^\n]*' - matches = re.findall(ospf_pattern, config_content) - - for match in matches: - interface_block = re.search(rf'add[^\n]*?interfaces={match}[^\n]*', config_content).group(0) - missing_passive = 'passive' not in interface_block - missing_auth = 'auth=' not in interface_block - - if missing_passive or missing_auth: - if missing_passive: - ospf_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}OSPF interface '{match}' is not set to passive{Style.RESET_ALL}") - if missing_auth: - ospf_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}OSPF interface '{match}' has no authentication{Style.RESET_ALL}") - ospf_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Potential unauthorized access and network disruption{Style.RESET_ALL}") - ospf_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Configure authentication and passive mode for OSPF interfaces to enhance security{Style.RESET_ALL}") - ospf_found = True - - if ospf_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] OSPF Interface Templates Check:{Style.RESET_ALL}") - for line in ospf_info: - print(line) - -# User Settings Check (Pass Length) -def check_user_settings(config_content): - user_settings_found = False - user_settings_info = [] - - user_settings_pattern = r'/user settings[\s\S]*?set[\s\S]*?minimum-categories=0[\s\S]*?minimum-password-length=0' - if re.search(user_settings_pattern, config_content): - user_settings_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}No minimum password complexity or length requirements{Style.RESET_ALL}") - user_settings_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.YELLOW}Set minimum password complexity and length requirements to enhance security{Style.RESET_ALL}") - user_settings_found = True - - if user_settings_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] Password Strength Requirements:{Style.RESET_ALL}") - for line in user_settings_info: - print(line) - -# PoE Check -def check_poe_settings(config_content): - poe_found = False - poe_info = [] - - poe_auto_on_pattern = r'/interface ethernet[\s\S]*?poe-out=auto-on' - poe_forced_on_pattern = r'/interface ethernet[\s\S]*?poe-out=forced-on' - - poe_auto_on_match = re.search(poe_auto_on_pattern, config_content) - poe_forced_on_match = re.search(poe_forced_on_pattern, config_content) - - if poe_auto_on_match: - poe_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}PoE is set to auto-on{Style.RESET_ALL}") - poe_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}There is a risk of damaging connected devices by unexpectedly supplying power to the port{Style.RESET_ALL}") - poe_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.YELLOW}Review and set PoE settings appropriately{Style.RESET_ALL}") - poe_found = True - - if poe_forced_on_match: - poe_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}PoE is set to forced-on{Style.RESET_ALL}") - poe_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}There is a significant risk of damaging connected devices by unexpectedly supplying power to the port{Style.RESET_ALL}") - poe_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] Recommendation: {Style.BRIGHT + Fore.YELLOW}Review and set PoE settings appropriately{Style.RESET_ALL}") - poe_found = True - - if poe_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] PoE Settings:{Style.RESET_ALL}") - for line in poe_info: - print(line) - -# SMB Check -def check_smb_settings(config_content): - smb_found = False - smb_info = [] - - smb_section_pattern = r'/ip smb\s*set\s.*?enabled=(\w+)' - match = re.search(smb_section_pattern, config_content) - - if match and match.group(1) == 'yes': - smb_info.append(f"{Style.BRIGHT + Fore.RED}[!] Warning: {Style.BRIGHT + Fore.YELLOW}SMB is enabled{Style.RESET_ALL}") - smb_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Impact: {Style.BRIGHT + Fore.YELLOW}Reading files, potential CVE-2018-7445{Style.RESET_ALL}") - smb_info.append(f"{Style.BRIGHT + Fore.WHITE}[*] Recommendation: {Style.BRIGHT + Fore.GREEN}Are you sure you want SMB? If you don't need it, turn it off. Be careful{Style.RESET_ALL}") - smb_found = True - - if smb_found: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] SMB Settings:{Style.RESET_ALL}") - for line in smb_info: - print(line) - -# Service Check -def check_services(config_content): - services_info = [] - - # Patterns for each service - service_patterns = { - "Telnet": r'/ip service[\s\S]*?set telnet address=.* disabled=(\S+)', - "FTP": r'/ip service[\s\S]*?set ftp address=.* disabled=(\S+)', - "WWW (HTTP)": r'/ip service[\s\S]*?set www address=.* disabled=(\S+)', - "SSH": r'/ip service[\s\S]*?set ssh address=.* disabled=(\S+)', - "WWW-SSL (HTTPS)": r'/ip service[\s\S]*?set www-ssl address=.* disabled=(\S+)', - "API": r'/ip service[\s\S]*?set api address=.* disabled=(\S+)', - "Winbox": r'/ip service[\s\S]*?set winbox address=.* disabled=(\S+)', - "API-SSL": r'/ip service[\s\S]*?set api-ssl address=.* disabled=(\S+)', - } - - for service, pattern in service_patterns.items(): - match = re.search(pattern, config_content) - if match: - status = match.group(1) - if status == 'no': - if service in ["Telnet", "FTP", "API", "API-SSL"]: - services_info.append(f"{Style.BRIGHT + Fore.RED}[*] {service} is enabled{Style.RESET_ALL} - {Style.BRIGHT + Fore.GREEN}Consider disabling for security reasons{Style.RESET_ALL}") - else: - services_info.append(f"{Style.BRIGHT + Fore.YELLOW}[*] {service} is enabled{Style.RESET_ALL}") - else: - services_info.append(f"{Style.BRIGHT + Fore.GREEN}[*] {service} is disabled{Style.RESET_ALL}") - else: - services_info.append(f"{Style.BRIGHT + Fore.RED}[!] {service} configuration not found{Style.RESET_ALL}") - - if services_info: - print(f"{Fore.CYAN}" + "-" * 30 + Style.RESET_ALL) - print(f"{Fore.CYAN}[+] RMI Interfaces Status:{Style.RESET_ALL}") - for line in services_info: - print(line) - - # General recommendation - print(f"{Style.BRIGHT + Fore.GREEN}[!] Recommendation:{Style.RESET_ALL} {Style.BRIGHT + Fore.GREEN}Restrict access to RMI only from trusted subnets{Style.RESET_ALL}") - -# Main -def main(): - parser = argparse.ArgumentParser(description="Vex: RouterOS Security Inspector") - parser.add_argument("--config", required=True, type=str, help="Path to the RouterOS configuration file") - args = parser.parse_args() - - config_file = args.config - - print(f"{Fore.GREEN}[*] Config Analyzing...{Style.RESET_ALL}") - try: - with open(config_file, 'r') as file: - config_content = file.read() - extract_info(config_content) - check_discovery_settings(config_content) - check_bandwidth_server(config_content) - check_dns_settings(config_content) - check_ddns_settings(config_content) - check_upnp_settings(config_content) - check_ssh_settings(config_content) - check_socks_settings(config_content) - check_romon(config_content) - check_mac_server(config_content) - check_mac_winbox_server(config_content) - check_mac_ping_server(config_content) - check_vrrp_authentication(config_content) - check_snmp_community(config_content) - check_ospf_templates(config_content) - check_user_settings(config_content) - check_poe_settings(config_content) - check_smb_settings(config_content) - check_services(config_content) - except Exception as e: - print(f"{Fore.RED}Error reading file: {e}{Style.RESET_ALL}") - -if __name__ == "__main__": - main()