mirror of
https://github.com/casterbyte/Sara.git
synced 2025-07-10 14:44:30 +02:00
sara v1.0
This commit is contained in:
parent
0833d202d5
commit
d9f23b2998
5 changed files with 508 additions and 656 deletions
240
README.md
240
README.md
|
@ -1,187 +1,103 @@
|
||||||
# Vex: RouterOS Security Inspector
|
# Sara: RouterOS Security Inspector
|
||||||
|
|
||||||
|
It is a autonomous RouterOS configuration analyzer for finding security issues on MikroTik hardware.
|
||||||
Autonomous RouterOS configuration analyzer to find security issues. No networking required, only read configurations.
|
|
||||||
|
|
||||||

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