Skip to content

Commit 28f1d90

Browse files
authored
Merge pull request #876 from Captain-T2004/SSL/TLS_MODULES
Added SSL/TLS Modules
2 parents f1ff5ed + 286c6ea commit 28f1d90

File tree

12 files changed

+1141
-5
lines changed

12 files changed

+1141
-5
lines changed

docs/Events.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@ The OWASP Nettacker Events page lists various conferences and meetups where the
2424
* OWASP Nettacker was presented at **OWASP Kyiv** Chapter by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=KrwQlgeZn7I)]
2525
* OWASP Nettacker was presented at the **AppSec Engineer** session by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=eXzIPuTtqAQ)]
2626
* OWASP Nettacker was presented at **Security BSides Dublin 2022** conference by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=GcRFkZEaWqI)]
27-
* OWASP Netacker was presented et the **Appplication Security Podcast** by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=tqZ8Lmucujw)]
27+
* OWASP Nettacker was presented at the **Application Security Podcast** by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=tqZ8Lmucujw)]
2828
* OWASP Nettacker was presented at the **OWASP Global AppSec DC 2023 Conference** by **Sam Stepanyan** [[1](https://www.youtube.com/watch?v=yZxjBme029A)]

docs/Modules.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,10 +148,11 @@ If you want to scan all ports please define -g 1-65535 range. Otherwise Nettacke
148148
* '**ProFTPd_integer_overflow_vuln**' - check ProFTPd for CVE-2011-1137
149149
* '**ProFTPd_memory_leak_vuln**' - check ProFTPd for CVE-2001-0136
150150
* '**ProFTPd_restriction_bypass_vuln**' - check ProFTPd for CVE-2009-3639
151-
* '**self_signed_certificate_vuln**' - check for self-signed SSL certificate
152151
* '**server_version_vuln**' - check if the web server is leaking server banner in 'Server' response header
153-
* '**ssl_certificate_expired_vuln**' - check if SSL certificate has expired
154-
* '**weak_signature_algorithm_vuln**'- check if SSL certificate is signed using SHA-1
152+
* '**ssl_signed_certificate_vuln**' - check for self-signed & other signing issues(weak signing algorithm) in SSL certificate
153+
* '**ssl_expired_certificate_vuln**' - check if SSL certificate has expired or is close to expiring
154+
* '**ssl_version_vuln**' - check if the server's SSL configuration supports old and insecure SSL versions
155+
* '**ssl_weak_cipher_vuln**' - check if server's SSL configuration supports weak cipher suites
155156
* '**wordpress_dos_cve_2018_6389_vuln**' - check if Wordpress is vulnerable to CVE-2018-6389 Denial Of Service (DOS)
156157
* '**wp_xmlrpc_bruteforce_vuln**' - check if Wordpress is vulnerable to credential Brute Force via XMLRPC wp.getUsersBlogs
157158
* '**wp_xmlrpc_pingback_vuln**' - check if Wordpress is vulnerable to XMLRPC pingback

nettacker/core/lib/ssl.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
import logging
2+
import socket
3+
import ssl
4+
from datetime import datetime, timezone
5+
6+
from OpenSSL import crypto
7+
8+
from nettacker.core.lib.base import BaseEngine, BaseLibrary
9+
10+
log = logging.getLogger(__name__)
11+
12+
13+
def is_weak_hash_algo(algo):
14+
algo = algo.lower()
15+
for unsafe_algo in ("md2", "md4", "md5", "sha1"):
16+
if unsafe_algo in algo:
17+
return True
18+
return False
19+
20+
21+
def create_socket_connection(context, host, port, timeout):
22+
socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
23+
socket_connection.settimeout(timeout)
24+
socket_connection.connect((host, port))
25+
socket_connection = context.wrap_socket(socket_connection, server_hostname=host)
26+
return socket_connection
27+
28+
29+
def is_weak_ssl_version(host, port, timeout):
30+
def test_ssl_version(host, port, timeout, ssl_version=None):
31+
try:
32+
context = ssl.SSLContext(ssl_version)
33+
socket_connection = create_socket_connection(context, host, port, timeout)
34+
return socket_connection.version()
35+
36+
except ssl.SSLError:
37+
return False
38+
39+
except (socket.timeout, ConnectionRefusedError, ConnectionResetError):
40+
return None
41+
42+
ssl_versions = (
43+
ssl.PROTOCOL_TLS_CLIENT, # TLS 1.3
44+
ssl.PROTOCOL_TLSv1_2,
45+
ssl.PROTOCOL_TLSv1_1,
46+
ssl.PROTOCOL_TLSv1,
47+
)
48+
supported_versions = []
49+
lowest_version = ""
50+
for ssl_version in ssl_versions:
51+
version = test_ssl_version(host, port, timeout, ssl_version=ssl_version)
52+
if version:
53+
lowest_version = version
54+
supported_versions.append(version)
55+
56+
return supported_versions, lowest_version not in {"TLSv1.2", "TLSv1.3"}
57+
58+
59+
def is_weak_cipher_suite(host, port, timeout):
60+
def test_single_cipher(host, port, cipher, timeout):
61+
try:
62+
context = ssl.create_default_context()
63+
context.check_hostname = False
64+
context.verify_mode = ssl.CERT_NONE
65+
context.set_ciphers(cipher)
66+
create_socket_connection(context, host, port, timeout)
67+
return True
68+
69+
except ssl.SSLError:
70+
return False
71+
72+
except (socket.timeout, ConnectionRefusedError, ConnectionResetError):
73+
return None
74+
75+
cipher_suites = [
76+
"HIGH", # OpenSSL cipher strings
77+
"MEDIUM",
78+
"LOW",
79+
"EXP",
80+
"eNULL",
81+
"aNULL",
82+
"RC4",
83+
"DES",
84+
"MD5",
85+
"SHA1",
86+
"DH",
87+
"ADH",
88+
"DHE",
89+
"ECDH",
90+
"ECDHE",
91+
"TLSv1",
92+
"TLSv1.1",
93+
"TLSv1.2",
94+
"TLSv1.3",
95+
]
96+
97+
supported_ciphers = []
98+
for cipher in cipher_suites:
99+
if test_single_cipher(host, port, cipher, timeout):
100+
supported_ciphers.append(cipher)
101+
102+
weak_ciphers = {"LOW", "EXP", "eNULL", "aNULL", "RC4", "DES", "MD5", "DH", "ADH"}
103+
for cipher in supported_ciphers:
104+
if cipher in weak_ciphers:
105+
return supported_ciphers, True
106+
107+
return supported_ciphers, False
108+
109+
110+
def create_tcp_socket(host, port, timeout):
111+
try:
112+
socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113+
socket_connection.settimeout(timeout)
114+
socket_connection.connect((host, port))
115+
ssl_flag = False
116+
except ConnectionRefusedError:
117+
return None
118+
119+
try:
120+
socket_connection = ssl.wrap_socket(socket_connection)
121+
ssl_flag = True
122+
except Exception:
123+
socket_connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
124+
socket_connection.settimeout(timeout)
125+
socket_connection.connect((host, port))
126+
127+
return socket_connection, ssl_flag
128+
129+
130+
def get_cert_info(cert):
131+
x509 = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
132+
weak_signing_algo = is_weak_hash_algo(str(x509.get_signature_algorithm()))
133+
cert_expires = datetime.strptime(x509.get_notAfter().decode("utf-8"), "%Y%m%d%H%M%S%z")
134+
cert_activation = datetime.strptime(x509.get_notBefore().decode("utf-8"), "%Y%m%d%H%M%S%z")
135+
issuer_str = ", ".join(
136+
f"{name.decode()}={value.decode()}" for name, value in x509.get_issuer().get_components()
137+
)
138+
subject_str = ", ".join(
139+
f"{name.decode()}={value.decode()}" for name, value in x509.get_subject().get_components()
140+
)
141+
return {
142+
"expired": x509.has_expired(),
143+
"self_signed": issuer_str == subject_str,
144+
"issuer": issuer_str,
145+
"subject": subject_str,
146+
"signing_algo": str(x509.get_signature_algorithm()),
147+
"weak_signing_algo": weak_signing_algo,
148+
"activation_date": cert_activation.strftime("%Y-%m-%d"),
149+
"not_activated": (cert_activation - datetime.now(timezone.utc)).days > 0,
150+
"expiration_date": cert_expires.strftime("%Y-%m-%d"),
151+
"expiring_soon": (cert_expires - datetime.now(timezone.utc)).days < 30,
152+
}
153+
154+
155+
class SslLibrary(BaseLibrary):
156+
def ssl_certificate_scan(self, host, port, timeout):
157+
tcp_socket = create_tcp_socket(host, port, timeout)
158+
if tcp_socket is None:
159+
return None
160+
161+
socket_connection, ssl_flag = tcp_socket
162+
peer_name = socket_connection.getpeername()
163+
scan_info = {
164+
"ssl_flag": ssl_flag,
165+
"peer_name": peer_name,
166+
"service": socket.getservbyport(int(port)),
167+
}
168+
169+
if ssl_flag:
170+
cert = ssl.get_server_certificate((host, port))
171+
cert_info = get_cert_info(cert)
172+
scan_info = cert_info | scan_info
173+
return scan_info
174+
175+
return scan_info
176+
177+
def ssl_version_and_cipher_scan(self, host, port, timeout):
178+
tcp_socket = create_tcp_socket(host, port, timeout)
179+
if tcp_socket is None:
180+
return None
181+
182+
socket_connection, ssl_flag = tcp_socket
183+
peer_name = socket_connection.getpeername()
184+
185+
if ssl_flag:
186+
try:
187+
cert = ssl.get_server_certificate((host, port))
188+
except ssl.SSLError:
189+
cert = None
190+
cert_info = get_cert_info(cert) if cert else None
191+
ssl_ver, weak_version = is_weak_ssl_version(host, port, timeout)
192+
cipher_suite, weak_cipher_suite = is_weak_cipher_suite(host, port, timeout)
193+
194+
return {
195+
"ssl_version": ssl_ver,
196+
"weak_version": weak_version,
197+
"cipher_suite": cipher_suite,
198+
"weak_cipher_suite": weak_cipher_suite,
199+
"issuer": cert_info["issuer"] if cert_info else "NA",
200+
"subject": cert_info["subject"] if cert_info else "NA",
201+
"expiration_date": cert_info["expiration_date"] if cert_info else "NA",
202+
"ssl_flag": ssl_flag,
203+
"peer_name": peer_name,
204+
"service": socket.getservbyport(int(port)),
205+
}
206+
207+
return {
208+
"ssl_flag": ssl_flag,
209+
"service": socket.getservbyport(int(port)),
210+
"peer_name": peer_name,
211+
}
212+
213+
214+
class SslEngine(BaseEngine):
215+
library = SslLibrary
216+
217+
def response_conditions_matched(self, sub_step, response):
218+
conditions = sub_step["response"]["conditions"]
219+
condition_type = sub_step["response"]["condition_type"]
220+
condition_results = {}
221+
if sub_step["method"] in {
222+
"ssl_certificate_scan",
223+
"ssl_version_and_cipher_scan",
224+
}:
225+
if response and response["ssl_flag"]:
226+
for condition in conditions:
227+
if "grouped_conditions" in condition:
228+
gc_type = conditions[condition]["condition_type"]
229+
gc_conditions = conditions[condition]["conditions"]
230+
gc_condition_results = {}
231+
for gc_condition in gc_conditions:
232+
if (
233+
gc_conditions[gc_condition]["reverse"]
234+
and not response[gc_condition]
235+
):
236+
gc_condition_results[gc_condition] = not response[gc_condition]
237+
238+
elif (
239+
not gc_conditions[gc_condition]["reverse"]
240+
and response[gc_condition]
241+
):
242+
gc_condition_results[gc_condition] = response[gc_condition]
243+
244+
if gc_type == "and":
245+
gc_condition_results = (
246+
gc_condition_results
247+
if len(gc_condition_results) == len(gc_conditions)
248+
else {}
249+
)
250+
251+
condition_results.update(gc_condition_results)
252+
253+
elif (conditions[condition]["reverse"] and not response[condition]) or (
254+
not conditions[condition]["reverse"] and response[condition]
255+
):
256+
condition_results[condition] = True
257+
258+
if condition_type == "and":
259+
return condition_results if len(condition_results) == len(conditions) else []
260+
if condition_type == "or":
261+
return condition_results if condition_results else []
262+
return []
263+
264+
return []
265+
266+
def apply_extra_data(self, sub_step, response):
267+
sub_step["response"]["ssl_flag"] = (
268+
response["ssl_flag"] if isinstance(response, dict) else False
269+
)
270+
sub_step["response"]["conditions_results"] = self.response_conditions_matched(
271+
sub_step, response
272+
)

nettacker/core/module.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,17 @@ def __init__(
4545
self.skip_service_discovery = options.skip_service_discovery
4646

4747
self.discovered_services = None
48-
self.ignored_core_modules = ["subdomain_scan", "icmp_scan", "port_scan"]
48+
self.ignored_core_modules = [
49+
"subdomain_scan",
50+
"icmp_scan",
51+
"port_scan",
52+
"ssl_weak_version_vuln",
53+
"ssl_weak_cipher_vuln",
54+
"ssl_certificate_weak_signature_vuln",
55+
"ssl_self_signed_certificate_vuln",
56+
"ssl_expired_certificate_vuln",
57+
"ssl_expiring_certificate_scan",
58+
]
4959

5060
contents = TemplateLoader("port_scan", {"target": ""}).load()
5161
self.service_discovery_signatures = list(
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
info:
2+
name: ssl_expiring_certificate_scan
3+
author: Captain-T2004
4+
severity: 6
5+
description: check if the ssl certificate is expiring soon
6+
reference:
7+
- https://www.beyondsecurity.com/resources/vulnerabilities/ssl-certificate-expiry
8+
profiles:
9+
- scan
10+
- ssl
11+
12+
payloads:
13+
- library: ssl
14+
steps:
15+
- method: ssl_certificate_scan
16+
timeout: 3
17+
host: "{target}"
18+
ports:
19+
- 21
20+
- 25
21+
- 110
22+
- 143
23+
- 443
24+
- 587
25+
- 990
26+
- 1080
27+
- 8080
28+
response:
29+
condition_type: or
30+
conditions:
31+
grouped_conditions:
32+
condition_type: and
33+
conditions:
34+
expiring_soon:
35+
reverse: false
36+
expiration_date:
37+
reverse: false
38+
subject:
39+
reverse: false
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
info:
2+
name: ssl_certificate_weak_signature_vuln
3+
author: Captain-T2004
4+
severity: 6
5+
description: check if there are any ssl_certificate vulnerabilities present
6+
reference:
7+
- https://www.ssl.com/article/ssl-tls-self-signed-certificates/
8+
profiles:
9+
- scan
10+
- ssl
11+
12+
payloads:
13+
- library: ssl
14+
steps:
15+
- method: ssl_certificate_scan
16+
timeout: 3
17+
host: "{target}"
18+
ports:
19+
- 21
20+
- 25
21+
- 110
22+
- 143
23+
- 443
24+
- 587
25+
- 990
26+
- 1080
27+
- 8080
28+
response:
29+
condition_type: or
30+
conditions:
31+
grouped_conditions:
32+
condition_type: and
33+
conditions:
34+
weak_signing_algo:
35+
reverse: false
36+
signing_algo:
37+
reverse: false

0 commit comments

Comments
 (0)