mirror of
https://github.com/casterbyte/Sara.git
synced 2025-07-11 23:24:28 +02:00
805 lines
No EOL
36 KiB
Python
805 lines
No EOL
36 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import re
|
|
from colorama import init, Fore, Style
|
|
|
|
init(autoreset=True)
|
|
|
|
def parse_arguments():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument('--config', required=True, help='RouterOS configuration file name')
|
|
return parser.parse_args()
|
|
|
|
def load_config(file_path):
|
|
with open(file_path, 'r') as file:
|
|
return file.read().splitlines()
|
|
|
|
def combine_multiline_statements(config_lines):
|
|
combined_lines = []
|
|
buffer = ""
|
|
for line in config_lines:
|
|
line = line.strip()
|
|
if line.endswith("\\"):
|
|
buffer += line[:-1] + " "
|
|
else:
|
|
buffer += line
|
|
combined_lines.append(buffer)
|
|
buffer = ""
|
|
return combined_lines
|
|
|
|
def extract_device_info(config_lines):
|
|
software_id = None
|
|
model = None
|
|
serial_number = None
|
|
version = None
|
|
|
|
for line in config_lines:
|
|
if line.startswith("# software id ="):
|
|
software_id = line.split('=')[1].strip()
|
|
elif line.startswith("# model ="):
|
|
model = line.split('=')[1].strip()
|
|
elif line.startswith("# serial number ="):
|
|
serial_number = line.split('=')[1].strip()
|
|
|
|
return software_id, model, serial_number
|
|
|
|
def extract_interfaces(config_lines):
|
|
interfaces = []
|
|
current_interface_type = None
|
|
|
|
for line in config_lines:
|
|
line = line.strip()
|
|
if line.startswith('/interface '):
|
|
current_interface_type = line.split()[1]
|
|
continue
|
|
|
|
if line.startswith('/') and not line.startswith('/interface'):
|
|
current_interface_type = None
|
|
|
|
if current_interface_type:
|
|
if line.startswith('set ') or line.startswith('add '):
|
|
name_match = re.search(r'name=(\S+)', line)
|
|
default_name_match = re.search(r'default-name=(\S+)', line)
|
|
if name_match:
|
|
interface_name = name_match.group(1)
|
|
interfaces.append((current_interface_type, interface_name))
|
|
elif default_name_match:
|
|
interface_name = default_name_match.group(1)
|
|
interfaces.append((current_interface_type, interface_name))
|
|
|
|
return interfaces
|
|
|
|
def extract_ip_addresses(config_lines):
|
|
ip_addresses = []
|
|
ip_pattern = re.compile(r'^/ip address')
|
|
add_pattern = re.compile(r'add address=([\d\.\/]+)(?: disabled=\S+)? interface=(\S+) network=\S+')
|
|
|
|
inside_ip_address_block = False
|
|
|
|
for line in config_lines:
|
|
if ip_pattern.match(line):
|
|
inside_ip_address_block = True
|
|
continue
|
|
|
|
if inside_ip_address_block and line.startswith("add "):
|
|
add_match = add_pattern.search(line)
|
|
if add_match:
|
|
ip_address = add_match.group(1)
|
|
interface_name = add_match.group(2)
|
|
ip_addresses.append((ip_address, interface_name))
|
|
|
|
return ip_addresses
|
|
|
|
def check_discovery_protocols(config_lines):
|
|
discovery_pattern = re.compile(r'^/ip neighbor discovery-settings')
|
|
set_pattern = re.compile(r'set discover-interface-list=(\S+)')
|
|
|
|
for line in config_lines:
|
|
if discovery_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
set_match = set_pattern.search(next_line)
|
|
if set_match:
|
|
discovery_setting = set_match.group(1)
|
|
if discovery_setting.lower() == 'all':
|
|
return (True, f"detected set discover-interface-list={discovery_setting}")
|
|
return (False, "No security issues found with Discovery protocols.")
|
|
|
|
def check_bandwidth_server(config_lines):
|
|
bandwidth_pattern = re.compile(r'^/tool bandwidth-server')
|
|
set_pattern_enabled = re.compile(r'set .*enabled=yes')
|
|
set_pattern_disabled = re.compile(r'set .*enabled=no')
|
|
|
|
inside_bandwidth_block = False
|
|
for line in config_lines:
|
|
if bandwidth_pattern.match(line):
|
|
inside_bandwidth_block = True
|
|
continue
|
|
|
|
if inside_bandwidth_block:
|
|
if set_pattern_disabled.search(line):
|
|
return (False, "No issues found with Bandwidth Server.")
|
|
elif set_pattern_enabled.search(line):
|
|
return (True, "detected active Bandwidth Server with 'enabled=yes' setting")
|
|
|
|
return (True, "detected active Bandwidth Server (default enabled)")
|
|
|
|
def check_dns_settings(config_lines):
|
|
dns_pattern = re.compile(r'^/ip dns')
|
|
set_pattern = re.compile(r'set allow-remote-requests=yes')
|
|
|
|
for line in config_lines:
|
|
if dns_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
if set_pattern.search(next_line):
|
|
return (True, "detected directive 'set allow-remote-requests=yes'")
|
|
return (False, "No issues found with DNS settings.")
|
|
|
|
def check_ddns(config_lines):
|
|
ddns_pattern = re.compile(r'^/ip cloud')
|
|
set_pattern = re.compile(r'set ddns-enabled=yes')
|
|
|
|
for line in config_lines:
|
|
if ddns_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
if set_pattern.search(next_line):
|
|
return (True, "DDNS is enabled. If not specifically used, it is recommended to disable it.")
|
|
return (False, "No issues found with DDNS settings.")
|
|
|
|
def check_upnp_settings(config_lines):
|
|
upnp_pattern = re.compile(r'^/ip upnp')
|
|
set_pattern = re.compile(r'set .*enabled=(yes|no)')
|
|
|
|
inside_upnp_block = False
|
|
|
|
for line in config_lines:
|
|
if upnp_pattern.match(line):
|
|
inside_upnp_block = True
|
|
continue
|
|
|
|
if inside_upnp_block:
|
|
set_match = set_pattern.search(line)
|
|
if set_match:
|
|
upnp_status = set_match.group(1)
|
|
if upnp_status == "yes":
|
|
return (True, "detected directive 'set enabled=yes'")
|
|
inside_upnp_block = False
|
|
|
|
return (False, "No issues found with UPnP settings.")
|
|
|
|
def extract_firewall_rules(config_lines, table):
|
|
firewall_rules = []
|
|
firewall_pattern = re.compile(rf'^/ip firewall {table}')
|
|
add_pattern = re.compile(r'add .*')
|
|
|
|
inside_firewall_block = False
|
|
|
|
for line in config_lines:
|
|
if firewall_pattern.match(line):
|
|
inside_firewall_block = True
|
|
continue
|
|
|
|
if inside_firewall_block:
|
|
if line.startswith("add "):
|
|
firewall_rules.append(line)
|
|
else:
|
|
inside_firewall_block = False
|
|
|
|
return firewall_rules
|
|
|
|
def extract_nat_rules(config_lines):
|
|
nat_rules = []
|
|
nat_pattern = re.compile(r'^/ip firewall nat')
|
|
add_pattern = re.compile(r'add .*')
|
|
|
|
inside_nat_block = False
|
|
|
|
for line in config_lines:
|
|
if nat_pattern.match(line):
|
|
inside_nat_block = True
|
|
continue
|
|
|
|
if inside_nat_block:
|
|
if line.startswith("add "):
|
|
nat_rules.append(line)
|
|
else:
|
|
inside_nat_block = False
|
|
|
|
return nat_rules
|
|
|
|
def check_bpdu_guard(config_lines):
|
|
bridge_port_pattern = re.compile(r'^/interface bridge port')
|
|
bpdu_guard_pattern = re.compile(r'add .*bpdu-guard=no')
|
|
|
|
for line in config_lines:
|
|
if bridge_port_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
while next_line_index < len(config_lines) and config_lines[next_line_index].startswith('add '):
|
|
if bpdu_guard_pattern.search(config_lines[next_line_index]):
|
|
return (True, "detected 'bpdu-guard=no'. It is recommended to enable BPDU Guard to protect STP from attacks")
|
|
next_line_index += 1
|
|
return (False, "No issues found with BPDU Guard settings.")
|
|
|
|
def check_ssh_settings(config_lines):
|
|
ssh_pattern = re.compile(r'^/ip ssh')
|
|
strong_crypto_pattern = re.compile(r'set .*strong-crypto=no')
|
|
|
|
for line in config_lines:
|
|
if ssh_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
if strong_crypto_pattern.search(next_line):
|
|
return (True, "detected 'strong-crypto=no'. It is recommended to enable strong cryptographic ciphers for SSH")
|
|
return (False, "No issues found with SSH settings.")
|
|
|
|
|
|
def check_dhcp_snooping(config_lines):
|
|
bridge_pattern = re.compile(r'^/interface bridge')
|
|
dhcp_snooping_pattern = re.compile(r'add .*dhcp-snooping=no')
|
|
|
|
for line in config_lines:
|
|
if bridge_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
while next_line_index < len(config_lines) and config_lines[next_line_index].startswith('add '):
|
|
if dhcp_snooping_pattern.search(config_lines[next_line_index]):
|
|
return (True, "detected 'dhcp-snooping=no'. It is recommended to enable DHCP Snooping to protect the network from DHCP attacks (DHCP Spoofing)")
|
|
next_line_index += 1
|
|
return (False, "No issues found with DHCP Snooping settings.")
|
|
|
|
def extract_routes(config_lines):
|
|
routes = []
|
|
route_pattern = re.compile(r'^/ip route')
|
|
add_pattern = re.compile(r'add .*')
|
|
|
|
inside_route_block = False
|
|
|
|
for line in config_lines:
|
|
if route_pattern.match(line):
|
|
inside_route_block = True
|
|
continue
|
|
|
|
if inside_route_block:
|
|
if line.startswith("add "):
|
|
routes.append(line)
|
|
else:
|
|
inside_route_block = False
|
|
|
|
return routes
|
|
|
|
def check_socks_settings(config_lines):
|
|
socks_pattern = re.compile(r'^/ip socks')
|
|
set_pattern = re.compile(r'set .*enabled=yes')
|
|
|
|
for line in config_lines:
|
|
if socks_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
if set_pattern.search(next_line):
|
|
return (True, "detected directive 'set enabled=yes'. SOCKS proxy can be used as a pivoting tool to access the internal network")
|
|
return (False, "No issues found with SOCKS settings.")
|
|
|
|
def check_vrrp_authentication(config_lines):
|
|
vrrp_pattern = re.compile(r'^/interface vrrp')
|
|
auth_pattern = re.compile(r'authentication=none')
|
|
|
|
for line in config_lines:
|
|
if vrrp_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
if auth_pattern.search(next_line):
|
|
return (True, "VRRP authentication is set to 'none'. This poses a risk of VRRP Hijacking.", "It's recommended to set the maximum priority to 255 if possible. If using VRRPv3, use FW to filter traffic towards MCAST 224.0.0.18")
|
|
return (False, "No issues found with VRRP authentication settings", None)
|
|
|
|
def check_ospf_authentication(config_lines):
|
|
ospf_pattern = re.compile(r'^/routing ospf interface-template')
|
|
auth_pattern = re.compile(r'auth=')
|
|
|
|
inside_ospf_block = False
|
|
|
|
for line in config_lines:
|
|
if ospf_pattern.match(line):
|
|
inside_ospf_block = True
|
|
continue
|
|
|
|
if inside_ospf_block:
|
|
if 'add ' in line:
|
|
if auth_pattern.search(line) is None:
|
|
return (True, "OSPF authentication is not configured. This poses a security risk.")
|
|
inside_ospf_block = False
|
|
|
|
return (False, "No issues found with OSPF authentication settings.")
|
|
|
|
def check_ospf_passive_setting(config_lines):
|
|
ospf_pattern = re.compile(r'^/routing ospf interface-template')
|
|
auth_pattern = re.compile(r'auth=')
|
|
passive_pattern = re.compile(r'passive')
|
|
|
|
inside_ospf_block = False
|
|
auth_missing = False
|
|
passive_missing = False
|
|
|
|
for line in config_lines:
|
|
if ospf_pattern.match(line):
|
|
inside_ospf_block = True
|
|
continue
|
|
|
|
if inside_ospf_block:
|
|
if 'add ' in line:
|
|
if auth_pattern.search(line) is None:
|
|
auth_missing = True
|
|
if passive_pattern.search(line) is None:
|
|
passive_missing = True
|
|
inside_ospf_block = False
|
|
|
|
return auth_missing, passive_missing
|
|
|
|
def check_ip_services(config_lines):
|
|
services_pattern = re.compile(r'^/ip service')
|
|
telnet_pattern = re.compile(r'set telnet .*disabled=(yes|no)')
|
|
ftp_pattern = re.compile(r'set ftp .*disabled=(yes|no)')
|
|
api_pattern = re.compile(r'set api .*disabled=(yes|no)')
|
|
api_ssl_pattern = re.compile(r'set api-ssl .*disabled=(yes|no)')
|
|
ssh_pattern = re.compile(r'set ssh .*disabled=(yes|no)')
|
|
winbox_pattern = re.compile(r'set winbox .*disabled=(yes|no)')
|
|
www_pattern = re.compile(r'set www .*disabled=(yes|no)')
|
|
www_ssl_pattern = re.compile(r'set www-ssl .*disabled=(yes|no)')
|
|
|
|
warnings = set()
|
|
info = set()
|
|
|
|
inside_services_block = False
|
|
|
|
for line in config_lines:
|
|
if services_pattern.match(line):
|
|
inside_services_block = True
|
|
continue
|
|
|
|
if inside_services_block:
|
|
if telnet_pattern.search(line):
|
|
telnet_disabled = telnet_pattern.search(line).group(1)
|
|
if telnet_disabled == "no":
|
|
warnings.add((True, "Telnet service is enabled. Turn it off, it's not safe to operate the equipment with it"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "Telnet service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if ftp_pattern.search(line):
|
|
ftp_disabled = ftp_pattern.search(line).group(1)
|
|
if ftp_disabled == "no":
|
|
warnings.add((True, "FTP service is enabled. If you don't use FTP, disable it and try not to store sensitive information there"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "FTP service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if api_pattern.search(line):
|
|
api_disabled = api_pattern.search(line).group(1)
|
|
if api_disabled == "no":
|
|
warnings.add((True, "API service is enabled. If not in use, it is recommended to disable it to prevent brute-force attacks"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "API service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if api_ssl_pattern.search(line):
|
|
api_ssl_disabled = api_ssl_pattern.search(line).group(1)
|
|
if api_ssl_disabled == "no":
|
|
warnings.add((True, "API-SSL service is enabled. If not in use, it is recommended to disable it to prevent brute-force attacks"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "API-SSL service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if ssh_pattern.search(line):
|
|
ssh_disabled = ssh_pattern.search(line).group(1)
|
|
if ssh_disabled == "no":
|
|
warnings.add((True, "SSH service is enabled. Filter access, you can use more secure key authentication"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "SSH service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if winbox_pattern.search(line):
|
|
winbox_disabled = winbox_pattern.search(line).group(1)
|
|
if winbox_disabled == "no":
|
|
warnings.add((True, "Winbox service is enabled. Winbox is constantly being attacked. Be careful with it, filter access"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "Winbox service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if www_pattern.search(line):
|
|
www_disabled = www_pattern.search(line).group(1)
|
|
if www_disabled == "no":
|
|
warnings.add((True, "HTTP service is enabled. Be careful with web-based control panels. Filter access"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "HTTP service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
if www_ssl_pattern.search(line):
|
|
www_ssl_disabled = www_ssl_pattern.search(line).group(1)
|
|
if www_ssl_disabled == "no":
|
|
warnings.add((True, "HTTPS service is enabled. Be careful with web-based control panels. Filter access"))
|
|
else:
|
|
info.add((False, Fore.YELLOW + Style.BRIGHT + "HTTPS service is " + Fore.WHITE + Style.BRIGHT + "disabled"))
|
|
|
|
return list(warnings), list(info)
|
|
|
|
def check_ntp_client(config_lines):
|
|
ntp_client_pattern = re.compile(r'^/system ntp client')
|
|
set_pattern = re.compile(r'set enabled=yes mode=unicast servers=(\S+)')
|
|
|
|
ntp_client_enabled = False
|
|
ntp_servers = []
|
|
|
|
for line in config_lines:
|
|
if ntp_client_pattern.match(line):
|
|
next_line_index = config_lines.index(line) + 1
|
|
if next_line_index < len(config_lines):
|
|
next_line = config_lines[next_line_index]
|
|
set_match = set_pattern.search(next_line)
|
|
if set_match:
|
|
ntp_client_enabled = True
|
|
ntp_servers = set_match.group(1).split(',')
|
|
|
|
if ntp_client_enabled:
|
|
return (True, f"NTP client is enabled. Servers: {', '.join(ntp_servers)}")
|
|
else:
|
|
return (False, "NTP client is not enabled or not using unicast mode.")
|
|
|
|
def check_romon_settings(config_lines):
|
|
romon_pattern = re.compile(r'^/tool romon')
|
|
set_pattern = re.compile(r'set .*enabled=yes')
|
|
|
|
inside_romon_block = False
|
|
|
|
for line in config_lines:
|
|
if romon_pattern.match(line):
|
|
inside_romon_block = True
|
|
continue
|
|
|
|
if inside_romon_block:
|
|
if set_pattern.search(line):
|
|
return (True, "ROMON is enabled. Be careful with this. If RouterOS is compromised, ROMON can be jumped to the next MikroTik hardware")
|
|
inside_romon_block = False
|
|
|
|
return (False, "No issues found with ROMON settings.")
|
|
|
|
def check_mac_telnet_server(config_lines):
|
|
mac_server_pattern = re.compile(r'^/tool mac-server')
|
|
allowed_interface_list_pattern = re.compile(r'set allowed-interface-list=all')
|
|
|
|
inside_mac_server_block = False
|
|
|
|
for line in config_lines:
|
|
if mac_server_pattern.match(line):
|
|
inside_mac_server_block = True
|
|
continue
|
|
|
|
if inside_mac_server_block:
|
|
if allowed_interface_list_pattern.search(line):
|
|
return (True, "MAC Telnet server is active on all interfaces. This reduces the security of the Winbox interface. Filter access")
|
|
inside_mac_server_block = False
|
|
|
|
return (False, "No issues found with MAC Telnet Server settings.")
|
|
|
|
def check_mac_winbox_server(config_lines):
|
|
mac_winbox_pattern = re.compile(r'^/tool mac-server mac-winbox')
|
|
inside_mac_winbox_block = False
|
|
|
|
for line in config_lines:
|
|
if mac_winbox_pattern.match(line):
|
|
inside_mac_winbox_block = True
|
|
continue
|
|
|
|
if inside_mac_winbox_block:
|
|
if 'set allowed-interface-list=all' in line:
|
|
return (True, "MAC Winbox Server is accessible on all interfaces. This reduces the security of the Winbox interface. Filter access")
|
|
inside_mac_winbox_block = False
|
|
|
|
return (False, "No issues found with MAC Winbox Server settings.")
|
|
|
|
def check_mac_ping_server(config_lines):
|
|
mac_ping_pattern = re.compile(r'^/tool mac-server ping')
|
|
inside_mac_ping_block = False
|
|
|
|
for line in config_lines:
|
|
if mac_ping_pattern.match(line):
|
|
inside_mac_ping_block = True
|
|
continue
|
|
|
|
if inside_mac_ping_block:
|
|
if 'set enabled=yes' in line:
|
|
return (True, "MAC Ping Server is enabled. Possible unwanted traffic")
|
|
inside_mac_ping_block = False
|
|
|
|
return (False, "No issues found with MAC Ping Server settings.")
|
|
|
|
def check_snmp_communities(config_lines):
|
|
snmp_pattern = re.compile(r'^/snmp community')
|
|
name_pattern = re.compile(r'name=(\S+)')
|
|
|
|
issues_found = False
|
|
snmp_issues = []
|
|
|
|
inside_snmp_block = False
|
|
|
|
for line in config_lines:
|
|
if snmp_pattern.match(line):
|
|
inside_snmp_block = True
|
|
continue
|
|
|
|
if inside_snmp_block:
|
|
name_match = name_pattern.search(line)
|
|
if name_match:
|
|
snmp_name = name_match.group(1)
|
|
if snmp_name.lower() in ["public", "private"]:
|
|
issues_found = True
|
|
snmp_issues.append(f"SNMP community '{snmp_name}' is set. Information Disclosure is possible. Please change SNMP community string")
|
|
|
|
if issues_found:
|
|
return (True, snmp_issues)
|
|
else:
|
|
return (False, "No issues found with SNMP settings.")
|
|
|
|
if __name__ == "__main__":
|
|
banner = '''
|
|
VVVVVVVV VVVVVVVV
|
|
V::::::V V::::::V
|
|
V::::::V V::::::V
|
|
V::::::V V::::::V
|
|
V:::::V V:::::Veeeeeeeeeeee xxxxxxx xxxxxxx
|
|
V:::::V V:::::ee::::::::::::eex:::::x x:::::x
|
|
V:::::V V:::::e::::::eeeee:::::ex:::::x x:::::x
|
|
V:::::V V:::::e::::::e e:::::ex:::::xx:::::x
|
|
V:::::V V:::::Ve:::::::eeeee::::::e x::::::::::x
|
|
V:::::V V:::::V e:::::::::::::::::e x::::::::x
|
|
V:::::V:::::V e::::::eeeeeeeeeee x::::::::x
|
|
V:::::::::V e:::::::e x::::::::::x
|
|
V:::::::V e::::::::e x:::::xx:::::x
|
|
V:::::V e::::::::eeeeeeee x:::::x x:::::x
|
|
V:::V ee:::::::::::::ex:::::x x:::::x
|
|
VVV eeeeeeeeeeeeexxxxxxx xxxxxxx
|
|
'''
|
|
|
|
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.0\n")
|
|
print(" " + Fore.YELLOW + Style.BRIGHT + "DISCLAIMER: 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")
|
|
print(" " + Fore.YELLOW + Style.BRIGHT + "CAUTION: for the tool to work correctly, use the RouterOS configuration from using the" + Fore.WHITE + Style.BRIGHT + " export verbose command\n")
|
|
|
|
|
|
args = parse_arguments()
|
|
config_lines = load_config(args.config)
|
|
config_lines = combine_multiline_statements(config_lines)
|
|
|
|
software_id, model, serial_number = extract_device_info(config_lines)
|
|
print(Fore.WHITE + Style.BRIGHT + "[+] Device Information:" + Style.RESET_ALL)
|
|
if software_id:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Software ID: " + Fore.WHITE + Style.BRIGHT + f"{software_id}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Software ID: " + Fore.WHITE + Style.BRIGHT + "unknown")
|
|
if model:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Model: " + Fore.WHITE + Style.BRIGHT + f"{model}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Model: " + Fore.WHITE + Style.BRIGHT + "unknown")
|
|
if serial_number:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Serial Number: " + Fore.WHITE + Style.BRIGHT + f"{serial_number}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Serial Number: " + Fore.WHITE + Style.BRIGHT + "unknown")
|
|
print("------------------------------")
|
|
|
|
interfaces = extract_interfaces(config_lines)
|
|
print(Fore.WHITE + Style.BRIGHT + "[+] Interfaces found:" + Style.RESET_ALL)
|
|
if interfaces:
|
|
for interface_type, interface_name in interfaces:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Type: " + Fore.WHITE + Style.BRIGHT + f"{interface_type}, " + Fore.YELLOW + Style.BRIGHT + "Name: " + Fore.WHITE + Style.BRIGHT + f"{interface_name}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No interfaces found.")
|
|
print("------------------------------")
|
|
|
|
ip_addresses = extract_ip_addresses(config_lines)
|
|
print(Fore.WHITE + Style.BRIGHT + "[+] IP Addresses found:" + Style.RESET_ALL)
|
|
if ip_addresses:
|
|
for ip_address, interface_name in ip_addresses:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] IP Address: " + Fore.WHITE + Style.BRIGHT + f"{ip_address}, " + Fore.YELLOW + Style.BRIGHT + "Interface: " + Fore.WHITE + Style.BRIGHT + f"{interface_name}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No IP addresses found.")
|
|
print("------------------------------")
|
|
|
|
discovery_protocol_status, discovery_protocol_message = check_discovery_protocols(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Discovery Protocols Check:" + Style.RESET_ALL)
|
|
if discovery_protocol_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{discovery_protocol_message}. Possible disclosure of sensitive information")
|
|
else:
|
|
print(f"[*] {discovery_protocol_message}")
|
|
print("------------------------------")
|
|
|
|
bandwidth_server_status, bandwidth_server_message = check_bandwidth_server(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Bandwidth Server Check:" + Style.RESET_ALL)
|
|
if bandwidth_server_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{bandwidth_server_message}. Possible unwanted traffic towards Bandwidth Server, be careful")
|
|
else:
|
|
print(f"[*] {bandwidth_server_message}")
|
|
print("------------------------------")
|
|
|
|
dns_settings_status, dns_settings_message = check_dns_settings(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] DNS Settings Check:" + Style.RESET_ALL)
|
|
if dns_settings_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{dns_settings_message}. This router is a DNS server, be careful")
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Router is acting as a DNS server and should restrict DNS traffic from external sources to prevent DNS Flood attacks")
|
|
else:
|
|
print(f"[*] {dns_settings_message}")
|
|
print("------------------------------")
|
|
|
|
ddns_status, ddns_message = check_ddns(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] DDNS Settings Check:" + Style.RESET_ALL)
|
|
if ddns_status:
|
|
print(Fore.YELLOW + Style.BRIGHT + f"[*] Warning: " + Fore.WHITE + Style.BRIGHT + f"{ddns_message}")
|
|
else:
|
|
print(f"[*] {ddns_message}")
|
|
print("------------------------------")
|
|
|
|
upnp_settings_status, upnp_settings_message = check_upnp_settings(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] UPnP Settings Check:" + Style.RESET_ALL)
|
|
if upnp_settings_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{upnp_settings_message}. The presence of active UPnP can be indicative of post-exploitation of a compromised RouterOS, and it can also be the cause of an external perimeter breach. Switch it off")
|
|
else:
|
|
print(f"[*] {upnp_settings_message}")
|
|
print("------------------------------")
|
|
|
|
ssh_status, ssh_message = check_ssh_settings(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] SSH Settings Check:" + Style.RESET_ALL)
|
|
if ssh_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{ssh_message}")
|
|
else:
|
|
print(f"[*] {ssh_message}")
|
|
print("------------------------------")
|
|
|
|
filter_rules = extract_firewall_rules(config_lines, 'filter')
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Firewall Filter Rules found:" + Style.RESET_ALL)
|
|
if filter_rules:
|
|
for rule in filter_rules:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Rule:" + Fore.WHITE + Style.BRIGHT + f" {rule}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No filter rules found.")
|
|
print(Style.BRIGHT + Fore.YELLOW + "[!] Don't forget to use the 'Drop All Other' rule on the external interface of the router. This helps protect the router from external perimeter breaches.")
|
|
print("------------------------------")
|
|
|
|
mangle_rules = extract_firewall_rules(config_lines, 'mangle')
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Firewall Mangle Rules found:" + Style.RESET_ALL)
|
|
if mangle_rules:
|
|
for rule in mangle_rules:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Rule:" + Fore.WHITE + Style.BRIGHT + f" {rule}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No mangle rules found.")
|
|
print(Style.BRIGHT + Fore.YELLOW + "[!] In some scenarios, using the mangle table can help save CPU resources.")
|
|
print("------------------------------")
|
|
|
|
nat_rules = extract_nat_rules(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Firewall NAT Rules found:" + Style.RESET_ALL)
|
|
if nat_rules:
|
|
for rule in nat_rules:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Rule:" + Fore.WHITE + Style.BRIGHT + f" {rule}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No NAT rules found.")
|
|
print("------------------------------")
|
|
|
|
raw_rules = extract_firewall_rules(config_lines, 'raw')
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Firewall Raw Rules found:" + Style.RESET_ALL)
|
|
if raw_rules:
|
|
for rule in raw_rules:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] Rule:" + Fore.WHITE + Style.BRIGHT + f" {rule}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No raw rules found.")
|
|
print("------------------------------")
|
|
|
|
routes = extract_routes(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] Routes:" + Style.RESET_ALL)
|
|
if routes:
|
|
for route in routes:
|
|
print(Fore.YELLOW + Style.BRIGHT + f"[*] Route:" + Fore.WHITE + Style.BRIGHT + f" {route}")
|
|
else:
|
|
print(Fore.YELLOW + Style.BRIGHT + "[*] No routes found.")
|
|
print("------------------------------")
|
|
|
|
socks_status, socks_message = check_socks_settings(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] SOCKS Settings Check:" + Style.RESET_ALL)
|
|
if socks_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{socks_message}")
|
|
else:
|
|
print(f"[*] {socks_message}")
|
|
print("------------------------------")
|
|
|
|
ip_services_warnings, ip_services_info = check_ip_services(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] IP Services Check:" + Style.RESET_ALL)
|
|
if ip_services_warnings:
|
|
for status, message in ip_services_warnings:
|
|
if status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{message}")
|
|
if ip_services_info:
|
|
for status, message in ip_services_info:
|
|
if not status:
|
|
print(Fore.YELLOW + Style.BRIGHT + f"[*] " + message)
|
|
print("------------------------------")
|
|
|
|
bpdu_guard_status, bpdu_guard_message = check_bpdu_guard(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] BPDU Guard Settings Check:" + Style.RESET_ALL)
|
|
if bpdu_guard_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{bpdu_guard_message}")
|
|
else:
|
|
print(f"[*] {bpdu_guard_message}")
|
|
print("------------------------------")
|
|
|
|
romon_status, romon_message = check_romon_settings(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] ROMON Settings Check:" + Style.RESET_ALL)
|
|
if romon_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{romon_message}")
|
|
else:
|
|
print(f"[*] {romon_message}")
|
|
print("------------------------------")
|
|
|
|
mac_telnet_status, mac_telnet_message = check_mac_telnet_server(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] MAC Telnet Server Check:" + Style.RESET_ALL)
|
|
if mac_telnet_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{mac_telnet_message}")
|
|
else:
|
|
print(f"[*] {mac_telnet_message}")
|
|
print("------------------------------")
|
|
|
|
mac_winbox_status, mac_winbox_message = check_mac_winbox_server(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] MAC Winbox Server Check:" + Style.RESET_ALL)
|
|
if mac_winbox_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{mac_winbox_message}")
|
|
else:
|
|
print(f"[*] {mac_winbox_message}")
|
|
print("------------------------------")
|
|
|
|
mac_ping_status, mac_ping_message = check_mac_ping_server(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] MAC Ping Server Check:" + Style.RESET_ALL)
|
|
if mac_ping_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{mac_ping_message}")
|
|
else:
|
|
print(f"[*] {mac_ping_message}")
|
|
print("------------------------------")
|
|
|
|
dhcp_snooping_status, dhcp_snooping_message = check_dhcp_snooping(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] DHCP Snooping Settings Check:" + Style.RESET_ALL)
|
|
if dhcp_snooping_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{dhcp_snooping_message}")
|
|
else:
|
|
print(f"[*] {dhcp_snooping_message}")
|
|
print("------------------------------")
|
|
|
|
ntp_client_status, ntp_client_message = check_ntp_client(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] NTP Client Settings Check:" + Style.RESET_ALL)
|
|
if ntp_client_status:
|
|
print(Fore.YELLOW + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{ntp_client_message}")
|
|
else:
|
|
print(f"[*] {ntp_client_message}")
|
|
print("------------------------------")
|
|
|
|
vrrp_auth_status, vrrp_auth_message, vrrp_auth_advice = check_vrrp_authentication(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] VRRP Security Check:" + Style.RESET_ALL)
|
|
if vrrp_auth_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{vrrp_auth_message}")
|
|
if vrrp_auth_advice:
|
|
print(Fore.YELLOW + Style.BRIGHT + f"[!] Advice: {vrrp_auth_advice}")
|
|
else:
|
|
print(f"[*] {vrrp_auth_message}")
|
|
print("------------------------------")
|
|
|
|
ospf_auth_status, ospf_passive_status = check_ospf_passive_setting(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] OSPF Security Check:" + Style.RESET_ALL)
|
|
if ospf_auth_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + "OSPF authentication is not configured. There is a risk of connecting an illegal OSPF speaker")
|
|
if ospf_passive_status:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + "OSPF passive interfaces are not configured. There is a risk of connecting an illegal OSPF speaker")
|
|
if not ospf_auth_status and not ospf_passive_status:
|
|
print(f"[*] No issues found with OSPF settings.")
|
|
print("------------------------------")
|
|
|
|
snmp_status, snmp_message = check_snmp_communities(config_lines)
|
|
print(Fore.GREEN + Style.BRIGHT + "[+] SNMP Security Check:" + Style.RESET_ALL)
|
|
if snmp_status:
|
|
for message in snmp_message:
|
|
print(Fore.RED + Style.BRIGHT + f"[*] Security Warning: " + Fore.WHITE + Style.BRIGHT + f"{message}")
|
|
else:
|
|
print(f"[*] {snmp_message}")
|
|
print("------------------------------")
|
|
|
|
# end of code |