sara v1.0

This commit is contained in:
casterbyte 2024-08-17 20:00:26 +05:00
parent 0833d202d5
commit d9f23b2998
5 changed files with 508 additions and 656 deletions

240
README.md
View file

@ -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, <caster@exploit.org>
Pseudonym: Caster
Version: 1.1
RouterOS Security Inspector. Designed for Security Professionals
Author: Magama Bazarov, <caster@exploit.org>
```
# 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, <caster@exploit.org>
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, <caster@exploit.org>
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

424
sara.py Normal file
View file

@ -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, <caster@exploit.org>\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()

View file

@ -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',
)

488
vex.py
View file

@ -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, <caster@exploit.org>")
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()