lib/scanner/ssl.rb in yawast-0.5.0.beta1 vs lib/scanner/ssl.rb in yawast-0.5.0.beta2
- old
+ new
@@ -17,92 +17,106 @@
ssl.connect
cert = ssl.peer_cert
unless cert.nil?
- Yawast::Utilities.puts_info 'Found X509 Certificate:'
- Yawast::Utilities.puts_info "\t\tIssued To: #{cert.subject.common_name} / #{cert.subject.organization}"
- Yawast::Utilities.puts_info "\t\tIssuer: #{cert.issuer.common_name} / #{cert.issuer.organization}"
- Yawast::Utilities.puts_info "\t\tVersion: #{cert.version}"
- Yawast::Utilities.puts_info "\t\tSerial: #{cert.serial}"
- Yawast::Utilities.puts_info "\t\tSubject: #{cert.subject}"
+ get_cert_info cert
+ end
- #check to see if cert is expired
- if cert.not_after > Time.now
- Yawast::Utilities.puts_info "\t\tExpires: #{cert.not_after}"
- else
- Yawast::Utilities.puts_vuln "\t\tExpires: #{cert.not_after} (Expired)"
- end
+ cert_chain = ssl.peer_cert_chain
+ get_cert_chain_info cert_chain, cert
- #check for SHA1 & MD5 certs
- if cert.signature_algorithm.include?('md5') || cert.signature_algorithm.include?('sha1')
- Yawast::Utilities.puts_vuln "\t\tSignature Algorithm: #{cert.signature_algorithm}"
- else
- Yawast::Utilities.puts_info "\t\tSignature Algorithm: #{cert.signature_algorithm}"
- end
+ puts "\t\tQualys SSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d=#{uri.host}&hideResults=on"
+ puts ''
- Yawast::Utilities.puts_info "\t\tKey: #{cert.public_key.class.to_s.gsub('OpenSSL::PKey::', '')}-#{get_x509_pub_key_strength(cert)}"
- Yawast::Utilities.puts_info "\t\t\tKey Hash: #{Digest::SHA1.hexdigest(cert.public_key.to_s)}"
- Yawast::Utilities.puts_info "\t\tExtensions:"
- cert.extensions.each { |ext| Yawast::Utilities.puts_info "\t\t\t#{ext}" unless ext.oid == 'subjectAltName' }
+ if check_ciphers
+ get_ciphers(uri)
+ end
- #alt names
- alt_names = cert.extensions.find {|e| e.oid == 'subjectAltName'}
- unless alt_names.nil?
- Yawast::Utilities.puts_info "\t\tAlternate Names:"
- alt_names.value.split(',').each { |name| Yawast::Utilities.puts_info "\t\t\t#{name.strip.delete('DNS:')}" }
- end
+ ssl.sysclose
- hash = Digest::SHA1.hexdigest(cert.to_der)
- Yawast::Utilities.puts_info "\t\tHash: #{hash}"
- puts "\t\t\thttps://censys.io/certificates?q=#{hash}"
- puts "\t\t\thttps://crt.sh/?q=#{hash}"
- puts ''
- end
+ get_tdes_session_msg_count(uri) if tdes_session_count
+ rescue => e
+ Yawast::Utilities.puts_error "SSL: Error Reading X509 Details: #{e.message}"
+ end
+ end
- cert_chain = ssl.peer_cert_chain
+ def self.get_cert_info(cert)
+ Yawast::Utilities.puts_info 'Found X509 Certificate:'
+ Yawast::Utilities.puts_info "\t\tIssued To: #{cert.subject.common_name} / #{cert.subject.organization}"
+ Yawast::Utilities.puts_info "\t\tIssuer: #{cert.issuer.common_name} / #{cert.issuer.organization}"
+ Yawast::Utilities.puts_info "\t\tVersion: #{cert.version}"
+ Yawast::Utilities.puts_info "\t\tSerial: #{cert.serial}"
+ Yawast::Utilities.puts_info "\t\tSubject: #{cert.subject}"
- if cert_chain.count == 1
- #HACK: This is an ugly way to guess if it's a missing intermediate, or self-signed
- #tIt looks like a change to Ruby's OpenSSL wrapper is needed to actually fix this right.
+ #check to see if cert is expired
+ if cert.not_after > Time.now
+ Yawast::Utilities.puts_info "\t\tExpires: #{cert.not_after}"
+ else
+ Yawast::Utilities.puts_vuln "\t\tExpires: #{cert.not_after} (Expired)"
+ end
- if cert.issuer == cert.subject
- Yawast::Utilities.puts_vuln "\t\tCertificate Is Self-Singed"
- else
- Yawast::Utilities.puts_warn "\t\tCertificate Chain Is Incomplete"
- end
+ #check for SHA1 & MD5 certs
+ if cert.signature_algorithm.include?('md5') || cert.signature_algorithm.include?('sha1')
+ Yawast::Utilities.puts_vuln "\t\tSignature Algorithm: #{cert.signature_algorithm}"
+ else
+ Yawast::Utilities.puts_info "\t\tSignature Algorithm: #{cert.signature_algorithm}"
+ end
- puts ''
- end
+ Yawast::Utilities.puts_info "\t\tKey: #{cert.public_key.class.to_s.gsub('OpenSSL::PKey::', '')}-#{get_x509_pub_key_strength(cert)}"
+ Yawast::Utilities.puts_info "\t\t\tKey Hash: #{Digest::SHA1.hexdigest(cert.public_key.to_s)}"
+ Yawast::Utilities.puts_info "\t\tExtensions:"
+ cert.extensions.each { |ext| Yawast::Utilities.puts_info "\t\t\t#{ext}" unless ext.oid == 'subjectAltName' || ext.oid == 'ct_precert_scts' }
- unless cert_chain.nil?
- Yawast::Utilities.puts_info 'Certificate: Chain'
- cert_chain.each do |c|
- Yawast::Utilities.puts_info "\t\tIssued To: #{c.subject.common_name} / #{c.subject.organization}"
- Yawast::Utilities.puts_info "\t\t\tIssuer: #{c.issuer.common_name} / #{c.issuer.organization}"
- Yawast::Utilities.puts_info "\t\t\tExpires: #{c.not_after}"
- Yawast::Utilities.puts_info "\t\t\tKey: #{c.public_key.class.to_s.gsub('OpenSSL::PKey::', '')}-#{get_x509_pub_key_strength(c)}"
- Yawast::Utilities.puts_info "\t\t\tSignature Algorithm: #{c.signature_algorithm}"
- Yawast::Utilities.puts_info "\t\t\tHash: #{Digest::SHA1.hexdigest(c.to_der)}"
- puts ''
- end
+ #ct_precert_scts
+ scts = cert.extensions.find {|e| e.oid == 'ct_precert_scts'}
+ unless scts.nil?
+ Yawast::Utilities.puts_info "\t\tSCTs:"
+ scts.value.split("\n").each { |line| puts "\t\t\t#{line}" }
+ end
- puts ''
+ #alt names
+ alt_names = cert.extensions.find {|e| e.oid == 'subjectAltName'}
+ unless alt_names.nil?
+ Yawast::Utilities.puts_info "\t\tAlternate Names:"
+ alt_names.value.split(',').each { |name| Yawast::Utilities.puts_info "\t\t\t#{name.strip.delete('DNS:')}" }
+ end
+
+ hash = Digest::SHA1.hexdigest(cert.to_der)
+ Yawast::Utilities.puts_info "\t\tHash: #{hash}"
+ puts "\t\t\thttps://censys.io/certificates?q=#{hash}"
+ puts "\t\t\thttps://crt.sh/?q=#{hash}"
+ puts ''
+ end
+
+ def self.get_cert_chain_info(cert_chain, cert)
+ if cert_chain.count == 1
+ #HACK: This is an ugly way to guess if it's a missing intermediate, or self-signed
+ #tIt looks like a change to Ruby's OpenSSL wrapper is needed to actually fix this right.
+
+ if cert.issuer == cert.subject
+ Yawast::Utilities.puts_vuln "\t\tCertificate Is Self-Singed"
+ else
+ Yawast::Utilities.puts_warn "\t\tCertificate Chain Is Incomplete"
end
- puts "\t\tQualys SSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d=#{uri.host}&hideResults=on"
puts ''
+ end
- if check_ciphers
- get_ciphers(uri)
+ unless cert_chain.nil?
+ Yawast::Utilities.puts_info 'Certificate: Chain'
+ cert_chain.each do |c|
+ Yawast::Utilities.puts_info "\t\tIssued To: #{c.subject.common_name} / #{c.subject.organization}"
+ Yawast::Utilities.puts_info "\t\t\tIssuer: #{c.issuer.common_name} / #{c.issuer.organization}"
+ Yawast::Utilities.puts_info "\t\t\tExpires: #{c.not_after}"
+ Yawast::Utilities.puts_info "\t\t\tKey: #{c.public_key.class.to_s.gsub('OpenSSL::PKey::', '')}-#{get_x509_pub_key_strength(c)}"
+ Yawast::Utilities.puts_info "\t\t\tSignature Algorithm: #{c.signature_algorithm}"
+ Yawast::Utilities.puts_info "\t\t\tHash: #{Digest::SHA1.hexdigest(c.to_der)}"
+ puts ''
end
- ssl.sysclose
-
- get_tdes_session_msg_count(uri) if tdes_session_count
- rescue => e
- Yawast::Utilities.puts_error "SSL: Error Reading X509 Details: #{e.message}"
+ puts ''
end
end
def self.get_ciphers(uri)
puts 'Supported Ciphers (based on your OpenSSL version):'
@@ -122,27 +136,31 @@
#ignore SSLv23, as it's an auto-negotiate, which just adds noise
if version.to_s != 'SSLv23'
#try to get the list of ciphers supported for each version
ciphers = nil
+ get_ciphers_failed = false
begin
ciphers = OpenSSL::SSL::SSLContext.new(version).ciphers
rescue => e
- Yawast::Utilities.puts_error "\tError getting cipher suites for #{version.to_s}, skipping. (#{e.message})"
+ Yawast::Utilities.puts_error "\tError getting cipher suites for #{version}, skipping. (#{e.message})"
+ get_ciphers_failed = true
end
if ciphers != nil
check_version_suites uri, ip, ciphers, version
+ elsif get_ciphers_failed == false
+ Yawast::Utilities.puts_info "\t#{version}: No cipher suites available."
end
end
end
puts ''
end
def self.check_version_suites(uri, ip, ciphers, version)
- puts "\tChecking for #{version.to_s} suites (#{ciphers.count} possible suites)"
+ puts "\tChecking for #{version} suites (#{ciphers.count} possible suites)"
ciphers.each do |cipher|
#try to connect and see what happens
begin
socket = TCPSocket.new(ip.to_s, uri.port)
@@ -151,37 +169,42 @@
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
ssl.hostname = uri.host
ssl.connect
- if cipher[2] < 112 || cipher[0].include?('RC4')
- #less than 112 bits or RC4, flag as a vuln
- Yawast::Utilities.puts_vuln "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
- elsif cipher[2] >= 128
- #secure, probably safe
- Yawast::Utilities.puts_info "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
- else
- #weak, but not "omg!" weak.
- Yawast::Utilities.puts_warn "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
- end
+ check_cipher_strength cipher, ssl
ssl.sysclose
rescue OpenSSL::SSL::SSLError => e
unless e.message.include?('alert handshake failure') ||
e.message.include?('no ciphers available') ||
e.message.include?('wrong version number') ||
- e.message.include?('alert protocol version')
+ e.message.include?('alert protocol version') ||
+ e.message.include?('Connection reset by peer')
Yawast::Utilities.puts_error "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}\t(Supported But Failed)"
end
rescue => e
Yawast::Utilities.puts_error "\t\tVersion: #{''.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}\t(#{e.message})"
ensure
ssl.sysclose unless ssl == nil
end
end
end
+ def self.check_cipher_strength(cipher, ssl)
+ if cipher[2] < 112 || cipher[0].include?('RC4')
+ #less than 112 bits or RC4, flag as a vuln
+ Yawast::Utilities.puts_vuln "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
+ elsif cipher[2] >= 128
+ #secure, probably safe
+ Yawast::Utilities.puts_info "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
+ else
+ #weak, but not "omg!" weak.
+ Yawast::Utilities.puts_warn "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
+ end
+ end
+
def self.check_hsts(head)
found = ''
head.each do |k, v|
if k.downcase.include? 'strict-transport-security'
@@ -192,59 +215,71 @@
if found == ''
Yawast::Utilities.puts_warn 'HSTS: Not Enabled'
else
Yawast::Utilities.puts_info "HSTS: Enabled (#{found})"
end
+ end
- puts ''
+ def self.check_hsts_preload(uri)
+ begin
+ info = JSON.parse(Net::HTTP.get(URI("https://hstspreload.com/api/v1/status/#{uri.host}")))
+
+ chrome = info['chrome'] != nil
+ firefox = info['firefox'] != nil
+ tor = info['tor'] != nil
+
+ Yawast::Utilities.puts_info "HSTS Preload: Chrome - #{chrome}; Firefox - #{firefox}; Tor - #{tor}"
+ rescue => e
+ Yawast::Utilities.puts_error "Error getting HSTS preload information: #{e.message}"
+ end
end
- def self.get_tdes_session_msg_count(uri)
- # this method will send a number of HEAD requests to see
- # if the connection is eventually killed.
- puts 'TLS Session Request Limit: Checking number of requests accepted using 3DES suites...'
+ def self.get_tdes_session_msg_count(uri)
+ # this method will send a number of HEAD requests to see
+ # if the connection is eventually killed.
+ puts 'TLS Session Request Limit: Checking number of requests accepted using 3DES suites...'
- count = 0
- begin
- req = Yawast::Shared::Http.get_http(uri)
- req.use_ssl = uri.scheme == 'https'
- req.keep_alive_timeout = 600
- headers = Yawast::Shared::Http.get_headers
+ count = 0
+ begin
+ req = Yawast::Shared::Http.get_http(uri)
+ req.use_ssl = uri.scheme == 'https'
+ req.keep_alive_timeout = 600
+ headers = Yawast::Shared::Http.get_headers
- #force 3DES - this is to ensure that 3DES specific limits are caught
- req.ciphers = ['3DES']
+ #force 3DES - this is to ensure that 3DES specific limits are caught
+ req.ciphers = ['3DES']
- req.start do |http|
- 10000.times do |i|
- http.head(uri.path, headers)
+ req.start do |http|
+ 10000.times do |i|
+ http.head(uri.path, headers)
- # hack to detect transparent disconnects
- if http.instance_variable_get(:@ssl_context).session_cache_stats[:cache_hits] != 0
- raise 'TLS Reconnected'
- end
+ # hack to detect transparent disconnects
+ if http.instance_variable_get(:@ssl_context).session_cache_stats[:cache_hits] != 0
+ raise 'TLS Reconnected'
+ end
- count += 1
+ count += 1
- if i % 20 == 0
- print '.'
- end
+ if i % 20 == 0
+ print '.'
end
end
- rescue => e
- puts
+ end
+ rescue => e
+ puts
- if e.message.include? 'alert handshake failure'
- Yawast::Utilities.puts_info 'TLS Session Request Limit: Server does not support 3DES cipher suites'
- else
- Yawast::Utilities.puts_info "TLS Session Request Limit: Connection terminated after #{count} requests (#{e.message})"
- end
-
- return
+ if e.message.include? 'alert handshake failure'
+ Yawast::Utilities.puts_info 'TLS Session Request Limit: Server does not support 3DES cipher suites'
+ else
+ Yawast::Utilities.puts_info "TLS Session Request Limit: Connection terminated after #{count} requests (#{e.message})"
end
- puts
- Yawast::Utilities.puts_vuln 'TLS Session Request Limit: Connection not terminated after 10,000 requests; possibly vulnerable to SWEET32'
+ return
end
+
+ puts
+ Yawast::Utilities.puts_vuln 'TLS Session Request Limit: Connection not terminated after 10,000 requests; possibly vulnerable to SWEET32'
+ end
#private methods
class << self
private
def get_x509_pub_key_strength(cert)