initial release

This commit is contained in:
casterbyte 2024-06-03 08:45:40 +05:00
parent 74b4ac3fd8
commit 4ee7d2c75b
3 changed files with 1017 additions and 0 deletions

212
README.md Normal file
View file

@ -0,0 +1,212 @@
# Vex
---------------
Autonomous RouterOS configuration analyzer to find security issues. No networking required, only read configurations.
![](/banner/banner.png)
```
Vex: RouterOS Security Inspector
Designed for security engineers
Author: Magama Bazarov, <caster@exploit.org>
Pseudonym: Caster
Version: 1.0
```
# 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
-------------
# Operating
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.
Vex performs 23 search steps, these include:
```
1. Discovery Protocols Check: Checks whether discovery protocols (such as LLDP) are enabled on all interfaces;
2. Bandwidth Server Check: Checks whether the Bandwidth Server is enabled;
3. DNS Settings Check: Checks whether remote DNS queries are allowed;
4. DDNS Settings Check: Checks whether Dynamic Domain Name System (DDNS) is enabled;
5. UPnP Settings Check: Checks if UPnP (Universal Plug and Play) is enabled;
6. SSH Settings Check: Checks whether cryptographic settings for SSH are enabled;
7. Firewall Filter Rules Check: Retrieves and displays firewall filter rules;
8. Firewall Mangle Rules Check: Retrieves and displays firewall mangle rules;
9. Firewall NAT Rules Check: Retrieves and displays firewall NAT rules;
10. Firewall Raw Rules Check: Retrieves and displays Raw firewall rules;
11. Routes Check: Retrieves and displays routes;
12. SOCKS Settings Check: Checks if the SOCKS proxy is enabled;
13. IP Services Check: Checks the status of various IP services (Telnet, FTP, API, API-SSL, SSH, Winbox, HTTP, HTTPS);
14. BPDU Guard Settings Check: Checks the BPDU Guard settings for STP protection;
15. ROMON Settings Check: Checks if ROMON is enabled;
16. MAC Telnet Server Check: Checks the MAC Telnet Server settings;
17. MAC Winbox Server Check: Checks the MAC Winbox Server settings;
18. MAC Ping Server Check: Checks the MAC Ping Server settings;
19. DHCP Snooping Settings Check: Checks the DHCP Snooping settings to protect against DHCP attacks;
20. NTP Client Settings Check: Checks the NTP client settings;
21. VRRP Security Check: Checks the VRRP authentication settings;
22. OSPF Security Check: Checks OSPF settings for authentication and passive interfaces;
23. SNMP Security Check: Checks SNMP community settings for insecure values;
```
The tool will not only help can help improve the security of the device, but also help improve the quality of hardening.
> Warning: For a complete RouterOS check, it is recommended to export the configuration using `export verbose` to unload the entire configuration
--------
# Usage
```bash
caster@kali:~$ sudo apt install python3-colorama
caster@kali:~$ git clone https://github.com/casterbyte/Vex
caster@kali:~$ cd Vex/
caster@kali:~/Vex$ python3 vex.py --help
```
```
usage: vex.py [-h] --config CONFIG
options:
-h, --help show this help message and exit
--config CONFIG RouterOS configuration file name
```
To perform a configuration analysis, you must supply the RouterOS configuration file as input. This is done with the `--config` argument:
```bash
caster@kali:~/Vex$ python3 vex.py --config RouterOS.conf
```
Here is an example of the analyzed config:
```bash
[+] Device Information:
[*] Software ID: 7HD9-Z1QD
[*] Model: C52iG-5HaxD2HaxD
[*] Serial Number: HEB08WY6MPT
------------------------------
[+] Interfaces found:
[*] Type: bridge, Name: home
[*] Type: ethernet, Name: ether1
[*] Type: ethernet, Name: ether2
[*] Type: ethernet, Name: ether3
[*] Type: ethernet, Name: ether4
[*] Type: ethernet, Name: ether5
[*] Type: wifiwave2, Name: wifi1
[*] Type: wifiwave2, Name: wifi2
[*] Type: vrrp, Name: vrrp1
[*] Type: wireguard, Name: wg-outerspace
[*] Type: ethernet, Name: switch1
[*] Type: list, Name: all
[*] Type: list, Name: none
[*] Type: list, Name: dynamic
[*] Type: list, Name: static
[*] Type: list, Name: LAN
[*] Type: lte, Name: default
[*] Type: macsec, Name: default
------------------------------
[+] IP Addresses found:
[*] IP Address: 192.168.0.254/24, Interface: home
[*] IP Address: 10.10.101.71/32, Interface: wg-outerspace
[*] IP Address: 192.168.0.11/24, Interface: vrrp1
------------------------------
[+] Discovery Protocols Check:
[*] Security Warning: detected set discover-interface-list=all. Possible disclosure of sensitive information
------------------------------
[+] Bandwidth Server Check:
[*] Security Warning: detected active Bandwidth Server with 'enabled=yes' setting. Possible unwanted traffic towards Bandwidth Server, be careful
------------------------------
[+] DNS Settings Check:
[*] Security Warning: detected directive 'set allow-remote-requests=yes'. This router is a DNS server, be careful
[*] Router is acting as a DNS server and should restrict DNS traffic from external sources to prevent DNS Flood attacks
------------------------------
[+] DDNS Settings Check:
[*] Warning: DDNS is enabled. If not specifically used, it is recommended to disable it.
------------------------------
[+] UPnP Settings Check:
[*] Security Warning: detected directive 'set enabled=yes'. 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
------------------------------
[+] SSH Settings Check:
[*] Security Warning: detected 'strong-crypto=no'. It is recommended to enable strong cryptographic ciphers for SSH
------------------------------
[+] Firewall Filter Rules found:
[*] Rule: add action=accept chain=input comment="Allow Established & Related, Drop Invalid" connection-state=established,related
[*] Rule: add action=drop chain=input connection-state=invalid
[*] Rule: add action=accept chain=forward connection-state=established,related
[*] Rule: add action=drop chain=forward connection-state=invalid
[!] 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.
------------------------------
[+] Firewall Mangle Rules found:
[*] No mangle rules found.
[!] In some scenarios, using the mangle table can help save CPU resources.
------------------------------
[+] Firewall NAT Rules found:
[*] Rule: add action=masquerade chain=srcnat comment="Access to Internet" out-interface=wg-outerspace
------------------------------
[+] Firewall Raw Rules found:
[*] No raw rules found.
------------------------------
[+] Routes:
[*] Route: add distance=1 dst-address=111.111.111.111/32 gateway=192.168.1.1
[*] Route: add dst-address=192.168.54.0/24 gateway=192.168.0.253
[*] Route: add dst-address=0.0.0.0/0 gateway=wg-outerspace
------------------------------
[+] SOCKS Settings Check:
[*] Security Warning: detected directive 'set enabled=yes'. SOCKS proxy can be used as a pivoting tool to access the internal network
------------------------------
[+] IP Services Check:
[*] Security Warning: SSH service is enabled. Filter access, you can use more secure key authentication
[*] Security Warning: API-SSL service is enabled. If not in use, it is recommended to disable it to prevent brute-force attacks
[*] Security Warning: Winbox service is enabled. Winbox is constantly being attacked. Be careful with it, filter access
[*] Security Warning: Telnet service is enabled. Turn it off, it's not safe to operate the equipment with it
[*] Security Warning: API service is enabled. If not in use, it is recommended to disable it to prevent brute-force attacks
[*] Security Warning: HTTP service is enabled. Be careful with web-based control panels. Filter access
[*] Security Warning: HTTPS service is enabled. Be careful with web-based control panels. Filter access
[*] Security Warning: FTP service is enabled. If you don't use FTP, disable it and try not to store sensitive information there
------------------------------
[+] BPDU Guard Settings Check:
[*] Security Warning: detected 'bpdu-guard=no'. It is recommended to enable BPDU Guard to protect STP from attacks
------------------------------
[+] ROMON Settings Check:
[*] Security Warning: ROMON is enabled. Be careful with this. If RouterOS is compromised, ROMON can be jumped to the next MikroTik hardware
------------------------------
[+] MAC Telnet Server Check:
[*] Security Warning: MAC Telnet server is active on all interfaces. This reduces the security of the Winbox interface. Filter access
------------------------------
[+] MAC Winbox Server Check:
[*] Security Warning: MAC Winbox Server is accessible on all interfaces. This reduces the security of the Winbox interface. Filter access
------------------------------
[+] MAC Ping Server Check:
[*] Security Warning: MAC Ping Server is enabled. Possible unwanted traffic
------------------------------
[+] DHCP Snooping Settings Check:
[*] Security Warning: detected 'dhcp-snooping=no'. It is recommended to enable DHCP Snooping to protect the network from DHCP attacks (DHCP Spoofing)
------------------------------
[+] NTP Client Settings Check:
[*] Security Warning: NTP client is enabled. Servers: 0.pool.ntp.org, 1.pool.ntp.org
------------------------------
[+] VRRP Security Check:
[*] No issues found with VRRP authentication settings
------------------------------
[+] OSPF Security Check:
[*] Security Warning: OSPF authentication is not configured. There is a risk of connecting an illegal OSPF speaker
[*] Security Warning: OSPF passive interfaces are not configured. There is a risk of connecting an illegal OSPF speaker
------------------------------
[+] SNMP Security Check:
[*] Security Warning: SNMP community 'public' is set. Information Disclosure is possible. Please change SNMP community string
[*] Security Warning: SNMP community 'private' is set. Information Disclosure is possible. Please change SNMP community string
------------------------------
```

BIN
banner/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

805
vex.py Normal file
View file

@ -0,0 +1,805 @@
#!/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