diff --git a/README.md b/README.md index 7cc233a..c4b74e6 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,366 @@ -# Sara: RouterOS Security Inspector (A new version is on the way) +# Sara: RouterOS Security Inspector -It is a autonomous RouterOS configuration analyzer for finding security issues on MikroTik hardware. +RouterOS configuration analyzer to find security misconfigurations and vulnerabilities. + +![](/banner/banner.png) ``` - _____ - / ____| - | (___ __ _ _ __ __ _ - \___ \ / _` | '__/ _` | - ____) | (_| | | | (_| | - |_____/ \__,_|_| \__,_| v1.0 +RouterOS Security Inspector. For security engineers +Operates remotely using SSH, designed to evaluate RouterOS security - RouterOS Security Inspector. Designed for security professionals - - Author: Magama Bazarov, +Author: Magama Bazarov, +Alias: Caster +Version: 1.1 +Codename: Judge ``` +# Disclaimer + +**Sara** is developed for security professionals to audit their own devices. **Unauthorized use may be illegal.** + +The author does not take any responsibility for the misuse of this tool, including, but not limited to: + +- Use for unauthorized access, hacking, or any form of cyberattack; + +- Misinterpretation of results leading to undesirable configuration changes; + +- Legal repercussions arising from the misuse of **Sara** for purposes other than security auditing. + +# Sara is not an attack tool + +**Sara does not bypass authentication, exploit vulnerabilities, or alter RouterOS configurations.** It works in **read-only mode**, requiring no administrative privileges. + +If you are unsure about the interpretation of the analysis results, consult an experienced network engineer before making any decisions! + +# Legal Restrictions + +Before use, ensure that your device auditing complies with your organization's local laws and policies. + +- You are solely responsible for your use of Sara; +- Use it only on your devices or with the owner's permission; +- Do not use Sara on other people's networks without the owner's explicit consent - this may violate computer security laws! + # Mechanism -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: +**Sara** uses [netmiko](https://github.com/ktbyers/netmiko) to remotely connect via SSH to RouterOS devices. It executes RouterOS system commands to extract configuration data and analyze it for potential vulnerabilities and signs of compromise. The user connects to the hardware himself using Sara by entering his username and password. Sara executes exactly `print` based commands, thus not changing the configuration of your hardware in any way. So, by the way, you can even use an RO-only account if you want to. +Sara does not use any exploits, payloads or bruteforce attacks. All RouterOS security analysis here is based on pure configuration analysis. -1. **SMB Service Detection**: Identifies if the SMB service is enabled, which may expose the device to vulnerabilities like CVE-2018-7445; +## What exactly is Sara checking for? -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; +1. **SMB protocol activity** – determines whether SMB is enabled, which may be vulnerable to CVE-2018-7445; -3. **UPnP Status Check**: Detects if Universal Plug and Play (UPnP) is enabled, which can open up the network to unauthorized access; -4. **WiFi Configuration Review**: Analyzes WiFi settings for vulnerabilities, including insecure authentication methods, enabled WPS, and PMKID exposure; -5. **DNS Configuration Review**: Checks DNS settings, looking for remote DNS requests being allowed and the absence of DNS over HTTPS (DoH); -6. **Dynamic DNS (DDNS) Status**: Identifies if DDNS is enabled, which might expose your network to unnecessary risks; -7. **Power over Ethernet (PoE) Settings Review**: Analyzes PoE configurations to ensure power management does not pose risks to connected devices; -8. **Protected RouterBOOT Check**: Ensures that Protected RouterBOOT is enabled, preventing unauthorized changes to the bootloader settings; -9. **SOCKS Proxy Detection**: Identifies if a SOCKS proxy is enabled, which could indicate a compromised device; -10. **Bandwidth Server Check**: Detects if the Bandwidth Server is enabled, which could lead to unwanted traffic on the network; -11. **OSPF Interface Analysis**: Examines OSPF interface settings for missing passive mode and authentication, both of which are crucial for securing OSPF communications; -12. **VRRP Interface Analysis**: Checks for VRRP interfaces that lack proper authentication, potentially exposing the network to Man-in-the-Middle (MITM) attacks; -13. **Discovery Protocols Configuration**: Reviews the settings for network discovery protocols, ensuring they are limited to trusted interfaces; -14. **User Password Policy Check**: Analyzes user password policies to ensure they meet security best practices; -15. **SSH Strong Crypto Detection**: Detects if SSH is configured with weak cryptography, providing advice on how to secure it; -16. **Connection Tracking Status**: Reviews the connection tracking settings, advising on when it might be beneficial to disable it; -17. **RoMON Status Check**: Detects if RoMON is enabled, highlighting the need for careful management to prevent unauthorized access to other RouterOS devices; -18. **MAC Server Settings Review**: Analyzes MAC Server and MAC Winbox settings, recommending restrictions to enhance security; -19. **SNMP Analysis**: Identifies the use of default or weak SNMP community strings, which could lead to information gathering attacks; -20. **Port Forwarding Rules Check**: Detects port forwarding rules (dst-nat), warning about potential exposure of internal services to the internet. +2. **Check the status of RMI interfaces** – identifies active management services (Telnet, FTP, Winbox, API, HTTP/HTTPS); -# Usage +3. **Wi-Fi Security Check** – determines whether WPS and PMKID support are enabled, which can be used in WPA2-PSK attacks; -To install Sara: + > At the moment, this check has minor stability issues, as different versions of RouterOS have different variations of Wi-Fi configurations. Keep that in mind, but feel free to make an issue, we'll look into it.s + +4. **Check UPnP** – determines whether UPnP is enabled, which can automatically forward ports and threaten network security; + +5. **Check DNS settings** – detects whether `allow-remote-requests`, which makes the router a DNS server, is enabled; + +6. **Check DDNS** – determines whether dynamic DNS is enabled, which can reveal the real IP address of the device; + +7. **PoE Test** – checks if PoE is enabled, which may cause damage to connected devices; + +8. **Check RouterBOOT security** – determines if RouterBOOT bootloader protection is enabled; + +9. **Check SOCKS Proxy** – identifies an active SOCKS Proxy that could be used by an attacker for pivoting, as well as indicating a potential compromise of the device. + +10. **Bandwidth Server Test (BTest)** – determines whether a bandwidth server is enabled that can be used for a Flood attack by the attacker; + +11. **Check discovery protocols** – determines whether CDP, LLDP, MNDP that can disclose network information are active; + +12. **Check minimum password length** – determines whether the `minimum-password-length` parameter is set to prevent the use of weak passwords; + +13. **SSH Check** – analyzes SSH settings, including the use of strong-crypto and Port Forwarding permission; + +14. **Check Connection Tracking** – determines whether Connection Tracking is enabled, which can increase the load and open additional attack vectors; + +15. **RoMON check** – detects RoMON activity, which allows you to manage devices at Layer 2; + +16. **Check Winbox MAC Server** – analyzes access by MAC address via Winbox and Telnet, which can be a vulnerability on a local network; + +17. **Check SNMP** – detects the use of weak SNMP community strings (`public`, `private`); + +18. **Check NAT rules** – analyzes port forwarding (`dst-nat`, `netmap`) that may allow access to internal services from the outside; + +19. **Check network access to RMI** – determines whether access to critical services (API, Winbox, SSH) is restricted to trusted IPs only; + +20. **Check RouterOS version** – analyzes the current version of RouterOS and compares it to known vulnerable versions; + +21. **RouterOS Vulnerability Check** – checks the RouterOS version against the CVE database and displays a list of known vulnerabilities; + +22. **“Keep Password” in Winbox** – warns of potential use of the “Keep Password” feature + +23. **Check default usernames** – defines the use of standard logins (`admin`, `engineer`, `test`, `mikrotik`); + +24. **Checking the schedulers** – detects malicious tasks that can load remote scripts, perform hidden reboots, or run too often; + +25. **Check static DNS records** – Analyzes static DNS records that can be used for phishing and MITM attacks. + +## A breakdown of one technique + +Sara analyzes MikroTik RouterOS configuration by sending commands via SSH and interpreting the results. Let's consider a basic example of checking an SMB service that may be vulnerable to CVE-2018-7445. + +```python +# SMB Check +def check_smb(connection): + separator("Checking SMB Service") + command = "/ip smb print" + output = connection.send_command(command) + + if "enabled: yes" in output: + print(Fore.RED + Style.BRIGHT + "[*] CAUTION: SMB service is enabled! Are you sure you want it? Also, avoid CVE-2018-7445") + else: + print(Fore.GREEN + "[+] SMB is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") +``` + +1. Sending a command to the router: command = `/ip smb print` - queries the status of the SMB service; +2. `output = connection.send_command(command)` - executes the command via SSH and receives its output, writing it to the variable memory; +3. If the output contains the string `“enabled: yes”`, then SMB is enabled and the script displays a warning. + +The same principle works for the other checks. Only read the configuration and then analyze it in detail. + +# Vulnerability Search (CVE) + +Sara performs a security analysis of RouterOS by checking the current firmware version and checking it against a database of known vulnerabilities (CVEs). This process identifies critical vulnerabilities that can be exploited by attackers to compromise the device. + +## But how does it work? + +1. Sara extracts the current RouterOS version from the device using the system command (`/system resource print`) + +2. The check is performed using the built-in `cve_lookup.py` module, which stores a dictionary of known RouterOS vulnerabilities. This module is based on data obtained [from the MITRE CVE database](https://cve.mitre.org/data/downloads) and contains: + + - CVE ID; + - Vulnerability Description; + - Range of vulnerable RouterOS versions + + Sara analyzes the version of the device and determines if it falls into the list of vulnerable versions. + +3. If the RouterOS version contains known vulnerabilities, Sara displays a warning indicating: + + - CVE ID; + - Description of the vulnerability and potential risks. + +## Specifics of checking + +- Sara does not verify real-world exploitation of vulnerabilities. It only cross-references the RouterOS version against publicly available CVE databases; +- If the device is running an older version of RouterOS, but vulnerable services have been manually disabled, some warnings may be false positives; +- The CVE database is updated over time, so it is recommended to keep an eye out for current patches from MikroTik yourself. + +# How to use + +You have two ways to install Sara: + +1. In Kali Linux: -1. Kali Linux ```bash caster@kali:~$ sudo apt update && sudo apt install sara caster@kali:~$ sara -h ``` -2. Via Python3 + +2. Manually using Git and Python: + ```bash -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 +~$ sudo apt install git python3-colorama python3-netmiko python3-packaging +~$ git clone https://github.com/casterbyte/Sara +~$ cd Sara +~/Sara$ sudo python3 setup.py install +~$ sara -h +``` + +## Trigger Arguments (CLI Options) + +Sara supports the following command line options: + +```bash +usage: sara.py [-h] --ip IP --username USERNAME --password PASSWORD [--port PORT] + +options: + -h, --help show this help message and exit + --ip IP The address of your MikroTik router + --username USERNAME SSH username (RO account can be used) + --password PASSWORD SSH password + --port PORT SSH port (default: 22) +``` + +1. `--ip` - this argument specifies the IP address of the MikroTik device to which Sara is connecting; + +2. `--username` - the SSH username that will be used to connect. Sara supports only authorized access; + + > You can use read-only (RO) accounts. Sara does not make configuration changes, so you do not need `write` or `full` level access. + +3. `--password` - password for SSH authentication; + +4. `--port` - allows you to specify a non-standard SSH port for connection. The default is **22**, but if you have changed the SSH port number, it must be specified manually. + +# Sara's Launch + +```bash +caster@kali:~$ python3 sara.py --ip 192.168.88.1 --username admin --password mypass _____ / ____| | (___ __ _ _ __ __ _ \___ \ / _` | '__/ _` | ____) | (_| | | | (_| | - |_____/ \__,_|_| \__,_| v1.0 + |_____/ \__,_|_| \__,_| - RouterOS Security Inspector. Designed for security professionals + RouterOS Security Inspector. For security engineers + Operates remotely using SSH, designed to evaluate RouterOS security Author: Magama Bazarov, + Alias: Caster + Version: 1.1 + Codename: Judge + Documentation & Usage: https://github.com/casterbyte/Sara - It's recommended to provide a configuration file exported using the 'export verbose' command + [!] DISCLAIMER: Use this tool only for auditing your own devices. + [!] Unauthorized use on third-party systems is ILLEGAL. + [!] The author is not responsible for misuse. -usage: sara [-h] --config-file CONFIG_FILE -sara: error: the following arguments are required: --config-file + WARNING: This tool is for security auditing of YOUR OWN RouterOS devices. + Unauthorized use may be illegal. Proceed responsibly. + + Do you wish to proceed? [yes/no]: yes +[*] Connecting to RouterOS at 192.168.88.1:22 +[*] Connection successful! +======================================== +[*] Checking RouterOS Version +[+] Detected RouterOS Version: 7.15.3 +[+] No known CVEs found for this version. +======================================== +[*] Checking SMB Service +[+] SMB is disabled. No risk detected. +[+] No issues found. +======================================== +[*] Checking RMI Services +[!] ALERT: TELNET is ENABLED! This is a high security risk. + - Account passwords can be intercepted +[!] ALERT: FTP is ENABLED! This is a high security risk. + - Are you sure you need FTP? +[!] ALERT: HTTP is ENABLED! This is a high security risk. + - Account passwords can be intercepted +[+] OK: SSH is enabled. Good! + - Are you using strong passwords and SSH keys for authentication? +[!] CAUTION: HTTP-SSL is enabled. + - HTTPS detected. Ensure it uses a valid certificate and strong encryption. +[!] CAUTION: API is enabled. + - RouterOS API is vulnerable to a bruteforce attack. If you need it, make sure you have access to it. +[!] CAUTION: WINBOX is enabled. +[!] CAUTION: If you're using 'Keep Password' in Winbox, your credentials may be stored in plaintext! + - If your PC is compromised, attackers can extract saved credentials. + - Consider disabling 'Keep Password' to improve security. +[!] CAUTION: API-SSL is enabled. + - RouterOS API is vulnerable to a bruteforce attack. If you need it, make sure you have access to it. +======================================== +[*] Checking Default Usernames +[!] CAUTION: Default username 'admin' detected! Change it to a unique one. +[!] CAUTION: Default username 'engineer' detected! Change it to a unique one. +======================================== +[*] Checking network access to RMI +[!] CAUTION: TELNET has no IP restriction set! Please restrict access. +[!] CAUTION: FTP has no IP restriction set! Please restrict access. +[!] CAUTION: WWW has no IP restriction set! Please restrict access. +[+] OK! SSH is restricted to: 192.168.88.0/24 +[!] CAUTION: WWW-SSL has no IP restriction set! Please restrict access. +[!] CAUTION: API has no IP restriction set! Please restrict access. +[+] OK! WINBOX is restricted to: 192.168.88.0/24 +[!] CAUTION: API-SSL has no IP restriction set! Please restrict access. +======================================== +[*] Checking Wi-Fi Security +[+] All Wi-Fi interfaces and security profiles have secure settings. +[*] If you use WPA-PSK or WPA2-PSK, take care of password strength. So that the handshake cannot be easily brute-forced. +[+] No issues found. +======================================== +[*] Checking UPnP Status +[+] UPnP is disabled. No risk detected. +[+] No issues found. +======================================== +[*] Checking DNS Settings +[!] CAUTION: Router is acting as a DNS server! This is just a warning. The DNS port on your RouterOS should not be on the external interface. +======================================== +[*] Checking DDNS Settings +[+] DDNS is disabled. No risk detected. +[+] No issues found. +======================================== +[*] Checking PoE Status +[!] CAUTION: PoE is enabled on ether1. Ensure that connected devices support PoE to prevent damage. +======================================== +[*] Checking RouterBOOT Protection +[!] CAUTION: RouterBOOT protection is disabled! This can allow unauthorized firmware changes and password resets via Netinstall. +======================================== +[*] Checking SOCKS Proxy Status +[+] SOCKS proxy is disabled. No risk detected. +[+] No issues found. +======================================== +[*] Checking Bandwidth Server Status +[+] Bandwidth server is disabled. No risk detected. +[+] No issues found. +======================================== +[*] Checking Neighbor Discovery Protocols +[+] No security risks found in Neighbor Discovery Protocol settings. +[+] No issues found. +======================================== +[*] Checking Password Policy +[!] CAUTION: No minimum password length is enforced! The length of the created passwords must be taken into account. +======================================== +[*] Checking SSH Security +[!] CAUTION: SSH Dynamic Port Forwarding is enabled! This could indicate a RouterOS compromise, and SSH DPF could also be used by an attacker as a pivoting technique. +[!] CAUTION: strong-crypto is disabled! It is recommended to enable it to enhance security. This will: + - Use stronger encryption, HMAC algorithms, and larger DH primes; + - Prefer 256-bit encryption, disable null encryption, prefer SHA-256; + - Disable MD5, use 2048-bit prime for Diffie-Hellman exchange; +======================================== +[*] Checking Connection Tracking +[+] Connection Tracking is properly configured. +[+] No issues found. +======================================== +[*] Checking RoMON Status +[+] RoMON is disabled. No risk detected. +[+] No issues found. +======================================== +[*] Checking Winbox MAC Server Settings +[+] MAC Winbox are properly restricted. +[+] MAC Telnet are properly restricted. +[+] MAC Ping are properly restricted. +======================================== +[*] Checking SNMP Community Strings +[+] SNMP community strings checked. No weak values detected. +[+] No issues found. +======================================== +[*] Checking Firewall NAT Rules +[+] No Destination NAT (dst-nat/netmap) rules detected. No risks found. +[+] No issues found. +======================================== +[*] Checking for Malicious Schedulers +[*] Checking: 'Unknown' → +[+] No malicious schedulers detected. +======================================== +[*] Checking Static DNS Entries +[!] WARNING: The following static DNS entries exist: + - dc01.myownsummer.org → 192.168.88.71 + - fake.example.com → 192.168.88.100 +[*] Were you the one who created those static DNS records? Make sure. +[*] Attackers during RouterOS post-exploitation like to tamper with DNS record settings, for example, for phishing purposes. +======================================== +[*] Checking Router Uptime +[*] Router Uptime: 64 days, 2 hours, 23 minutes + +[*] Disconnected from RouterOS (192.168.88.1:22) +[*] All checks have been completed. Security inspection completed in 3.03 seconds + +[*] Remember: Security is a process, not a state. ``` -Sara uses just one argument, it is the name/path to the RouterOS configuration file. The tool supports `.rsc` files. +# Copyright -# Work Example - -```bash -caster@kali:~$ sara --config-file routeros.rsc - - _____ - / ____| - | (___ __ _ _ __ __ _ - \___ \ / _` | '__/ _` | - ____) | (_| | | | (_| | - |_____/ \__,_|_| \__,_| v1.0 - - RouterOS Security Inspector. Designed for security professionals - - Author: Magama Bazarov, - - It's recommended to provide a configuration file exported using the 'export verbose' command - -[*] Analyzing the configuration file: forsara.rsc (34.53 KB) - -[+] Device Information - [*] RouterOS Version: X.XX.X - [*] Model: XXXX-XXXXXXXXXX - [*] Serial Number: XXXXXXXXXXX - -[+] Checking RMI Services - [!] Warning: The following RMI services are enabled and may be unsafe: telnet, ftp, www. - [!] Caution: The following RMI services are enabled: ssh, www-ssl, winbox. - [!] Note: The following RMI services are enabled and might be susceptible to brute force attacks: api, api-ssl. - [*] Solution: Disable the above RMI services if they are not required for security. - [*] Tip: Restrict access to enabled services to trusted subnets only. - -[+] Checking UPnP - [!] Warning: UPnP is enabled. This can expose your network to various security risks, including unauthorized access. - [*] Solution: Disable UPnP unless absolutely necessary, and ensure your firewall is properly configured. - -[+] Checking WiFi Settings - [!] Warning: WPS is enabled on interface wifi1. WPS Pin code can be cracked, brute-forced. - [!] Warning: PMKID is enabled on interface wifi1. PMKID is easy to bruteforce. - [!] Warning: Interface wifi1 is using insecure authentication method 'wpa2-psk'. WPA/WPA2-PSK are long gone, use WPA2-E, WPA3. - -[+] Checking DNS Settings - [!] Warning: Router is configured to allow remote DNS requests. Close the DNS UDP/53 port from the Internet. - [!] Note: DNS over HTTPS (DoH) is not configured. Consider configuring a DoH server for improved privacy. - -[+] Checking PoE Settings - [!] Warning: PoE is enabled on interface ether1 with setting 'auto-on'. This could supply power to connected devices and potentially damage them if not properly managed. - -[+] Checking Protected RouterBOOT - [!] Warning: Protected RouterBOOT is disabled. This may allow unauthorized changes to the bootloader settings. - [*] Solution: Enable Protected RouterBOOT to prevent unauthorized access to the bootloader. - -[+] Checking SOCKS Proxy - [!] Warning: SOCKS Proxy is enabled. The presence of SOCKS may indicate that the device has been compromised. - [*] Solution: Disable SOCKS Proxy if it is not required. - -[+] Checking User Password Policies - [!] Warning: Password policies are not properly configured. Both minimum password categories and minimum password length are set to 0. - [*] 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. - -[+] Checking Connection Tracking - [!] Connection Tracking is currently set to 'auto'. - [*] 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. - -[+] Checking MAC Server Settings - [!] Warning: MAC Server is allowed on all interfaces (allowed-interface-list=all). This compromises the security of the Winbox interface. - [!] Warning: MAC Winbox is allowed on all interfaces (allowed-interface-list=all). This compromises the security of the Winbox interface. - [!] Warning: MAC Ping is enabled. Possible unwanted traffic. - [*] Solution: Limit MAC server and MAC Winbox to specific trusted interfaces, and disable MAC Ping if it is not required. - -[+] Checking SNMP Communities - [!] Warning: SNMP community 'public' is in use. Possible Information Gathering attack vector by bruteforcing community string. - [!] Warning: SNMP community 'private' is in use. Possible Information Gathering attack vector by bruteforcing community string. - [*] Solution: Change the SNMP community names to something more secure, and restrict SNMP access to trusted IP addresses only. -``` +Copyright (c) 2025 Magama Bazarov. This project is licensed under the Apache 2.0 License # Outro -Tool will be maintained and updated, suggestions: caster@exploit.org - - +MikroTik devices are widely used around the world. Sara is designed to help engineers improve security - use it wisely. +E-mail for contact: caster@exploit.org diff --git a/banner/banner.png b/banner/banner.png new file mode 100644 index 0000000..9c18be4 Binary files /dev/null and b/banner/banner.png differ diff --git a/cve_lookup.py b/cve_lookup.py new file mode 100644 index 0000000..a66cab6 --- /dev/null +++ b/cve_lookup.py @@ -0,0 +1,68 @@ +# Sara's helper module for CVE search based on RouterOS version analysis +# Downloaded and adapted from: https://cve.mitre.org/data/downloads +# The CVE search thanks to this module is passive and does not involve sending various payloads, launching exploits and so on + +cve_routeros_database = { + "CVE-2008-0680": "SNMPd in MikroTik RouterOS 3.2 and earlier allows remote attackers to cause a denial of service (daemon crash) via a crafted SNMP SET request.", + "CVE-2008-6976": "MikroTik RouterOS 3.x through 3.13 and 2.x through 2.9.51 allows remote attackers to modify Network Management System (NMS) settings via a crafted SNMP set request.", + "CVE-2012-6050": "The winbox service in MikroTik RouterOS 5.15 and earlier allows remote attackers to cause a denial of service (CPU consumption), read the router version, and possibly have other impacts via a request to download the router's DLLs or plugins, as demonstrated by roteros.dll", + "CVE-2015-2350": "Cross-site request forgery (CSRF) vulnerability in MikroTik RouterOS 5.0 and earlier allows remote attackers to hijack the authentication of administrators for requests that change the administrator password via a request in the status page to /cfg.", + "CVE-2017-17537": "MikroTik RouterBOARD v6.39.2 and v6.40.5 allows an unauthenticated remote attacker to cause a denial of service by connecting to TCP port 53 and sending data that begins with many '\0' characters", + "CVE-2017-17538": "MikroTik v6.40.5 devices allow remote attackers to cause a denial of service via a flood of ICMP packets.", + "CVE-2017-6297": "The L2TP Client in MikroTik RouterOS versions 6.83.3 and 6.37.4 does not enable IPsec encryption after a reboot, which allows man-in-the-middle attackers to view transmitted data unencrypted and gain access to networks on the L2TP server by monitoring the packets for the transmitted data and obtaining the L2TP secret", + "CVE-2017-6444": "The MikroTik Router hAP Lite 6.25 has no protection mechanism for unsolicited TCP ACK packets in the case of a fast network connection", + "CVE-2017-7285": "A vulnerability in the network stack of MikroTik Version 6.38.5 released 2017-03-09 could allow an unauthenticated remote attacker to exhaust all available CPU via a flood of TCP RST packets", + "CVE-2017-8338": "A vulnerability in MikroTik Version 6.38.5 could allow an unauthenticated remote attacker to exhaust all available CPU via a flood of UDP packets on port 500 (used for L2TP over IPsec)", + "CVE-2018-10066": "An issue was discovered in MikroTik RouterOS 6.41.4. Missing OpenVPN server certificate verification allows a remote unauthenticated attacker capable of intercepting client traffic to act as a malicious OpenVPN server. This may allow the attacker to gain access to the client's internal network", + "CVE-2018-10070": "A vulnerability in MikroTik Version 6.41.4 could allow an unauthenticated remote attacker to exhaust all available CPU and all available RAM by sending a crafted FTP request on port 21 that begins with many '\0' characters", + "CVE-2018-1157": "Mikrotik RouterOS before 6.42.7 and 6.40.9 is vulnerable to a memory exhaustion vulnerability. An authenticated remote attacker can crash the HTTP server and in some circumstances reboot the system via a crafted HTTP POST request.", + "CVE-2018-1158": "Mikrotik RouterOS before 6.42.7 and 6.40.9 is vulnerable to a stack exhaustion vulnerability. An authenticated remote attacker can crash the HTTP server via recursive parsing of JSON.", + "CVE-2018-14847": "MikroTik RouterOS through 6.42 allows unauthenticated remote attackers to read arbitrary files and remote authenticated attackers to write arbitrary files due to a directory traversal vulnerability in the WinBox interface.", + "CVE-2018-7445": "A buffer overflow was found in the MikroTik RouterOS SMB service when processing NetBIOS session request messages. Remote attackers with access to the service can exploit this vulnerability and gain code execution on the system. The overflow occurs before authentication takes place", + "CVE-2019-13074": "A vulnerability in the FTP daemon on MikroTik routers through 6.44.3 could allow remote attackers to exhaust all available memory, causing the device to reboot because of uncontrolled resource management.", + "CVE-2019-15055": "MikroTik RouterOS through 6.44.5 and 6.45.x through 6.45.3 improperly handles the disk name, which allows authenticated users to delete arbitrary files. Attackers can exploit this vulnerability to reset credential storage, which allows them access to the management interface as an administrator without authentication", + "CVE-2019-16160": "An integer underflow in the SMB server of MikroTik RouterOS before 6.45.5 allows remote unauthenticated attackers to crash the service.", + "CVE-2019-3924": "MikroTik RouterOS before 6.43.12 (stable) and 6.42.12 (long-term) is vulnerable to an intermediary vulnerability. The software will execute user defined network requests to both WAN and LAN clients. A remote unauthenticated attacker can use this vulnerability to bypass the router's firewall or for general network scanning activities.", + "CVE-2019-3943": "MikroTik RouterOS versions Stable 6.43.12 and below, Long-term 6.42.12 and below, and Testing 6.44beta75 and below are vulnerable to an authenticated, remote directory traversal via the HTTP or Winbox interfaces. An authenticated, remote attack can use this vulnerability to read and write files outside of the sandbox directory (/rw/disk)", + "CVE-2019-3978": "RouterOS versions 6.45.6 Stable, 6.44.5 Long-term, and below allow remote unauthenticated attackers to trigger DNS queries via port 8291. The queries are sent from the router to a server of the attacker's choice. The DNS responses are cached by the router, potentially resulting in cache poisoning", + "CVE-2019-3981": "MikroTik Winbox 3.20 and below is vulnerable to man in the middle attacks. A man in the middle can downgrade the client's authentication protocol and recover the user's username and MD5 hashed password.", + "CVE-2020-10364": "The SSH daemon on MikroTik routers through 6.44.3 could allow remote attackers to generate CPU activity, trigger refusal of new authorized connections, and cause a reboot via connect and write system calls, because of uncontrolled resource management", + "CVE-2020-11881": "An array index error in MikroTik RouterOS 6.41.3 through 6.46.5, and 7.x through 7.0 Beta5, allows an unauthenticated remote attacker to crash the SMB server via modified setup-request packets,", + "CVE-2020-20021": "An issue discovered in MikroTik Router 6.46.3 and earlier allows attacker to cause denial of service via misconfiguration in the SSH daemon.", + "CVE-2020-20214": "MikroTik RouterOS 6.44.6 (long-term tree) suffers from an assertion failure vulnerability in the btest process. An authenticated remote attacker can cause a Denial of Service due to an assertion failure via a crafted packet.", + "CVE-2020-20217": "MikroTik RouterOS before 6.47 (stable tree) suffers from an uncontrolled resource consumption vulnerability in the /nova/bin/route process. An authenticated remote attacker can cause a Denial of Service due to overloading the systems CPU.", + "CVE-2020-20220": "MikroTik RouterOS prior to stable 6.47 suffers from a memory corruption vulnerability in the /nova/bin/bfd process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference).", + "CVE-2020-20222": "MikroTik RouterOS 6.44.6 (long-term tree) suffers from a memory corruption vulnerability in the /nova/bin/sniffer process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference).", + "CVE-2020-20225": "MikroTik RouterOS before 6.47 (stable tree) suffers from an assertion failure vulnerability in the /nova/bin/user process. An authenticated remote attacker can cause a Denial of Service due to an assertion failure via a crafted packet.", + "CVE-2020-20227": "MikroTik RouterOS stable 6.47 suffers from a memory corruption vulnerability in the /nova/bin/diskd process. An authenticated remote attacker can cause a Denial of Service due to invalid memory access.", + "CVE-2020-20230": "MikroTik RouterOS before stable 6.47 suffers from an uncontrolled resource consumption in the sshd process. An authenticated remote attacker can cause a Denial of Service due to overloading the systems CPU.", + "CVE-2020-20231": "MikroTik RouterOS through stable version 6.48.3 suffers from a memory corruption vulnerability in the /nova/bin/detnet process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference).", + "CVE-2020-20236": "MikroTik RouterOS 6.46.3 (stable tree) suffers from a memory corruption vulnerability in the /nova/bin/sniffer process. An authenticated remote attacker can cause a Denial of Service due to improper memory access.", + "CVE-2020-20237": "Mikrotik RouterOS 6.46.3 (stable tree) suffers from a memory corruption vulnerability in the /nova/bin/sniffer process. An authenticated remote attacker can cause a Denial of Service due to improper memory access.", + "CVE-2020-20245": "Mikrotik RouterOS stable 6.46.3 suffers from a memory corruption vulnerability in the log process. An authenticated remote attacker can cause a Denial of Service due to improper memory access.", + "CVE-2020-20246": "Mikrotik RouterOS stable 6.46.3 suffers from a memory corruption vulnerability in the mactel process. An authenticated remote attacker can cause a Denial of Service due to improper memory access.", + "CVE-2020-20248": "Mikrotik RouterOS before stable 6.47 suffers from an uncontrolled resource consumption in the memtest process. An authenticated remote attacker can cause a Denial of Service due to overloading the systems CPU.", + "CVE-2020-20249": "Mikrotik RouterOS before stable 6.47 suffers from a memory corruption vulnerability in the resolver process. By sending a crafted packet", + "CVE-2020-20250": "Mikrotik RouterOS before stable version 6.47 suffers from a memory corruption vulnerability in the /nova/bin/lcdstat process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference) NOTE: this is different from CVE-2020-20253 and CVE-2020-20254. All four vulnerabilities in the /nova/bin/lcdstat process are discussed in the CVE-2020-20250", + "CVE-2020-20252": "Mikrotik RouterOS before stable version 6.47 suffers from a memory corruption vulnerability in the /nova/bin/lcdstat process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference)", + "CVE-2020-20253": "Mikrotik RouterOS before 6.47 (stable tree) suffers from a divison by zero vulnerability in the /nova/bin/lcdstat process. An authenticated remote attacker can cause a Denial of Service due to a divide by zero error.", + "CVE-2020-20254": "Mikrotik RouterOS before 6.47 (stable tree) suffers from a memory corruption vulnerability in the /nova/bin/lcdstat process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference).", + "CVE-2020-20262": "Mikrotik RouterOS before 6.47 (stable tree) suffers from an assertion failure vulnerability in the /ram/pckg/security/nova/bin/ipsec process. An authenticated remote attacker can cause a Denial of Service due to an assertion failure via a crafted packet.", + "CVE-2020-20264": "Mikrotik RouterOS before 6.47 (stable tree) in the /ram/pckg/advanced-tools/nova/bin/netwatch process. An authenticated remote attacker can cause a Denial of Service due to a divide by zero error.", + "CVE-2020-20265": "Mikrotik RouterOS before 6.47 (stable tree) suffers from a memory corruption vulnerability in the /ram/pckg/wireless/nova/bin/wireless process. An authenticated remote attacker can cause a Denial of Service due via a crafted packet.", + "CVE-2020-20266": "Mikrotik RouterOS before 6.47 (stable tree) suffers from a memory corruption vulnerability in the /nova/bin/dot1x process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference).", + "CVE-2020-5720": "MikroTik WinBox before 3.21 is vulnerable to a path traversal vulnerability that allows creation of arbitrary files wherevere WinBox has write permissions. WinBox is vulnerable to this attack if it connects to a malicious endpoint or if an attacker mounts a man in the middle attack.", + "CVE-2020-5721": "MikroTik WinBox 3.22 and below stores the user's cleartext password in the settings.cfg.viw configuration file when the Keep Password field is set and no Master Password is set. Keep Password is set by default and", + "CVE-2021-27221": "MikroTik RouterOS 6.47.9 allows remote authenticated ftp users to create or overwrite arbitrary .rsc files via the /export command. NOTE: the vendor's position is that this is intended behavior because of how user policies work.", + "CVE-2021-3014": "MikroTik RouterOS through 6.48 is vulnerable to XSS in the hotspot login page via the target parameter", + "CVE-2021-36613": "MikroTik RouterOS before stable 6.48.2 suffers from a memory corruption vulnerability in the ptp process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference)", + "CVE-2021-36614": "MikroTik RouterOS before stable 6.48.2 suffers from a memory corruption vulnerability in the tr069-client process. An authenticated remote attacker can cause a Denial of Service (NULL pointer dereference)", + "CVE-2022-34960": "The container package in MikroTik RouterOS 7.4beta4 allows an attacker to create mount points pointing to symbolic links", + "CVE-2022-36522": "Mikrotik RouterOS through stable 6.48.3 was discovered to contain an assertion failure in the component /advanced-tools/nova/bin/netwatch. This vulnerability allows attackers to cause a Denial of Service (DoS) via a crafted packet.", + "CVE-2022-45313": "Mikrotik RouterOS before stable 7.5 was discovered to contain an out-of-bounds read in the hotspot process. This vulnerability allows attackers to execute arbitrary code via a crafted nova message.", + "CVE-2022-45315": "Mikrotik RouterOS before stable 7.6 was discovered to contain an out-of-bounds read in the snmp process. This vulnerability allows attackers to execute arbitrary code via a crafted packet.", + "CVE-2023-24094": "An issue in the bridge2 component of MikroTik RouterOS v6.40.5 allows attackers to cause a Denial of Service (DoS) via crafted packets.", + "CVE-2023-30799": "MikroTik RouterOS stable before 6.49.7 and long-term through 6.48.6 are vulnerable to a privilege escalation issue. A remote and authenticated attacker can escalate privileges from admin to super-admin on the Winbox or HTTP interface. The attacker can abuse this vulnerability to execute arbitrary code on the system.", + "CVE-2023-30800": "The web server used by MikroTik RouterOS version 6 is affected by a heap memory corruption issue. A remote and unauthenticated attacker can corrupt the server's heap memory by sending a crafted HTTP request. As a result", + "CVE-2023-41570": "MikroTik RouterOS v7.1 to 7.11 was discovered to contain incorrect access control mechanisms in place for the Rest API.", +} \ No newline at end of file diff --git a/sara.py b/sara.py index 36dc7ff..dd7974c 100644 --- a/sara.py +++ b/sara.py @@ -1,11 +1,20 @@ #!/usr/bin/env python3 -import argparse -import re -import colorama -from colorama import Fore, Style +# Copyright (c) 2025 Magama Bazarov +# Licensed under the Apache 2.0 License -# Colorama +# Connecting required libraries and cve_lookup module +import argparse +import colorama +import time +import re +import sys +from netmiko import ConnectHandler +from colorama import Fore, Style +from cve_lookup import cve_routeros_database +from packaging.version import Version + +# Initialize colorama for colored console output colorama.init(autoreset=True) def banner(): @@ -15,409 +24,762 @@ def banner(): | (___ __ _ _ __ __ _ \___ \ / _` | '__/ _` | ____) | (_| | | | (_| | - |_____/ \__,_|_| \__,_| v1.0 + |_____/ \__,_|_| \__,_| """ + # Display the program banner and metadata print(banner_text) - print(" " + Fore.YELLOW + "RouterOS Security Inspector. Designed for security professionals\n") - print(" " + Fore.YELLOW + "Author: " + Style.RESET_ALL + "Magama Bazarov, \n") - print(" " + "It's recommended to provide a configuration file exported using the 'export verbose' command") + print(" " + Fore.YELLOW + "RouterOS Security Inspector. For security engineers") + print(" " + Fore.YELLOW + "Operates remotely using SSH, designed to evaluate RouterOS security\n") + print(" " + Fore.YELLOW + "Author: " + Style.RESET_ALL + "Magama Bazarov, ") + print(" " + Fore.YELLOW + "Alias: " + Style.RESET_ALL + "Caster") + print(" " + Fore.YELLOW + "Version: " + Style.RESET_ALL + "1.1") + print(" " + Fore.YELLOW + "Codename: " + Style.RESET_ALL + "Judge") + print(" " + Fore.YELLOW + "Documentation & Usage: " + Style.RESET_ALL + "https://github.com/casterbyte/Sara") print() -def parse_arguments(): - parser = argparse.ArgumentParser() - parser.add_argument('--config-file', required=True, help='Path to the RouterOS configuration file (or .rsc)') - return parser.parse_args() + # Display a legal disclaimer to emphasize responsible usage + print(" " + Fore.YELLOW + "[!] DISCLAIMER: Use this tool only for auditing your own devices.") + print(" " + Fore.YELLOW + "[!] Unauthorized use on third-party systems is ILLEGAL.") + print(" " + Fore.YELLOW + "[!] The author is not responsible for misuse.") + print() -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)", +# Establish SSH connection to the RouterOS device using Netmiko +def connect_to_router(ip, username, password, port): + device = { + "device_type": "mikrotik_routeros", + "host": ip, + "username": username, + "password": password, + "port": port, } - - 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}") + print(Fore.GREEN + Style.BRIGHT + f"[*] Connecting to RouterOS at {ip}:{port}") + connection = ConnectHandler(**device) + print(Fore.WHITE + "[*] Connection successful!") + return connection except Exception as e: - print(f"{Fore.RED}Error: An unexpected error occurred: {e}{Style.RESET_ALL}") + print(Fore.RED + f"[-] Connection failed: {e}") + exit(1) + +# Print a visual separator for better readability in the output +def separator(title): + print(Fore.WHITE + Style.BRIGHT + '=' * 40) + print(Fore.WHITE + Style.BRIGHT + f"[*] {title}") + +def parse_version(version_str): + # Parses a version string into a comparable Version object. Example: "6.49.7" → Version(6.49.7) + return Version(version_str) + +def extract_version_from_cve(description): + # Case: "X.Y to Z.W" + range_match = re.search(r"v?(\d+\.\d+(?:\.\d+)?)\s*to\s*v?(\d+\.\d+(?:\.\d+)?)", description, re.IGNORECASE) + if range_match: + start_version, end_version = range_match.groups() + return "range", parse_version(start_version), parse_version(end_version) + + # Case: "before X.Y.Z", "after X.Y.Z", "through X.Y.Z", "and below X.Y.Z" + keyword_match = re.search(r"(before|through|after|and below)?\s*v?(\d+\.\d+(?:\.\d+)?)", description, re.IGNORECASE) + if keyword_match: + keyword, version = keyword_match.groups() + return keyword, None, parse_version(version) + + # Case: "6.49.x" (Wildcard notation) + wildcard_match = re.search(r"v?(\d+\.\d+)\.x", description, re.IGNORECASE) + if wildcard_match: + base_version = wildcard_match.group(1) # Example: "6.49" + return "before", None, parse_version(base_version + ".999") # Treat as "6.49.999" for comparison + + return None, None, None + +# Retrieves the RouterOS version and checks for known CVEs +def check_routeros_version(connection): + separator("Checking RouterOS Version") + command = "/system resource print" + output = connection.send_command(command) + + match = re.search(r"version:\s*([\d.]+)", output) + if match: + routeros_version = parse_version(match.group(1)) + print(Fore.GREEN + f"[+] Detected RouterOS Version: {routeros_version}") + + found_cves = [] + + for cve, description in cve_routeros_database.items(): + keyword, start_version, end_version = extract_version_from_cve(description) + + if keyword == "range" and start_version and end_version: + if start_version <= routeros_version <= end_version: + found_cves.append((cve, description)) + + elif keyword and end_version: + if keyword == "before" and routeros_version < end_version: + found_cves.append((cve, description)) + elif keyword == "through" and routeros_version <= end_version: + found_cves.append((cve, description)) + elif keyword == "after" and routeros_version > end_version: + found_cves.append((cve, description)) + elif keyword == "and below" and routeros_version <= end_version: + found_cves.append((cve, description)) + + # Direct version match + elif str(routeros_version) in description: + found_cves.append((cve, description)) + + if found_cves: + print(Fore.YELLOW + f"[!] CAUTION: Found {len(found_cves)} CVEs affecting RouterOS {routeros_version}!") + for cve, description in found_cves: + print(Fore.RED + f" - {cve}: {description}") + else: + print(Fore.GREEN + "[+] No known CVEs found for this version.") + else: + print(Fore.RED + Style.BRIGHT + "[-] ERROR: Could not determine RouterOS version.") + + +# Check if SMB service is enabled (potential security risk) +def check_smb(connection): + separator("Checking SMB Service") + command = "/ip smb print" + output = connection.send_command(command) + + if "enabled: yes" in output: + print(Fore.RED + Style.BRIGHT + "[*] CAUTION: SMB service is enabled! Did you turn it on? Do you need SMB? Also avoid CVE-2018-7445") + else: + print(Fore.GREEN + "[+] SMB is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Check for high-risk remote management interfaces (RMI) +def check_rmi_services(connection): + separator("Checking RMI Services") + command = "/ip service print" + output = connection.send_command(command) + + high_risk = ["telnet", "ftp", "www"] + moderate_risk = ["api", "api-ssl", "winbox", "www-ssl"] + safe = ["ssh"] + + risks_found = False + + for line in output.splitlines(): + line = line.strip() + if re.search(r"^\d+\s+X", line): + continue + match = re.search(r"(\S+)\s+\d+", line) + if match: + service_name = match.group(1).lower() + display_name = service_name.upper().replace("WWW", "HTTP").replace("WWW-SSL", "HTTPS") + + if service_name in high_risk: + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: {display_name} is ENABLED! This is a high security risk.") + if service_name == "ftp": + print(Fore.RED + " - Are you sure you need FTP?") + if service_name == "telnet": + print(Fore.RED + " - Account passwords can be intercepted") + if service_name == "www": + print(Fore.RED + " - Account passwords can be intercepted") + risks_found = True + + elif service_name in moderate_risk: + print(Fore.YELLOW + Style.BRIGHT + f"[!] CAUTION: {display_name} is enabled.") + if service_name in ["api", "api-ssl"]: + print(Fore.YELLOW + " - RouterOS API is vulnerable to a bruteforce attack. If you need it, make sure you have access to it.") + elif service_name == "www-ssl": + print(Fore.GREEN + " - HTTPS detected. Ensure it uses a valid certificate and strong encryption.") + elif service_name == "winbox": + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: If you're using 'Keep Password' in Winbox, your credentials may be stored in plaintext!") + print(Fore.YELLOW + " - If your PC is compromised, attackers can extract saved credentials.") + print(Fore.YELLOW + " - Consider disabling 'Keep Password' to improve security.") + + elif service_name in safe: + print(Fore.GREEN + f"[+] OK: {display_name} is enabled. Good!") + print(Fore.GREEN + " - Are you using strong passwords and SSH keys for authentication?") + + if not risks_found: + print(Fore.GREEN + "[+] No high-risk RMI services enabled.") + print(Fore.GREEN + "[+] No issues found.") + +# Check for default usernames that could be security risks +def check_default_users(connection): + separator("Checking Default Usernames") + command = "/user print detail" + output = connection.send_command(command) + + default_users = {"admin", "engineer", "user", "test", "root", "mikrotik", "routeros"} + risks_found = False + + for line in output.split("\n\n"): + match = re.search(r"name=\"?(\w+)\"?", line) + if match: + username = match.group(1).lower() + if username in default_users: + print(Fore.YELLOW + Style.BRIGHT + f"[!] CAUTION: Default username '{username}' detected! Change it to a unique one.") + risks_found = True + if not risks_found: + print(Fore.GREEN + "[+] No default usernames found.") + +# Verify whether critical services have restricted network access +def checking_access_to_RMI(connection): + separator("Checking network access to RMI") + command = "/ip service print detail" + output = connection.send_command(command) + + risks_found = False + + for line in output.split("\n\n"): + service_match = re.search(r'name="([^"]+)"', line) + address_match = re.search(r'address=([\d./,]+)', line) + + if service_match: + service_name = service_match.group(1) + + if address_match: + address_list = address_match.group(1).split(",") + if not address_list or address_list == [""] or "0.0.0.0/0" in address_list: + print(Fore.YELLOW + Style.BRIGHT + f"[!] CAUTION: {service_name.upper()} is exposed to the entire network! Restrict access to trusted IP ranges.") + risks_found = True + else: + print(Fore.GREEN + f"[+] OK! {service_name.upper()} is restricted to: {', '.join(address_list)}") + else: + print(Fore.YELLOW + Style.BRIGHT + f"[!] CAUTION: {service_name.upper()} has no IP restriction set! Please restrict access.") + risks_found = True + + if not risks_found: + print(Fore.GREEN + "[+] All services have proper IP restrictions.") + +# Analyze Wi-Fi security settings, including WPS and PMKID vulnerabilities +# I think this is the most unstable feature of the whole Sarah, need more feedback from users to get it perfect +def check_wifi_security(connection): + separator("Checking Wi-Fi Security") + + risks_found = False + try: + # Retrieve RouterOS version to determine supported commands + command = "/system resource print" + output = connection.send_command(command) + version_match = re.search(r"version:\s*([\d.]+)", output) + routeros_version = Version(version_match.group(1)) if version_match else Version("0.0.0") + + # Wi-Fi (ROS v6/v7) + commands = ["/interface wifi print detail", "/interface wireless print detail"] + found_valid_output = False + + for command in commands: + output = connection.send_command(command) + if "bad command name" not in output.lower() and output.strip(): + found_valid_output = True + interfaces = output.split("\n\n") + for interface in interfaces: + name_match = re.search(r'name="([^"]+)"', interface) + default_name_match = re.search(r'default-name="([^"]+)"', interface) + pmkid_match = re.search(r'disable-pmkid=(\S+)', interface) + wps_match = re.search(r'wps=(\S+)', interface) + + name = name_match.group(1) if name_match else (default_name_match.group(1) if default_name_match else "Unknown") + pmkid = pmkid_match.group(1) if pmkid_match else "unknown" + wps = wps_match.group(1) if wps_match else None # Fix: If WPS is not found, set None + + if pmkid == "no": + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: Wi-Fi '{name}' has insecure settings!") + print(Fore.RED + " - PMKID attack is possible (disable-pmkid=no)") + risks_found = True + + # Fix: Do not report WPS if it's completely missing in the output + if wps is not None and wps != "disable": + print(Fore.RED + f"[!] ALERT: Wi-Fi '{name}' has WPS enabled ({wps}), Risk of PIN bruteforcing and Pixie Dust attacks.") + risks_found = True + + if not found_valid_output: + print(Fore.RED + "[-] ERROR: Unable to retrieve Wi-Fi interface settings. Unsupported RouterOS version or missing interface.") + + # Security profiles (ROS v6) + security_profiles_output = connection.send_command("/interface wireless security-profiles print detail") + if security_profiles_output.strip(): + profiles = security_profiles_output.split("\n\n") + for profile in profiles: + profile_name_match = re.search(r'name="([^"]+)"', profile) + pmkid_match = re.search(r'disable-pmkid=(\S+)', profile) + + profile_name = profile_name_match.group(1) if profile_name_match else "Unknown" + pmkid = pmkid_match.group(1) if pmkid_match else "unknown" + + if pmkid == "no": + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: Security Profile '{profile_name}' allows PMKID attack! (disable-pmkid=no)") + risks_found = True + + # /interface wifi security print (ROS v7.10+ only) + if routeros_version >= Version("7.10"): + security_output = connection.send_command("/interface wifi security print") + if security_output.strip(): + securities = security_output.split("\n\n") + for security in securities: + sec_name_match = re.search(r'name="([^"]+)"', security) + pmkid_match = re.search(r'disable-pmkid=(\S+)', security) + wps_match = re.search(r'wps=(\S+)', security) + + if sec_name_match and (pmkid_match or wps_match): + sec_name = sec_name_match.group(1) + pmkid = pmkid_match.group(1) if pmkid_match else "unknown" + wps = wps_match.group(1) if wps_match else None # Fix: Avoid "WPS is enabled (unknown)" + + if pmkid == "no": + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: Wi-Fi security profile '{sec_name}' has insecure settings!") + print(Fore.RED + " - PMKID attack is possible (disable-pmkid=no)") + risks_found = True + + if wps is not None and wps != "disable": + print(Fore.RED + f"[!] ALERT: Wi-Fi security profile '{sec_name}' has WPS enabled ({wps}), Risk of PIN bruteforcing and Pixie Dust attacks.") + risks_found = True + else: + print(Fore.RED + "[-] ERROR: Unable to retrieve Wi-Fi security settings.") + else: + print(Fore.CYAN + "[*] Skipping `/interface wifi security print` (not supported in this version)") + + except Exception as e: + print(Fore.RED + f"[-] ERROR: Failed to check Wi-Fi settings: {e}") + + if not risks_found: + print(Fore.GREEN + "[+] All Wi-Fi interfaces and security profiles have secure settings.") + print(Fore.YELLOW + "[*] If you use WPA-PSK or WPA2-PSK, take care of password strength. So that the handshake cannot be easily brute-forced.") + print(Fore.GREEN + "[+] No issues found.") + +# Check if UPnP is enabled +def check_upnp_status(connection): + separator("Checking UPnP Status") + command = "/ip upnp print" + output = connection.send_command(command) + + if "enabled: yes" in output: + print(Fore.RED + Style.BRIGHT + "[!] ALERT: UPnP is ENABLED! This is a very insecure protocol that automatically pushes internal hosts to the Internet. This protocol is used for automatic port forwarding and may also indicate a potential router compromise. Did you enable UPnP yourself?") + else: + print(Fore.GREEN + "[+] UPnP is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Check if the router is acting as a DNS server +def check_dns_status(connection): + separator("Checking DNS Settings") + command = "/ip dns print" + output = connection.send_command(command) + + if "allow-remote-requests: yes" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: Router is acting as a DNS server! This is just a warning. The DNS port on your RouterOS should not be on the external interface.") + else: + print(Fore.GREEN + "[+] DNS remote requests are disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Check DDNS Settings +def check_ddns_status(connection): + separator("Checking DDNS Settings") + command = "/ip cloud print" + output = connection.send_command(command) + + if "ddns-enabled: yes" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: Dynamic DNS is enabled! Are you sure you need it?") + else: + print(Fore.GREEN + "[+] DDNS is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Detect active PoE interfaces that might pose a risk to connected devices +def check_poe_status(connection): + separator("Checking PoE Status") + command = "/interface ethernet print detail" + output = connection.send_command(command) + + risks_found = False + interfaces = output.split("\n\n") + + for interface in interfaces: + name_match = re.search(r'name="([^"]+)"', interface) + poe_match = re.search(r'poe-out=(\S+)', interface) + name = name_match.group(1) if name_match else "Unknown" + poe = poe_match.group(1) if poe_match else "none" + + if poe in ["auto-on", "forced-on"]: + print(Fore.YELLOW + Style.BRIGHT + f"[!] CAUTION: PoE is enabled on {name}. Ensure that connected devices support PoE to prevent damage.") + risks_found = True + + if not risks_found: + print(Fore.GREEN + "[+] No PoE-enabled interfaces detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Checking RouterBOOT +def check_routerboot_protection(connection): + separator("Checking RouterBOOT Protection") + command = "/system routerboard settings print" + output = connection.send_command(command) + + if "protected-routerboot: disabled" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: RouterBOOT protection is disabled! This can allow unauthorized firmware changes and password resets via Netinstall.") + else: + print(Fore.GREEN + "[+] RouterBOOT protection is enabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +def check_socks_status(connection): + separator("Checking SOCKS Proxy Status") + command = "/ip socks print" + output = connection.send_command(command) + + if "enabled: yes" in output: + print(Fore.RED + Style.BRIGHT + "[!] ALERT: SOCKS proxy is enabled! This may indicate a possible compromise of the device, the entry point to the internal network.") + else: + print(Fore.GREEN + "[+] SOCKS proxy is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Verify if RouterBOOT protection is enabled to prevent unauthorized firmware modifications +def check_bandwidth_server_status(connection): + separator("Checking Bandwidth Server Status") + command = "/tool bandwidth-server print" + output = connection.send_command(command) + + if "enabled: yes" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: Bandwidth server is enabled! Possible unwanted traffic, possible CPU load.") + else: + print(Fore.GREEN + "[+] Bandwidth server is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Analyze discovery protocols (CDP, LLDP, MNDP) that might expose network information +def check_neighbor_discovery(connection): + separator("Checking Neighbor Discovery Protocols") + command = "/ip neighbor discovery-settings print" + output = connection.send_command(command) + + if "discover-interface-list: all" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: RouterOS sends Discovery protocol packets to all interfaces. This can be used by an attacker to gather data about RouterOS.") + + protocol_match = re.search(r'protocol: ([\w,]+)', output) + if protocol_match: + protocols = protocol_match.group(1) + print(Fore.YELLOW + Style.BRIGHT + f"[!] Neighbor Discovery Protocols enabled: {protocols}") + if "discover-interface-list: all" not in output and not protocol_match: + print(Fore.GREEN + "[+] No security risks found in Neighbor Discovery Protocol settings.") + print(Fore.GREEN + "[+] No issues found.") + +# Ensure a minimum password length policy is enforced +def check_password_length_policy(connection): + separator("Checking Password Policy") + command = "/user settings print" + output = connection.send_command(command) + + if "minimum-password-length: 0" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: No minimum password length is enforced! The length of the created passwords must be taken into account.") + if "minimum-password-length: 0" not in output: + print(Fore.GREEN + "[+] Password policy is enforced. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Analyze SSH security settings, including strong encryption and port forwarding risks +def check_ssh_security(connection): + separator("Checking SSH Security") + command = "/ip ssh print" + output = connection.send_command(command) + + if "forwarding-enabled: both" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: SSH Dynamic Port Forwarding is enabled! This could indicate a RouterOS compromise, and SSH DPF could also be used by an attacker as a pivoting technique.") + if "strong-crypto: no" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: strong-crypto is disabled! It is recommended to enable it to enhance security. This will:") + print(Fore.YELLOW + " - Use stronger encryption, HMAC algorithms, and larger DH primes;") + print(Fore.YELLOW + " - Prefer 256-bit encryption, disable null encryption, prefer SHA-256;") + print(Fore.YELLOW + " - Disable MD5, use 2048-bit prime for Diffie-Hellman exchange;") + if "forwarding-enabled: both" not in output and "strong-crypto: no" not in output: + print(Fore.GREEN + "[+] SSH security settings are properly configured.") + print(Fore.GREEN + "[+] No issues found.") + +# Check if connection tracking is enabled, which may impact performance +def check_connection_tracking(connection): + separator("Checking Connection Tracking") + command = "/ip firewall connection tracking print" + output = connection.send_command(command) + if "enabled: auto" in output or "enabled: on" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: Connection Tracking is enabled! This means RouterOS is tracks connection statuses.") + print(Fore.YELLOW + " - If this device is a transit router and does NOT use NAT, consider disabling connection tracking to reduce CPU load.") + + if "enabled: auto" not in output and "enabled: on" not in output: + print(Fore.GREEN + "[+] Connection Tracking is properly configured.") + print(Fore.GREEN + "[+] No issues found.") + +# Verify if RoMON is enabled, which might expose Layer 2 management access +def check_romon_status(connection): + separator("Checking RoMON Status") + command = "/tool romon print" + output = connection.send_command(command) + + if "enabled: yes" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: RoMON is enabled! This allows Layer 2 management access, which may expose the router to unauthorized control.") + print(Fore.YELLOW + " - If RoMON is not required, disable it to reduce attack surface.") + if "enabled: yes" not in output: + print(Fore.GREEN + "[+] RoMON is disabled. No risk detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Analyze MAC-based Winbox access settings +def check_mac_winbox_security(connection): + separator("Checking Winbox MAC Server Settings") + + # MAC-Winbox Server + command = "tool mac-server mac-winbox print" + output = connection.send_command(command) + if "allowed-interface-list: all" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: MAC Winbox access is enabled on all interfaces. This compromises the security of the Winbox interface.") + else: + print(Fore.GREEN + "[+] MAC Winbox are properly restricted.") + + # MAC-Server + command = "tool mac-server print" + output = connection.send_command(command) + if "allowed-interface-list: all" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: MAC Telnet access is enabled on all interfaces. This compromises the security of the Winbox interface.") + else: + print(Fore.GREEN + "[+] MAC Telnet are properly restricted.") + + # MAC Ping + command = "tool mac-server ping print" + output = connection.send_command(command) + if "enabled: yes" in output: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: MAC Ping is enabled. Possible unwanted traffic.") + else: + print(Fore.GREEN + "[+] MAC Ping are properly restricted.") + +# Check for weak SNMP community strings that could be exploited +def check_snmp(connection): + separator("Checking SNMP Community Strings") + command = "/snmp community print" + output = connection.send_command(command) + + bad_names = ["public", "private", "admin", "mikrotik", "mikrotik_admin", "root", "routeros", "zabbix"] + risks_found = False + + for line in output.splitlines(): + match = re.search(r'^\s*\d+\s+[*X]?\s*([\w-]+)', line) + if match: + community_name = match.group(1).lower() + if community_name in bad_names: + print(Fore.YELLOW + Style.BRIGHT + f"[!] CAUTION: Weak SNMP community string detected: '{community_name}'. Change it to a secure, unique value.") + risks_found = True + + if not risks_found: + print(Fore.GREEN + "[+] SNMP community strings checked. No weak values detected.") + print(Fore.GREEN + "[+] No issues found.") + +# Detect and analyze firewall NAT rules that could expose internal services +def check_dst_nat_rules(connection): + separator("Checking Firewall NAT Rules") + command = "/ip firewall nat print" + output = connection.send_command(command) + dst_nat_rules = [] + for line in output.splitlines(): + if "action=dst-nat" in line or "action=netmap" in line: + dst_nat_rules.append(line.strip()) + if dst_nat_rules: + print(Fore.YELLOW + Style.BRIGHT + "[!] CAUTION: Destination NAT (dst-nat/netmap) rules detected! Exposing devices to the internet can be dangerous.") + print(Fore.YELLOW + Style.BRIGHT + "[*] Similar rules can also be created by the attacker. Did you really create these rules yourself?") + print(Fore.YELLOW + " - Review the following NAT rules:") + for rule in dst_nat_rules: + print(Fore.YELLOW + f" {rule}") + if not dst_nat_rules: + print(Fore.GREEN + "[+] No Destination NAT (dst-nat/netmap) rules detected. No risks found.") + print(Fore.GREEN + "[+] No issues found.") + +# Identify potentially malicious scheduled tasks +def detect_malicious_schedulers(connection): + separator("Checking for Malicious Schedulers") + command = "/system scheduler print detail" + output = connection.send_command(command) + + risks_found = False + fetch_files = set() + + for task in output.split("\n\n"): + name_match = re.search(r'name="?([^"]+)"?', task) + event_match = re.search(r'on-event="?([^"\n]+)"?', task) + policy_match = re.search(r'policy=([\w,]+)', task) + interval_match = re.search(r'interval=(\d+)([smhd])', task) + + name = name_match.group(1) if name_match else "Unknown" + event = event_match.group(1).strip() if event_match else "" + policy = policy_match.group(1).split(",") if policy_match else [] + interval_value, interval_unit = (int(interval_match.group(1)), interval_match.group(2)) if interval_match else (None, None) + + # DEBUG + print(Fore.CYAN + f"[*] Checking: '{name}' → {event}") + + # Fetch detection + fetch_match = re.search(r'dst-path=([\S]+)', event) + if "fetch" in event and fetch_match: + fetched_file = fetch_match.group(1).strip(";") + fetch_files.add(fetched_file) + print(Fore.YELLOW + f"[!] Noted fetched file: {fetched_file}") + + # Import detection (checks if imported file was fetched earlier) + import_match = re.search(r'import\s+([\S]+)', event) + if "import" in event and import_match: + imported_file = import_match.group(1).strip(";") + if imported_file in fetch_files: + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: '{name}' is a BACKDOOR!") + print(Fore.RED + " - This scheduler imports a previously fetched script.") + print(Fore.RED + " - Attacker can inject any command remotely via this script.") + print(Fore.RED + f" - Interval: {interval_value}{interval_unit}, ensuring persistence.") + risks_found = True + + # High privileges checking + dangerous_policies = {"password", "sensitive", "sniff", "ftp"} + if any(p in dangerous_policies for p in policy): + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: '{name}' has HIGH PRIVILEGES!") + print(Fore.RED + f" - It has dangerous permissions: {', '.join(policy)}") + risks_found = True + + # Reboot detection (Anti-forensics & persistence check) + if "reboot" in event: + if interval_value and interval_unit in ["s", "m", "h"] and interval_value < 12: + print(Fore.RED + f"[!] ALERT: '{name}' reboots router TOO FREQUENTLY ({interval_value}{interval_unit})!") + print(Fore.RED + " - This may be an attempt to prevent log analysis (anti-forensics).") + risks_found = True + else: + print(Fore.YELLOW + f"[!] CAUTION: '{name}' schedules a reboot.") + print(Fore.YELLOW + " - Ensure this is intentional and not used to hide attacks.") + continue + + # Frequent execution detection + if interval_value and interval_unit in ["s", "m", "h"] and interval_value < 25: + print(Fore.RED + Style.BRIGHT + f"[!] ALERT: '{name}' executes TOO FREQUENTLY ({interval_value}{interval_unit})!") + print(Fore.RED + " - This indicates botnet-like persistence.") + risks_found = True + + if not risks_found: + print(Fore.GREEN + "[+] No malicious schedulers detected.") + +# Checking DNS Static Entries +def check_static_dns_entries(connection): + separator("Checking Static DNS Entries") + command = "/ip dns static print detail" + output = connection.send_command(command) + + dns_entries = [] + entry_blocks = output.split("\n\n") + + for entry in entry_blocks: + name_match = re.search(r'name="([^"]+)"', entry) + address_match = re.search(r'address=([\d.]+)', entry) + + if name_match and address_match: + name = name_match.group(1) + address = address_match.group(1) + dns_entries.append((name, address)) + + if dns_entries: + print(Fore.YELLOW + "[!] WARNING: The following static DNS entries exist:") + for name, address in dns_entries: + print(Fore.CYAN + f" - {name} → {address}") + + print(Fore.YELLOW + "[*] Were you the one who created those static DNS records? Make sure.") + print(Fore.YELLOW + "[*] Attackers during RouterOS post-exploitation like to tamper with DNS record settings, for example, for phishing purposes.") + else: + print(Fore.GREEN + "[+] No static DNS entries found.") + +# Retrieve router uptime +def get_router_uptime(connection): + separator("Checking Router Uptime") + command = "/system resource print" + output = connection.send_command(command) + + # Extract uptime value + match = re.search(r"uptime:\s*([\w\d\s]+)", output) + + if match: + uptime_raw = match.group(1) + weeks = days = hours = minutes = 0 + + # Extract individual time units + if "w" in uptime_raw: + weeks = int(re.search(r"(\d+)w", uptime_raw).group(1)) + if "d" in uptime_raw: + days = int(re.search(r"(\d+)d", uptime_raw).group(1)) + if "h" in uptime_raw: + hours = int(re.search(r"(\d+)h", uptime_raw).group(1)) + if "m" in uptime_raw: + minutes = int(re.search(r"(\d+)m", uptime_raw).group(1)) + + # Convert weeks to days and format output + total_days = weeks * 7 + days + print(Fore.GREEN + Style.BRIGHT + f"[*] Router Uptime: {total_days} days, {hours} hours, {minutes} minutes") + else: + print(Fore.RED + "[-] ERROR: Could not retrieve uptime.") + +# Require user confirmation before proceeding, emphasizing legal responsibility +def confirm_legal_usage(): + print(" " + "WARNING: This tool is for security auditing of YOUR OWN RouterOS devices.") + print(" " + "Unauthorized use may be illegal. Proceed responsibly.\n") + response = input(" " + "Do you wish to proceed? [yes/no]: ").strip() + + if response.lower() != "yes": + print("\nOperation aborted. Exiting...") + sys.exit(0) + +# # Parse command-line arguments, establish connection, and execute all security checks +def main(): + # Print banner + banner() + + # Argument parsing + parser = argparse.ArgumentParser() + parser.add_argument("--ip", help="The address of your MikroTik router") + parser.add_argument("--username", help="SSH username (RO account can be used)") + parser.add_argument("--password", help="SSH password") + parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)") + args = parser.parse_args() + + if len(sys.argv) == 2 and sys.argv[1] in ["-h", "--help"]: + parser.print_help() + sys.exit(0) + + if not args.ip or not args.username or not args.password: + print(Fore.YELLOW + Style.BRIGHT + "[!] ERROR: Missing required arguments") + print(Fore.YELLOW + "[!] Use 'sara --help' for more information") + sys.exit(1) + + confirm_legal_usage() + + # Start timer + start_time = time.time() + + # Connecting to the router + connection = connect_to_router(args.ip, args.username, args.password, args.port) + + # Execute all implemented security checks in sequence + check_routeros_version(connection) + check_smb(connection) + check_rmi_services(connection) + check_default_users(connection) + checking_access_to_RMI(connection) + check_wifi_security(connection) + check_upnp_status(connection) + check_dns_status(connection) + check_ddns_status(connection) + check_poe_status(connection) + check_routerboot_protection(connection) + check_socks_status(connection) + check_bandwidth_server_status(connection) + check_neighbor_discovery(connection) + check_password_length_policy(connection) + check_ssh_security(connection) + check_connection_tracking(connection) + check_romon_status(connection) + check_mac_winbox_security(connection) + check_snmp(connection) + check_dst_nat_rules(connection) + detect_malicious_schedulers(connection) + check_static_dns_entries(connection) + get_router_uptime(connection) + + # Print a blank line for better output formatting + print () + + # Close the SSH connection to the router + connection.disconnect() + print(Fore.GREEN + Style.BRIGHT + f"[*] Disconnected from RouterOS ({args.ip}:{args.port})") + + # Measure and display the total execution time + end_time = time.time() + total_time = round(end_time - start_time, 2) + + # Print a closing message emphasizing continuous security improvements + print(Fore.GREEN + Style.BRIGHT + f"[*] All checks have been completed. Security inspection completed in {total_time} seconds\n") + print(Fore.MAGENTA + Style.BRIGHT + "[*] " + Fore.WHITE + "Remember: " + Fore.RED + "Security" + Fore.WHITE + " is a " + Fore.GREEN + "process" + Fore.WHITE + ", not a " + Fore.YELLOW + "state.") if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/setup.py b/setup.py index 7b69ce7..b12ea4f 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ from setuptools import setup, find_packages setup( name="sara", - version="1.0", - url="https://github.com/casterbyte/sara", + version="1.1", + url="https://github.com/casterbyte/Sara", author="Magama Bazarov", author_email="caster@exploit.org", scripts=['sara.py'], @@ -11,11 +11,14 @@ setup( long_description=open('README.md').read(), long_description_content_type='text/markdown', license="Apache-2.0", - keywords=['mikrotik', 'routeros', 'config analyzer'], + keywords=['mikrotik', 'routeros', 'config analyzer', 'network security',], packages=find_packages(), install_requires=[ 'colorama', + 'netmiko', + 'packaging', ], + py_modules=['cve_lookup'], entry_points={ "console_scripts": ["sara = sara:main"], },