From 41a5adbb30e77ca284d2b103b20043f5386e3c68 Mon Sep 17 00:00:00 2001 From: syn-4ck Date: Tue, 4 Apr 2023 21:28:59 +0200 Subject: [PATCH] Add telnet plugin and improvements --- docs/about.md | 15 +++++++ src/analyze/cisco/ios/analyze_cisco_device.py | 1 - .../cisco/ios/cisco_parser/parse_config.py | 17 -------- .../cisco/ios/issue/cisco_ios_issue.py | 5 +-- src/analyze/cisco/ios/plugins/dns_plugin.py | 4 +- src/analyze/cisco/ios/plugins/http_plugin.py | 12 ++---- src/analyze/cisco/ios/plugins/ssh_plugin.py | 16 ++----- .../cisco/ios/plugins/telnet_plugin.py | 42 +++++++++++++++++++ .../cisco/ios/plugins/username_plugin.py | 16 ++----- src/report/templates/html_template.html | 10 +++-- 10 files changed, 77 insertions(+), 61 deletions(-) create mode 100644 docs/about.md create mode 100644 src/analyze/cisco/ios/plugins/telnet_plugin.py diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 0000000..5fa3b33 --- /dev/null +++ b/docs/about.md @@ -0,0 +1,15 @@ +# About pynipper + +## Roadmap + +Tool under construction! + + + +## Core contributors + +[syn-4ck](https://github.com/syn-4ck) + +## Author + +[syn-4ck](https://github.com/syn-4ck) diff --git a/src/analyze/cisco/ios/analyze_cisco_device.py b/src/analyze/cisco/ios/analyze_cisco_device.py index 2758cd0..a1e9e68 100644 --- a/src/analyze/cisco/ios/analyze_cisco_device.py +++ b/src/analyze/cisco/ios/analyze_cisco_device.py @@ -35,7 +35,6 @@ def analyze_cisco_device(device, input_filename, output_filename, output_type, c # Device data to generate report data = {} data['hostname'] = get_cisco_ios_hostname(input_filename) - data['device-type'] = [dev.value for dev in DeviceType if dev.name == device][0] # Generate report diff --git a/src/analyze/cisco/ios/cisco_parser/parse_config.py b/src/analyze/cisco/ios/cisco_parser/parse_config.py index 47f0145..b73e723 100644 --- a/src/analyze/cisco/ios/cisco_parser/parse_config.py +++ b/src/analyze/cisco/ios/cisco_parser/parse_config.py @@ -110,20 +110,3 @@ def get_cisco_ios_tcp_keep_alives_out(filename: str) -> bool: return True else: return False - -# Services configuration -# ----------------------- -# TODO: Migrate to specific module - -# If the device has telnet configured -> true - - -def get_cisco_ios_telnet(filename: str) -> bool: - parser = parse_cisco_ios_config_file(filename) - transport_disable = parser.find_objects("transport input none") - ssh_enable = parser.find_objects("transport input ssh") - telnet_disable = parser.find_objects("no transport input telnet") - if (len(transport_disable) > 0 or len(ssh_enable) > 0 or len(telnet_disable) > 0): # noqa: E501 - return False - else: - return True diff --git a/src/analyze/cisco/ios/issue/cisco_ios_issue.py b/src/analyze/cisco/ios/issue/cisco_ios_issue.py index 4508bd6..7c06130 100644 --- a/src/analyze/cisco/ios/issue/cisco_ios_issue.py +++ b/src/analyze/cisco/ios/issue/cisco_ios_issue.py @@ -1,12 +1,11 @@ class CiscoIOSIssue: - def __init__(self, title, observation, impact, ease, recommendation, line_number): + def __init__(self, title, observation, impact, ease, recommendation): self.title = title self.observation = observation self.impact = impact self.ease = ease self.recommendation = recommendation - self.line_number = line_number def __str__(self): print("Issue " + self.title + ":") @@ -15,7 +14,6 @@ def __str__(self): print("Impact: " + self.impact) print("Ease: " + self.ease) print("Recommendation: " + self.recommendation) - print("Line number: " + self.line_number) print("\n---------------------------------------------------------------------------------------------------") # noqa: E501 def __dict__(self): @@ -25,5 +23,4 @@ def __dict__(self): d['impact'] = self.impact d['ease'] = self.ease d['recommendation'] = self.recommendation - d['line_number'] = self.line_number return d diff --git a/src/analyze/cisco/ios/plugins/dns_plugin.py b/src/analyze/cisco/ios/plugins/dns_plugin.py index a00f3bf..6fda609 100644 --- a/src/analyze/cisco/ios/plugins/dns_plugin.py +++ b/src/analyze/cisco/ios/plugins/dns_plugin.py @@ -29,14 +29,12 @@ def _has_no_domain_lookup(self, filename: str) -> bool: def get_domain_name(self, filename: str): if (not self._has_dns_enabled(filename) and not self._has_no_domain_lookup(filename)): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "Domain Lookups", "Cisco IOS-based devices support name lookups using the DNS. However, if a DNS server has not been configured, then the DNS request is broadcast.It is determined that name lookups had not been disabled and no DNS servers had been configured.", # noqa: E501 "An attacker who was able to capture network traffic could monitor DNS queries from the device. Furthermore, Cisco devices can connect to Telnet servers by supplying only the hostname or IP address of the server. A mistyped Cisco command could be interpreted as an attempt to connect to a Telnet server and broadcast on the network.", # noqa: E501 "It would be trivial for an attacker to capture network traffic broadcast from a device. Furthermore, network traffic capture tools are widely available on the Internet.", # noqa: E501 - "It is recommends that domain lookups be disabled. Domain lookups can be disabled with the following command: no ip domain-lookup. If domain lookups are required, It is recommends that DNS be configured. DNS can be configured with the following command: ip name-server .", # noqa: E501 - parser.find_objects("ip name-server ")[0].linenum if len(parser.find_objects("ip name-server ")) > 0 else 0 + "It is recommends that domain lookups be disabled. Domain lookups can be disabled with the following command: no ip domain-lookup. If domain lookups are required, It is recommends that DNS be configured. DNS can be configured with the following command: ip name-server ." # noqa: E501 ) def analyze(self, config_file) -> None: diff --git a/src/analyze/cisco/ios/plugins/http_plugin.py b/src/analyze/cisco/ios/plugins/http_plugin.py index 00b3b20..b30f1f4 100644 --- a/src/analyze/cisco/ios/plugins/http_plugin.py +++ b/src/analyze/cisco/ios/plugins/http_plugin.py @@ -26,14 +26,12 @@ def _has_http(self, filename: str) -> bool: def get_cisco_ios_http(self, filename: str): if (self._has_http(filename)): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "HyperText Transport Protocol Service", "Recent Cisco IOS-based devices support web-based administration using the HTTP protocol. Cisco web-based administration facilities can sometimes be basic but they do provide a simple method of administering remote devices. However, HTTP is a clear-text protocol and is vulnerable to various packet-capture techniques.", # noqa: E501 "An attacker who was able to monitor network traffic could capture authentication credentials.", # noqa: E501 "Network packet and password sniffing tools are widely available on the Internet. Once authentication credentials have been captured it is trivial to use the credentials to log in using the captured credentials.", # noqa: E501 - "It is recommended that, if not required, the HTTP service be disabled. If a remote method of access to the device is required, consider using HTTPS or SSH. The encrypted HTTPS and SSH services may require a firmware or hardware upgrade. The HTTP service can be disabled with the following IOS command: no ip http server. If it is not possible to upgrade the device to use the encrypted HTTPS or SSH services, additional security can be configured.", # noqa: E501 - parser.find_objects("ip http server")[0].linenum if len(parser.find_objects("ip http server")) > 0 else 0 + "It is recommended that, if not required, the HTTP service be disabled. If a remote method of access to the device is required, consider using HTTPS or SSH. The encrypted HTTPS and SSH services may require a firmware or hardware upgrade. The HTTP service can be disabled with the following IOS command: no ip http server. If it is not possible to upgrade the device to use the encrypted HTTPS or SSH services, additional security can be configured." # noqa: E501 ) return None @@ -51,14 +49,12 @@ def _get_cisco_ios_http_access_list(self, filename: str): def get_cisco_ios_http_access_list(self, filename: str): if (self._get_cisco_ios_http_access_list(filename) is None): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "ACL restrict for HTTP service", "The HTTP service was not configured with an access-list to restrict network access to the device.", "An attacker who was able to monitor network traffic could capture authentication credentials. This issue is made more serious with the enable password being used for authentication as this would give the attacker full administrative access to the device with the captured credentials. This issue is mitigated slightly by employing an access list to restrict network access to the device.", # noqa: E501 "Network packet and password sniffing tools are widely available on the Internet. Once authentication credentials have been captured it is trivial to use the credentials to log in using the captured credentials. Furthermore, it may be possible for an attacker to masquerade as the administrators host in order to bypass configured network access restrictions.", # noqa: E501 - "If you can't disable HTTP, an access list can be configured to restrict access to the device. An access list can be specified with the following command:ip http access-class ", # noqa: E501 - parser.find_objects("ip http access-class")[0].linenum if len(parser.find_objects("ip http access-class")) > 0 else 0 + "If you can't disable HTTP, an access list can be configured to restrict access to the device. An access list can be specified with the following command:ip http access-class " # noqa: E501 ) return None @@ -76,14 +72,12 @@ def _get_cisco_ios_http_auth(self, filename: str) -> str: def get_cisco_ios_http_auth(self, filename: str): if (self._get_cisco_ios_http_auth(filename) == ""): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "Authentication mode to HTTP service", "The HTTP service was not configured with an access-list to restrict network access to the device.", "An attacker who was able to monitor network traffic could capture authentication credentials. This issue is made more serious with the enable password being used for authentication as this would give the attacker full administrative access to the device with the captured credentials. This issue is mitigated slightly by employing an access list to restrict network access to the device.", # noqa: E501 "Network packet and password sniffing tools are widely available on the Internet. Once authentication credentials have been captured it is trivial to use the credentials to log in using the captured credentials. Furthermore, it may be possible for an attacker to masquerade as the administrators host in order to bypass configured network access restrictions.", # noqa: E501 - "If you can't disable HTTP, the authentication method can be changed using the following command (where the authentication method is either local, enable, tacacs or aaa): ip http authentication ", # noqa: E501 - parser.find_objects("ip http auth")[0].linenum if len(parser.find_objects("ip http auth")) > 0 else 0 + "If you can't disable HTTP, the authentication method can be changed using the following command (where the authentication method is either local, enable, tacacs or aaa): ip http authentication " # noqa: E501 ) return None diff --git a/src/analyze/cisco/ios/plugins/ssh_plugin.py b/src/analyze/cisco/ios/plugins/ssh_plugin.py index d71118b..8256a22 100644 --- a/src/analyze/cisco/ios/plugins/ssh_plugin.py +++ b/src/analyze/cisco/ios/plugins/ssh_plugin.py @@ -37,14 +37,12 @@ def _get_cisco_ios_ssh_version(self, filename: str) -> str: def get_cisco_ios_ssh(self, filename: str): if (not self._has_cisco_ios_ssh(filename) or self._get_cisco_ios_ssh_version(filename) == "" or self._get_cisco_ios_ssh_version(filename) != "2"): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "SSH Protocol Version", "The SSH service is commonly used for encrypted command-based remote device management. There are multiple SSH protocol versions and SSH servers will often support multiple versions to maintain backwards compatibility. Although flaws have been identified in implementations of version 2 of the SSH protocol, fundamental flaws exist in SSH protocol version 1.", # noqa: E501 "An attacker who was able to intercept SSH protocol version 1 traffic would be able to perform a man-in-the-middle style attack. The attacker could then capture network traffic and possibly authentication credentials.", # noqa: E501 "Although vulnerabilities are widely known, exploiting the vulnerabilities in the SSH protocol can be difficult.", - "When SSH protocol version 2 support is configured on Cisco IOS devices, support for version 1 will be disabled. This can be configured with the following command: ip ssh version 2", # noqa: E501 - parser.find_objects("ip ssh version")[0].linenum if len(parser.find_objects("ip ssh version")) > 0 else 0 + "When SSH protocol version 2 support is configured on Cisco IOS devices, support for version 1 will be disabled. This can be configured with the following command: ip ssh version 2" # noqa: E501 ) return None @@ -63,14 +61,12 @@ def _get_cisco_ios_ssh_retries(self, filename: str) -> str: def get_cisco_ios_ssh_reties(self, filename: str): retries = self._get_cisco_ios_ssh_retries(filename) if (retries == "" or retries > 5): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "SSH retries misconfiguration", "The SSH service must have a defined number of retries, the recommended is between 0 and 5.", "Set a retries number allows to reduce the bruteforce and dictionary attacks. If a retry number is defined, the attacker can not test with an user multiple passwords.", # noqa: E501 "This issue improve the hardening of passwords in the network device.", - "This can be configured with the following command: ip ssh authentication-retries .", - parser.find_objects("ip ssh authentication-retries")[0].linenum if len(parser.find_objects("ip ssh authentication-retries")) else 0 + "This can be configured with the following command: ip ssh authentication-retries ." ) return None @@ -89,14 +85,12 @@ def _get_cisco_ios_ssh_timeout(self, filename: str) -> int: def get_cisco_ios_ssh_timeout(self, filename: str): timeout = self._get_cisco_ios_ssh_timeout(filename) if (timeout == 0 or timeout > 120): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "SSH timeout misconfiguration", "The SSH service must have a defined timeout between 0 and 60 seconds.", "Set a timeout allows disable not used or malicious sessions in background.", "This issue only increase the device management security, it is not exploitable.", - "This can be configured with the following command: ip ssh time-out .", - parser.find_objects("ip ssh time-out")[0].linenum if len(parser.find_objects("ip ssh time-out")) > 0 else 0 + "This can be configured with the following command: ip ssh time-out ." ) return None @@ -114,14 +108,12 @@ def _get_cisco_ios_ssh_interface(self, filename: str) -> str: def get_cisco_ios_ssh_interface(self, filename: str): if (self._get_cisco_ios_ssh_interface(filename) == ""): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "SSH source-interface enabled", "The SSH service must have a controlated set of source interfaces to manage the device", "To reduce bruteforce attacks is usefull have a set of source interfaces, logged and filtered, to access to SSH device management.", "This issue only increase the device management security, it is not exploitable, but it reduce bruteforce attacks.", - "This can be configured with the following command: ip ssh source-interface ", - parser.find_objects("ip ssh source-interface")[0].linenum if len(parser.find_objects("ip ssh source-interface")) > 0 else 0 + "This can be configured with the following command: ip ssh source-interface " ) return None diff --git a/src/analyze/cisco/ios/plugins/telnet_plugin.py b/src/analyze/cisco/ios/plugins/telnet_plugin.py new file mode 100644 index 0000000..6a25344 --- /dev/null +++ b/src/analyze/cisco/ios/plugins/telnet_plugin.py @@ -0,0 +1,42 @@ +# flake8: noqa + +from ..core.base_plugin import GenericPlugin +from ..issue.cisco_ios_issue import CiscoIOSIssue + +class PluginTelnet(GenericPlugin): + + def __init__(self): + super().__init__() + + def name(self): + return "Telnet configuration" + + def _get_cisco_ios_telnet(self, filename: str) -> bool: + parser = self.parse_cisco_ios_config_file(filename) + transport_disable = parser.find_objects("transport input none") + ssh_enable = parser.find_objects("transport input ssh") + telnet_disable = parser.find_objects("no transport input telnet") + if (len(transport_disable) > 0 or len(ssh_enable) > 0 or len(telnet_disable) > 0): # noqa: E501 + return False + else: + return True + + def get_telnet_configuration(self, filename: str): + if (self._get_cisco_ios_telnet(filename)): + return CiscoIOSIssue( + "Telnet", + "Telnet is widely used to provide remote command-based access to a variety of devices and is commonly used on network devices for remote administration. However, Telnet is a clear-text protocol and is vulnerable to various packet capture techniques.", # noqa: E501 + "An attacker who was able to monitor network traffic could capture sensitive information or authentication credentials.", # noqa: E501 + "Network packet and password sniffing tools are widely available on the Internet and some of the tools are specifically designed to capture clear-text protocol authentication credentials. However, in a switched environment an attacker may not be able to capture network traffic destined for other devices without employing an attack such as Address Resolution Protocol (ARP) spoofing.", + "Nipper recommends that, if possible, Telnet be disabled. If remote administrative access to the device is required, Nipper recommends that SSH be configured. The Telnet service can be disabled on individual lines with the following command: transport input none. The following Cisco IOS command can be used to disable Telnet on individual lines, but enable SSH: transport input ssh" # noqa: E501 + ) + return None + + def analyze(self, config_file) -> None: + issues = [] + + issues.append(self.get_telnet_configuration(config_file)) + + for issue in issues: + if issue is not None: + self.add_issue(issue) \ No newline at end of file diff --git a/src/analyze/cisco/ios/plugins/username_plugin.py b/src/analyze/cisco/ios/plugins/username_plugin.py index 073b515..81cab16 100644 --- a/src/analyze/cisco/ios/plugins/username_plugin.py +++ b/src/analyze/cisco/ios/plugins/username_plugin.py @@ -25,14 +25,12 @@ def _has_password_encryption(self, filename: str) -> bool: def get_users_without_password_encryption(self, filename: str): if (not self._has_password_encryption(filename)): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "Service Password Encryption", "Cisco service passwords are stored by default in their clear-text form rather than being encrypted. However, it is possible to have these passwords stored using the reversible Cisco encryption.", # noqa: E501 "If a malicious user were to see a Cisco configuration that contained clear-text passwords, they could use the passwords to access the device. However, an attacker who had access to a Cisco configuration file would easily be able to reverse the passwords.", # noqa: E501 "Encryption provide a greater level of security than clear-text passwords.", - "The Cisco password encryption service be enabled. The Cisco password encryption service can be started with the following Cisco IOS commands: service password-encryption or password encryption aes", # noqa: E501 - parser.find_objects("no service password-encryption")[0].linenum if len(parser.find_objects("no service password-encryption")) > 0 else 0 + "The Cisco password encryption service be enabled. The Cisco password encryption service can be started with the following Cisco IOS commands: service password-encryption or password encryption aes" # noqa: E501 ) return None @@ -46,14 +44,12 @@ def _has_cisco_password_0(self, filename: str) -> bool: def get_users_with_password_0(self, filename: str): if (self._has_cisco_password_0(filename)): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "Cisco type-0 passwords", "Cisco passwords are stored in their clear-text form rather than being encrypted when we use the type 0. However, it is possible to have these passwords stored using the reversible Cisco encryption.", "If a malicious user were to see a Cisco configuration that contained clear-text passwords, they could use the passwords to access the device. However, an attacker who had access to a Cisco configuration file would easily be able to reverse the passwords.", # noqa: E501 "Encryption provide a greater level of security than clear-text passwords.", - "The Cisco user passwords should be type 6, 8 or 9, and you can use the following Cisco IOS commands: username password [6|8|9] ", # noqa: E501 - parser.find_objects(r'username .+ password 0 .+')[0].linenum if len(parser.find_objects(r'username .+ password 0 .+')) > 0 else 0 + "The Cisco user passwords should be type 6, 8 or 9, and you can use the following Cisco IOS commands: username password [6|8|9] " # noqa: E501 ) return None @@ -67,14 +63,12 @@ def _has_cisco_password_7(self, filename: str) -> bool: def get_users_with_password_7(self, filename: str): if (self._has_cisco_password_7(filename)): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "Cisco type-7 passwords", "Cisco passwords are stored using an old and broken algorithm when we use the type 7 (Vigenere cypher). However, it is possible to have these passwords stored using stronger algorithms, like AES.", # noqa: E501 "If a malicious user were to see a Cisco configuration that contained Vigenere encrypted passwords, they could crack the passwords to access the device. However, an attacker who had access to a Cisco configuration file would easily be able to decrypt the passwords.", # noqa: E501 "Newer and strong encryption provide a greater level of security than Vigenere cyphered passwords.", - "The Cisco user passwords should be type 6, 8 or 9, and you can use the following Cisco IOS commands: username password [6|8|9] ", # noqa: E501 - parser.find_objects(r'username .+ password 7 .+')[0].linenum if len(parser.find_objects(r'username .+ password 7 .+')) > 0 else 0 + "The Cisco user passwords should be type 6, 8 or 9, and you can use the following Cisco IOS commands: username password [6|8|9] " # noqa: E501 ) return None @@ -88,14 +82,12 @@ def _has_cisco_password_5(self, filename: str) -> bool: def get_users_with_password_5(self, filename: str): if (self._has_cisco_password_5(filename)): - parser = self.parse_cisco_ios_config_file(filename) return CiscoIOSIssue( "Cisco type-5 passwords", "Cisco passwords are stored using an old and broken algorithm when we use the type 5 (salted MD5 hashing). However, it is possible to have these passwords stored using stronger algorithms, like AES.", # noqa: E501 "If a malicious user were to see a Cisco configuration that contained MD5 hashed passwords, they could crack the passwords to access the device using HashCat. However, an attacker who had access to a Cisco configuration file would easily be able to get the passwords.", # noqa: E501 "Newer and strong encryption provide a greater level of security than salted MD5 hashed passwords.", - "The Cisco user passwords should be type 6, 8 or 9, and you can use the following Cisco IOS commands: username password [6|8|9] ", # noqa: E501 - parser.find_objects(r'username .+ password 5 .+')[0].linenum if len(parser.find_objects(r'username .+ password 5 .+')) > 0 else 0 + "The Cisco user passwords should be type 6, 8 or 9, and you can use the following Cisco IOS commands: username password [6|8|9] " # noqa: E501 ) return None diff --git a/src/report/templates/html_template.html b/src/report/templates/html_template.html index 10aedbf..ee15a0d 100644 --- a/src/report/templates/html_template.html +++ b/src/report/templates/html_template.html @@ -27,7 +27,10 @@

Contents

{% endif %} {% if vulns %}
  • Vulnerabilities
  • - {% endif %} + {% endif %} + {% if exposed_passwords %} +
  • Exposed passwords
  • + {% endif %}
    @@ -42,6 +45,9 @@

    1. About This Report

    {% if vulns %}
  • a vulnerabilities section (optional) with all CVEs related with the device type and version
  • {% endif %} + {% if exposed_passwords %} +
  • a exposed password section that details the exposed secrets detected by pynipper-ng;
  • + {% endif %}