mirror of
https://github.com/casterbyte/Sara.git
synced 2025-07-11 15:14:28 +02:00
488 lines
24 KiB
Python
488 lines
24 KiB
Python
#!/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()
|