4
4
##
5
5
6
6
class MetasploitModule < Msf ::Auxiliary
7
-
8
7
include Msf ::Exploit ::Remote ::Tcp
9
8
include Msf ::Auxiliary ::Scanner
10
9
include Msf ::Auxiliary ::Report
@@ -40,7 +39,7 @@ def initialize
40
39
# poodle
41
40
[ 'URL' , 'https://security.googleblog.com/2014/10/this-poodle-bites-exploiting-ssl-30.html' ] ,
42
41
[ 'CVE' , '2014-3566' ] ,
43
- [ 'URL' , 'https://www.openssl.org/~bodo/ssl-poodle.pdf' ] ,
42
+ [ 'URL' , 'http://web.archive.org/web/20240319071045/ https://www.openssl.org/~bodo/ssl-poodle.pdf' ] ,
44
43
# TLS v1.0 and v1.1 depreciation
45
44
[ 'URL' , 'https://datatracker.ietf.org/doc/rfc8996/' ] ,
46
45
# SSLv2 deprecation
@@ -56,7 +55,7 @@ def initialize
56
55
# BEAST
57
56
[ 'CVE' , '2011-3389' ] ,
58
57
# RC4
59
- [ 'URL' , 'http://www.isg.rhul.ac.uk/tls/' ] ,
58
+ [ 'URL' , 'http://web.archive.org/web/20240607160328/https:// www.isg.rhul.ac.uk/tls/' ] ,
60
59
[ 'CVE' , '2013-2566' ] ,
61
60
# LOGJAM
62
61
[ 'CVE' , '2015-4000' ] ,
@@ -81,80 +80,6 @@ def initialize
81
80
)
82
81
end
83
82
84
- def get_metasploit_ssl_versions
85
- # There are two ways to generate a list of valid SSL Versions (SSLv3, TLS1.1, etc) and cipher suites (AES256-GCM-SHA384,
86
- # ECDHE-RSA-CHACHA20-POLY1305, etc). The first would be to generate them independently. It's possible to
87
- # pull all SSLContext methods (SSL Versions) via OpenSSL::SSL::SSLContext::METHODS here, as referenced in
88
- # https://github.com/rapid7/rex-socket/blob/6ea0bb3b4e19c53d73e4337617be72c0ed351ceb/lib/rex/socket/ssl_tcp.rb#L46
89
- # then pull all ciphers with OpenSSL::Cipher.ciphers. Now in theory you have a nice easy loop:
90
- #
91
- # OpenSSL::SSL::SSLContext::METHODS.each do |ssl_version|
92
- # OpenSSL::Cipher.ciphers.each do |cipher_suite|
93
- # # do something
94
- # end
95
- # end
96
- #
97
- # However, in practice we find that OpenSSL::SSL::SSLContext::METHODS includes '_client' and '_server' variants
98
- # such as :TLSv1, :TLSv1_client, :TLSv1_server. In this case, we only need :TLSv1, so we need to remove ~2/3 of the list.
99
- #
100
- # Next, we'll find that many ciphers in OpenSSL::Cipher.ciphers are not applicable for various SSL versions.
101
- # The loop we previously looked at has (at the time of writing on Kali Rollin, msf 6.2.23) 3060 rounds.
102
- # This is a lot of iterations when we already know there are many combinations that will not be applicable for our
103
- # use. Luckily there is a 2nd way which is much more efficient.
104
- #
105
- # The OpenSSL library includes https://docs.ruby-lang.org/en/2.4.0/OpenSSL/SSL/SSLContext.html#method-i-ciphers
106
- # which we can use to generate a list of all ciphers, and SSL versions they work with. The structure is:
107
- #
108
- # [[name, version, bits, alg_bits], ...]
109
- #
110
- # which makes it very easy to just pull the 2nd element (version, or SSL version) from each list item, and unique it.
111
- # This gives us the list of all SSL versions which also have at least one working cipher on our system.
112
- # Using this method we produce no unusable SSL versions or matching cipher suites and the list is 60 items long, so 1/51 the size.
113
- # Later in get_metasploit_ssl_cipher_suites, we can grab all cipher suites to a SSL version easily by simply filtering
114
- # the 2nd element (version, or SSL version) from each list item.
115
-
116
- if datastore [ 'SSLVersion' ] == 'All'
117
- return Array . new ( OpenSSL ::SSL ::SSLContext . new . ciphers . length ) { |i | ( OpenSSL ::SSL ::SSLContext . new . ciphers [ i ] [ 1 ] ) . to_s } . uniq . reverse
118
- end
119
-
120
- [ datastore [ 'SSLVersion' ] ]
121
- end
122
-
123
- def get_metasploit_ssl_cipher_suites ( ssl_version )
124
- # See comments in get_metasploit_ssl_versions for details on the use of
125
- # OpenSSL::SSL::SSLContext.new.ciphers vs other methods to generate
126
- # valid ciphers for a given SSL version
127
-
128
- # First find all valid ciphers that the Metasploit host supports.
129
- # Also transform the SSL version to a standard format.
130
- ssl_version = ssl_version . to_s . gsub ( '_' , '.' )
131
- all_ciphers = OpenSSL ::SSL ::SSLContext . new . ciphers
132
- valid_ciphers = [ ]
133
-
134
- # For each cipher that the Metasploit host supports, determine if that cipher
135
- # is supported for use with the SSL version passed into this function. If it is,
136
- # then add it to the valid_ciphers list.
137
- all_ciphers . each do |cipher |
138
- # cipher list has struct of [cipher, ssl_version, <int>, <int>]
139
- if cipher [ 1 ] == ssl_version
140
- valid_ciphers << cipher [ 0 ]
141
- end
142
- end
143
-
144
- # If the user wants to use all ciphers then return all valid ciphers.
145
- # Otherwise return only the one that matches the one the user specified
146
- # in the SSLCipher datastore option.
147
- #
148
- # If no match is found for some reason then we will return an empty array.
149
- if datastore [ 'SSLCipher' ] == 'All'
150
- return valid_ciphers
151
- elsif valid_ciphers . contains? datastore [ 'SSLCipher' ]
152
- return [ datastore [ 'SSLCipher' ] ]
153
- end
154
-
155
- [ ]
156
- end
157
-
158
83
def public_key_size ( cert )
159
84
if cert . public_key . respond_to? :n
160
85
return cert . public_key . n . num_bytes * 8
@@ -241,6 +166,22 @@ def print_cert(cert, ip)
241
166
end
242
167
end
243
168
169
+ # Process certificate with enhanced analysis
170
+ def process_certificate ( ip , cert )
171
+ print_cert ( cert , ip )
172
+
173
+ # Store certificate in loot with rex-sslscan metadata
174
+ loot_cert = store_loot (
175
+ 'ssl.certificate.rex_sslscan' ,
176
+ 'application/x-pem-file' ,
177
+ ip ,
178
+ cert . to_pem ,
179
+ "ssl_cert_#{ ip } _#{ rport } .pem" ,
180
+ "SSL Certificate from #{ ip } :#{ rport } "
181
+ )
182
+ print_good ( "Certificate saved to loot: #{ loot_cert } " )
183
+ end
184
+
244
185
def check_vulnerabilities ( ip , ssl_version , ssl_cipher , cert )
245
186
# POODLE
246
187
if ssl_version == 'SSLv3'
@@ -368,31 +309,6 @@ def check_vulnerabilities(ip, ssl_version, ssl_cipher, cert)
368
309
369
310
return if cert . nil?
370
311
371
- key_size = public_key_size ( cert )
372
- if key_size > 0
373
- if key_size == 1024
374
- print_good ( 'Public Key only 1024 bits' )
375
- report_vuln (
376
- host : ip ,
377
- port : rport ,
378
- proto : 'tcp' ,
379
- name : name ,
380
- info : "Module #{ fullname } confirmed certificate key size 1024 bits" ,
381
- refs : [ 'CWE-326' ]
382
- )
383
- elsif key_size < 1024
384
- print_good ( 'Public Key < 1024 bits' )
385
- report_vuln (
386
- host : ip ,
387
- port : rport ,
388
- proto : 'tcp' ,
389
- name : name ,
390
- info : "Module #{ fullname } confirmed certificate key size < 1024 bits" ,
391
- refs : [ 'CWE-326' ]
392
- )
393
- end
394
- end
395
-
396
312
# certificate signed md5
397
313
alg = cert . signature_algorithm
398
314
@@ -435,88 +351,143 @@ def check_vulnerabilities(ip, ssl_version, ssl_cipher, cert)
435
351
end
436
352
end
437
353
354
+ # Enhanced vulnerability checking leveraging rex-sslscan data
355
+ def check_vulnerabilities_enhanced ( ip , ssl_version , cipher_name , cert , is_weak_cipher )
356
+ check_vulnerabilities ( ip , ssl_version , cipher_name , cert )
357
+
358
+ if is_weak_cipher
359
+ print_good ( "#{ ip } :#{ rport } - Weak cipher detected: #{ cipher_name } " )
360
+ report_vuln (
361
+ host : ip ,
362
+ port : rport ,
363
+ proto : 'tcp' ,
364
+ name : name ,
365
+ info : "Module #{ fullname } detected weak cipher: #{ cipher_name } " ,
366
+ refs : [ 'CWE-327' ]
367
+ )
368
+ end
369
+ end
370
+
371
+ # Store comprehensive rex-sslscan results
372
+ def store_rex_sslscan_results ( ip , scan_result )
373
+ # Create detailed report
374
+ report_data = {
375
+ host : ip ,
376
+ port : rport ,
377
+ scan_timestamp : Time . now . utc ,
378
+ ssl_versions : {
379
+ sslv2_supported : scan_result . supports_sslv2? ,
380
+ sslv3_supported : scan_result . supports_sslv3? ,
381
+ tlsv1_supported : scan_result . supports_tlsv1? ,
382
+ tlsv1_1_supported : scan_result . supports_tlsv1_1? ,
383
+ tlsv1_2_supported : scan_result . supports_tlsv1_2?
384
+ } ,
385
+ cipher_summary : {
386
+ total_accepted : scan_result . accepted . length ,
387
+ total_rejected : scan_result . rejected . length ,
388
+ weak_ciphers : scan_result . weak_ciphers . length ,
389
+ strong_ciphers : scan_result . strong_ciphers . length
390
+ } ,
391
+ detailed_ciphers : scan_result . ciphers . to_a
392
+ }
393
+
394
+ # Store as JSON loot
395
+ loot_file = store_loot (
396
+ 'ssl.scan.rex_sslscan' ,
397
+ 'application/json' ,
398
+ ip ,
399
+ report_data . to_json ,
400
+ "ssl_scan_#{ ip } _#{ rport } .json" ,
401
+ "Rex::SSLScan results for #{ ip } :#{ rport } "
402
+ )
403
+ print_good ( "Detailed scan results saved to loot: #{ loot_file } " )
404
+ end
405
+
406
+ # Process rex-sslscan results
407
+ def process_rex_sslscan_results ( ip , scan_result )
408
+ # Report certificate if available
409
+ if scan_result . cert
410
+ process_certificate ( ip , scan_result . cert )
411
+ end
412
+
413
+ # Process accepted ciphers by version
414
+ %i[ SSLv2 SSLv3 TLSv1 TLSv1_1 TLSv1_2 ] . each do |version |
415
+ accepted_ciphers = scan_result . accepted ( version )
416
+ next if accepted_ciphers . empty?
417
+
418
+ print_good ( "#{ ip } :#{ rport } - #{ version } supported with #{ accepted_ciphers . length } cipher(s)" )
419
+
420
+ key_size = public_key_size ( scan_result . cert )
421
+ if key_size > 0
422
+ if key_size == 1024
423
+ print_good ( 'Public Key only 1024 bits' )
424
+ report_vuln (
425
+ host : ip ,
426
+ port : rport ,
427
+ proto : 'tcp' ,
428
+ name : name ,
429
+ info : "Module #{ fullname } confirmed certificate key size 1024 bits" ,
430
+ refs : [ 'CWE-326' ]
431
+ )
432
+ elsif key_size < 1024
433
+ print_good ( 'Public Key < 1024 bits' )
434
+ report_vuln (
435
+ host : ip ,
436
+ port : rport ,
437
+ proto : 'tcp' ,
438
+ name : name ,
439
+ info : "Module #{ fullname } confirmed certificate key size < 1024 bits" ,
440
+ refs : [ 'CWE-326' ]
441
+ )
442
+ end
443
+ end
444
+
445
+ accepted_ciphers . each do |cipher_info |
446
+ cipher_name = cipher_info [ :cipher ]
447
+ key_length = cipher_info [ :key_length ]
448
+ is_weak = cipher_info [ :weak ]
449
+
450
+ # Report the cipher
451
+ print_status ( " #{ version } : #{ cipher_name } (#{ key_length } bits)#{ is_weak ? ' - WEAK' : '' } " )
452
+
453
+ # Check for vulnerabilities using existing logic
454
+ check_vulnerabilities_enhanced ( ip , version . to_s , cipher_name , scan_result . cert , is_weak )
455
+ end
456
+ end
457
+
458
+ # Report weak ciphers summary
459
+ weak_ciphers = scan_result . weak_ciphers
460
+ if weak_ciphers . any?
461
+ print_bad ( "#{ ip } :#{ rport } - #{ weak_ciphers . length } weak cipher(s) detected" )
462
+ end
463
+
464
+ # Store comprehensive scan results in loot
465
+ store_rex_sslscan_results ( ip , scan_result )
466
+ end
467
+
438
468
# Fingerprint a single host
439
469
def run_host ( ip )
440
- # Get the available SSL/TLS versions that that Metasploit host supports
441
- versions = get_metasploit_ssl_versions
442
-
443
- certs_found = { }
444
- skip_ssl_version = false
445
- vprint_status ( "Scanning #{ ip } for: #{ versions . map ( &:to_s ) . join ( ', ' ) } " )
446
-
447
- # For each SSL/TLS version...
448
- versions . each do |version |
449
- skip_ssl_version = false
450
-
451
- # Get the cipher suites that SSL/TLS can use on the Metasploit host
452
- # and print them out.
453
- ciphers = get_metasploit_ssl_cipher_suites ( version )
454
- vprint_status ( "Scanning #{ ip } #{ version } with ciphers: #{ ciphers . map ( &:to_s ) . join ( ', ' ) } " )
455
-
456
- # For each cipher attempt to connect to the server. If we could connect with the given SSL version,
457
- # then skip it and move onto the next one. If the cipher isn't supported, then note this.
458
- # If the server responds with a peer certificate, make a new certificate object from it and find
459
- # its fingerprint, then check it for vulnerabilities, before saving it to loot if it hasn't been
460
- # saved already (check done using the certificate's SHA1 hash).
461
- #
462
- # In all cases the SSL version and cipher combination will also be checked for vulnerabilities
463
- # using the check_vulnerabilities function.
464
- ciphers . each do |cipher |
465
- break if skip_ssl_version
466
-
467
- vprint_status ( "Attempting connection with SSL Version: #{ version } , Cipher: #{ cipher } " )
468
- begin
469
- # setting the connect global to false means we can't see the socket, therefore the cert
470
- connect ( true , { 'SSL' => true , 'SSLVersion' => version . sub ( '.' , '_' ) . to_sym , 'SSLCipher' => cipher } ) # Force SSL
471
- print_good ( "Connected with SSL Version: #{ version } , Cipher: #{ cipher } " )
472
-
473
- if sock . respond_to? :peer_cert
474
- cert = OpenSSL ::X509 ::Certificate . new ( sock . peer_cert )
475
- # https://stackoverflow.com/questions/16516555/ruby-code-for-openssl-to-generate-fingerprint
476
- cert_fingerprint = OpenSSL ::Digest ::SHA1 . new ( cert . to_der ) . to_s
477
- if certs_found . key? cert_fingerprint
478
- # dont check the cert more than once if its the same cert
479
- check_vulnerabilities ( ip , version , cipher , nil )
480
- else
481
- loot_cert = store_loot ( 'ssl.certificate' , 'text/plain' , ip , cert . to_text )
482
- print_good ( "Certificate saved to loot: #{ loot_cert } " )
483
- print_cert ( cert , ip )
484
- check_vulnerabilities ( ip , version , cipher , cert )
485
- end
486
- certs_found [ cert_fingerprint ] = cert
487
- end
488
- rescue ::OpenSSL ::SSL ::SSLError => e
489
- error_message = e . message . match ( / state=(.+)$/ )
490
-
491
- if error_message . nil?
492
- vprint_error ( "\t SSL Connection Error: #{ e } " )
493
- next
494
- end
495
-
496
- # catch if the ssl_version/protocol isn't allowed and then we can skip out of it.
497
- if error_message [ 1 ] . include? 'no protocols available'
498
- skip_ssl_version = true
499
- vprint_error ( "\t Doesn't accept #{ version } connections, Skipping" )
500
- break
501
- end
502
- vprint_error ( "\t Does not accept #{ version } using cipher #{ cipher } , error message: #{ error_message [ 1 ] } " )
503
- rescue ArgumentError => e
504
- if e . message . match ( %r{This version of Ruby does not support the requested SSL/TLS version} )
505
- skip_ssl_version = true
506
- vprint_error ( "\t #{ e . message } , Skipping" )
507
- break
508
- end
509
- print_error ( "Exception encountered: #{ e } " )
510
- rescue StandardError => e
511
- if e . message . match ( /connection was refused/ ) || e . message . match ( /timed out/ )
512
- print_error ( "\t Port closed or timeout occurred." )
513
- return 'Port closed or timeout occurred.'
514
- end
515
- print_error ( "\t Exception encountered: #{ e } " )
516
- ensure
517
- disconnect
518
- end
470
+ print_status ( "Starting enhanced SSL/TLS scan of #{ ip } :#{ rport } " )
471
+
472
+ begin
473
+ ctx = { 'Msf' => framework , 'MsfExploit' => self }
474
+ # Initialize rex-sslscan scanner
475
+ scanner = Rex ::SSLScan ::Scanner . new ( ip , rport , ctx )
476
+
477
+ # Perform the scan
478
+ scan_result = scanner . scan
479
+
480
+ # Check if SSL/TLS is supported
481
+ unless scan_result . supports_ssl?
482
+ print_error ( "#{ ip } :#{ rport } - Server does not appear to support SSL/TLS" )
483
+ return
519
484
end
485
+
486
+ # Process and report results
487
+ process_rex_sslscan_results ( ip , scan_result )
488
+ rescue StandardError => e
489
+ print_error ( "#{ ip } :#{ rport } - Scan error: #{ e . message } " )
490
+ vprint_error ( "#{ ip } :#{ rport } - Backtrace: #{ e . backtrace } " )
520
491
end
521
492
end
522
493
end
0 commit comments