44##
55
66class MetasploitModule < Msf ::Auxiliary
7-
87 include Msf ::Exploit ::Remote ::Tcp
98 include Msf ::Auxiliary ::Scanner
109 include Msf ::Auxiliary ::Report
@@ -40,7 +39,7 @@ def initialize
4039 # poodle
4140 [ 'URL' , 'https://security.googleblog.com/2014/10/this-poodle-bites-exploiting-ssl-30.html' ] ,
4241 [ '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' ] ,
4443 # TLS v1.0 and v1.1 depreciation
4544 [ 'URL' , 'https://datatracker.ietf.org/doc/rfc8996/' ] ,
4645 # SSLv2 deprecation
@@ -56,7 +55,7 @@ def initialize
5655 # BEAST
5756 [ 'CVE' , '2011-3389' ] ,
5857 # 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/' ] ,
6059 [ 'CVE' , '2013-2566' ] ,
6160 # LOGJAM
6261 [ 'CVE' , '2015-4000' ] ,
@@ -81,80 +80,6 @@ def initialize
8180 )
8281 end
8382
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-
15883 def public_key_size ( cert )
15984 if cert . public_key . respond_to? :n
16085 return cert . public_key . n . num_bytes * 8
@@ -241,6 +166,22 @@ def print_cert(cert, ip)
241166 end
242167 end
243168
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+
244185 def check_vulnerabilities ( ip , ssl_version , ssl_cipher , cert )
245186 # POODLE
246187 if ssl_version == 'SSLv3'
@@ -368,31 +309,6 @@ def check_vulnerabilities(ip, ssl_version, ssl_cipher, cert)
368309
369310 return if cert . nil?
370311
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-
396312 # certificate signed md5
397313 alg = cert . signature_algorithm
398314
@@ -435,88 +351,143 @@ def check_vulnerabilities(ip, ssl_version, ssl_cipher, cert)
435351 end
436352 end
437353
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+
438468 # Fingerprint a single host
439469 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
519484 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 } " )
520491 end
521492 end
522493end
0 commit comments