mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-08-28 23:26:18 +02:00
use the new rex-sslscan gem
remove old integerated code and replace it with the gem. done. MS-1693
This commit is contained in:
parent
4b2d6b623d
commit
029a28c95b
@ -40,6 +40,7 @@ PATH
|
||||
rex-random_identifier
|
||||
rex-registry
|
||||
rex-socket
|
||||
rex-sslscan
|
||||
rex-struct2
|
||||
rex-text
|
||||
rex-zip
|
||||
@ -241,6 +242,9 @@ GEM
|
||||
rex-registry (0.1.0)
|
||||
rex-socket (0.1.0)
|
||||
rex-core
|
||||
rex-sslscan (0.1.0)
|
||||
rex-socket
|
||||
rex-text
|
||||
rex-struct2 (0.1.0)
|
||||
rex-text (0.2.1)
|
||||
rex-zip (0.1.0)
|
||||
|
@ -1,220 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
require 'rex/socket'
|
||||
require 'rex/text/table'
|
||||
|
||||
module Rex::SSLScan
|
||||
class Result
|
||||
|
||||
attr_accessor :openssl_sslv2
|
||||
|
||||
attr_reader :ciphers
|
||||
attr_reader :supported_versions
|
||||
|
||||
def initialize()
|
||||
@cert = nil
|
||||
@ciphers = Set.new
|
||||
@supported_versions = [:SSLv2, :SSLv3, :TLSv1]
|
||||
@deprecated_weak_ciphers = [
|
||||
'ECDHE-RSA-DES-CBC3-SHA',
|
||||
'ECDHE-ECDSA-DES-CBC3-SHA',
|
||||
'SRP-DSS-3DES-EDE-CBC-SHA',
|
||||
'SRP-RSA-3DES-EDE-CBC-SHA',
|
||||
'SRP-3DES-EDE-CBC-SHA',
|
||||
'EDH-RSA-DES-CBC3-SHA',
|
||||
'EDH-DSS-DES-CBC3-SHA',
|
||||
'ECDH-RSA-DES-CBC3-SHA',
|
||||
'ECDH-ECDSA-DES-CBC3-SHA',
|
||||
'DES-CBC3-SHA',
|
||||
'PSK-3DES-EDE-CBC-SHA',
|
||||
'EXP-EDH-RSA-DES-CBC-SHA',
|
||||
'EXP-EDH-DSS-DES-CBC-SHA',
|
||||
'EXP-DES-CBC-SHA',
|
||||
'EXP-RC2-CBC-MD5',
|
||||
'EXP-RC4-MD5'
|
||||
]
|
||||
end
|
||||
|
||||
def cert
|
||||
@cert
|
||||
end
|
||||
|
||||
def cert=(input)
|
||||
unless input.kind_of? OpenSSL::X509::Certificate or input.nil?
|
||||
raise ArgumentError, "Must be an X509 Cert!"
|
||||
end
|
||||
@cert = input
|
||||
end
|
||||
|
||||
def sslv2
|
||||
@ciphers.reject{|cipher| cipher[:version] != :SSLv2 }
|
||||
end
|
||||
|
||||
def sslv3
|
||||
@ciphers.reject{|cipher| cipher[:version] != :SSLv3 }
|
||||
end
|
||||
|
||||
def tlsv1
|
||||
@ciphers.reject{|cipher| cipher[:version] != :TLSv1 }
|
||||
end
|
||||
|
||||
def weak_ciphers
|
||||
accepted.reject{|cipher| cipher[:weak] == false }
|
||||
end
|
||||
|
||||
def strong_ciphers
|
||||
accepted.reject{|cipher| cipher[:weak] }
|
||||
end
|
||||
|
||||
# Returns all accepted ciphers matching the supplied version
|
||||
# @param version [Symbol, Array] The SSL Version to filter on
|
||||
# @raise [ArgumentError] if the version supplied is invalid
|
||||
# @return [Array] An array of accepted cipher details matching the supplied versions
|
||||
def accepted(version = :all)
|
||||
enum_ciphers(:accepted, version)
|
||||
end
|
||||
|
||||
# Returns all rejected ciphers matching the supplied version
|
||||
# @param version [Symbol, Array] The SSL Version to filter on
|
||||
# @raise [ArgumentError] if the version supplied is invalid
|
||||
# @return [Array] An array of rejected cipher details matching the supplied versions
|
||||
def rejected(version = :all)
|
||||
enum_ciphers(:rejected, version)
|
||||
end
|
||||
|
||||
def each_accepted(version = :all)
|
||||
accepted(version).each do |cipher_result|
|
||||
yield cipher_result
|
||||
end
|
||||
end
|
||||
|
||||
def each_rejected(version = :all)
|
||||
rejected(version).each do |cipher_result|
|
||||
yield cipher_result
|
||||
end
|
||||
end
|
||||
|
||||
def supports_sslv2?
|
||||
!(accepted(:SSLv2).empty?)
|
||||
end
|
||||
|
||||
def supports_sslv3?
|
||||
!(accepted(:SSLv3).empty?)
|
||||
end
|
||||
|
||||
def supports_tlsv1?
|
||||
!(accepted(:TLSv1).empty?)
|
||||
end
|
||||
|
||||
def supports_ssl?
|
||||
supports_sslv2? or supports_sslv3? or supports_tlsv1?
|
||||
end
|
||||
|
||||
def supports_weak_ciphers?
|
||||
!(weak_ciphers.empty?)
|
||||
end
|
||||
|
||||
def standards_compliant?
|
||||
if supports_ssl?
|
||||
return false if supports_sslv2?
|
||||
return false if supports_weak_ciphers?
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Adds the details of a cipher test to the Result object.
|
||||
# @param version [Symbol] the SSL Version
|
||||
# @param cipher [String] the SSL cipher
|
||||
# @param key_length [Fixnum] the length of encryption key
|
||||
# @param status [Symbol] :accepted or :rejected
|
||||
def add_cipher(version, cipher, key_length, status)
|
||||
unless @supported_versions.include? version
|
||||
raise ArgumentError, "Must be a supported SSL Version"
|
||||
end
|
||||
unless OpenSSL::SSL::SSLContext.new(version).ciphers.flatten.include?(cipher) \
|
||||
|| @deprecated_weak_ciphers.include?(cipher)
|
||||
raise ArgumentError, "Must be a valid SSL Cipher for #{version}!"
|
||||
end
|
||||
unless key_length.kind_of? Fixnum
|
||||
raise ArgumentError, "Must supply a valid key length"
|
||||
end
|
||||
unless [:accepted, :rejected].include? status
|
||||
raise ArgumentError, "Status must be either :accepted or :rejected"
|
||||
end
|
||||
|
||||
strong_cipher_ctx = OpenSSL::SSL::SSLContext.new(version)
|
||||
# OpenSSL Directive For Strong Ciphers
|
||||
# See: http://www.rapid7.com/vulndb/lookup/ssl-weak-ciphers
|
||||
strong_cipher_ctx.ciphers = "ALL:!aNULL:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM"
|
||||
|
||||
if strong_cipher_ctx.ciphers.flatten.include? cipher
|
||||
weak = false
|
||||
else
|
||||
weak = true
|
||||
end
|
||||
|
||||
cipher_details = {:version => version, :cipher => cipher, :key_length => key_length, :weak => weak, :status => status}
|
||||
@ciphers << cipher_details
|
||||
end
|
||||
|
||||
def to_s
|
||||
unless supports_ssl?
|
||||
return "Server does not appear to support SSL on this port!"
|
||||
end
|
||||
table = Rex::Text::Table.new(
|
||||
'Header' => 'SSL Ciphers',
|
||||
'Indent' => 1,
|
||||
'Columns' => ['Status', 'Weak', 'SSL Version', 'Key Length', 'Cipher'],
|
||||
'SortIndex' => -1
|
||||
)
|
||||
ciphers.each do |cipher|
|
||||
if cipher[:weak]
|
||||
weak = '*'
|
||||
else
|
||||
weak = ' '
|
||||
end
|
||||
table << [cipher[:status].to_s.capitalize, weak , cipher[:version], cipher[:key_length], cipher[:cipher]]
|
||||
end
|
||||
|
||||
# Sort by SSL Version, then Key Length, and then Status
|
||||
table.rows.sort_by!{|row| [row[0],row[2],row[3]]}
|
||||
text = "#{table.to_s}"
|
||||
if @cert
|
||||
text << " \n\n #{@cert.to_text}"
|
||||
end
|
||||
if openssl_sslv2 == false
|
||||
text << "\n\n *** WARNING: Your OS hates freedom! Your OpenSSL libs are compiled without SSLv2 support!"
|
||||
end
|
||||
text
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# @param state [Symbol] Either :accepted or :rejected
|
||||
# @param version [Symbol, Array] The SSL Version to filter on (:SSLv2, :SSLv3, :TLSv1, :all)
|
||||
# @return [Set] The Set of cipher results matching the filter criteria
|
||||
def enum_ciphers(state, version = :all)
|
||||
case version
|
||||
when Symbol
|
||||
case version
|
||||
when :all
|
||||
return @ciphers.select{|cipher| cipher[:status] == state}
|
||||
when :SSLv2, :SSLv3, :TLSv1
|
||||
return @ciphers.select{|cipher| cipher[:status] == state and cipher[:version] == version}
|
||||
else
|
||||
raise ArgumentError, "Invalid SSL Version Supplied: #{version}"
|
||||
end
|
||||
when Array
|
||||
version = version.reject{|v| !(@supported_versions.include? v)}
|
||||
if version.empty?
|
||||
return @ciphers.select{|cipher| cipher[:status] == state}
|
||||
else
|
||||
return @ciphers.select{|cipher| cipher[:status] == state and version.include? cipher[:version]}
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "Was expecting Symbol or Array and got #{version.class}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,206 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
require 'rex/socket'
|
||||
require 'rex/sslscan/result'
|
||||
|
||||
module Rex::SSLScan
|
||||
|
||||
class Scanner
|
||||
|
||||
attr_accessor :context
|
||||
attr_accessor :host
|
||||
attr_accessor :port
|
||||
attr_accessor :timeout
|
||||
|
||||
attr_reader :supported_versions
|
||||
attr_reader :sslv2
|
||||
|
||||
# Initializes the scanner object
|
||||
# @param host [String] IP address or hostname to scan
|
||||
# @param port [Fixnum] Port number to scan, default: 443
|
||||
# @param timeout [Fixnum] Timeout for connections, in seconds. default: 5
|
||||
# @raise [StandardError] Raised when the configuration is invalid
|
||||
def initialize(host,port = 443,context = {},timeout=5)
|
||||
@host = host
|
||||
@port = port
|
||||
@timeout = timeout
|
||||
@context = context
|
||||
if check_opensslv2 == true
|
||||
@supported_versions = [:SSLv2, :SSLv3, :TLSv1]
|
||||
@sslv2 = true
|
||||
else
|
||||
@supported_versions = [:SSLv3, :TLSv1]
|
||||
@sslv2 = false
|
||||
end
|
||||
raise StandardError, "The scanner configuration is invalid" unless valid?
|
||||
end
|
||||
|
||||
# Checks whether the scanner option has a valid configuration
|
||||
# @return [Boolean] True or False, the configuration is valid.
|
||||
def valid?
|
||||
begin
|
||||
@host = Rex::Socket.getaddress(@host, true)
|
||||
rescue
|
||||
return false
|
||||
end
|
||||
return false unless @port.kind_of? Fixnum
|
||||
return false unless @port >= 0 and @port <= 65535
|
||||
return false unless @timeout.kind_of? Fixnum
|
||||
return true
|
||||
end
|
||||
|
||||
# Initiate the Scan against the target. Will test each cipher one at a time.
|
||||
# @return [Result] object containing the details of the scan
|
||||
def scan
|
||||
scan_result = Rex::SSLScan::Result.new
|
||||
scan_result.openssl_sslv2 = sslv2
|
||||
# If we can't get any SSL connection, then don't bother testing
|
||||
# individual ciphers.
|
||||
if test_ssl == :rejected and test_tls == :rejected
|
||||
return scan_result
|
||||
end
|
||||
|
||||
@supported_versions.each do |ssl_version|
|
||||
sslctx = OpenSSL::SSL::SSLContext.new(ssl_version)
|
||||
sslctx.ciphers.each do |cipher_name, ssl_ver, key_length, alg_length|
|
||||
status = test_cipher(ssl_version, cipher_name)
|
||||
scan_result.add_cipher(ssl_version, cipher_name, key_length, status)
|
||||
if status == :accepted and scan_result.cert.nil?
|
||||
scan_result.cert = get_cert(ssl_version, cipher_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
scan_result
|
||||
end
|
||||
|
||||
def test_ssl
|
||||
begin
|
||||
scan_client = Rex::Socket::Tcp.create(
|
||||
'Context' => @context,
|
||||
'PeerHost' => @host,
|
||||
'PeerPort' => @port,
|
||||
'SSL' => true,
|
||||
'SSLVersion' => :SSLv23,
|
||||
'Timeout' => @timeout
|
||||
)
|
||||
rescue ::Exception => e
|
||||
return :rejected
|
||||
ensure
|
||||
if scan_client
|
||||
scan_client.close
|
||||
end
|
||||
end
|
||||
return :accepted
|
||||
end
|
||||
|
||||
def test_tls
|
||||
begin
|
||||
scan_client = Rex::Socket::Tcp.create(
|
||||
'Context' => @context,
|
||||
'PeerHost' => @host,
|
||||
'PeerPort' => @port,
|
||||
'SSL' => true,
|
||||
'SSLVersion' => :TLSv1,
|
||||
'Timeout' => @timeout
|
||||
)
|
||||
rescue ::Exception => e
|
||||
return :rejected
|
||||
ensure
|
||||
if scan_client
|
||||
scan_client.close
|
||||
end
|
||||
end
|
||||
return :accepted
|
||||
end
|
||||
|
||||
# Tests the specified SSL Version and Cipher against the configured target
|
||||
# @param ssl_version [Symbol] The SSL version to use (:SSLv2, :SSLv3, :TLSv1)
|
||||
# @param cipher [String] The SSL Cipher to use
|
||||
# @return [Symbol] Either :accepted or :rejected
|
||||
def test_cipher(ssl_version, cipher)
|
||||
validate_params(ssl_version,cipher)
|
||||
begin
|
||||
scan_client = Rex::Socket::Tcp.create(
|
||||
'Context' => @context,
|
||||
'PeerHost' => @host,
|
||||
'PeerPort' => @port,
|
||||
'SSL' => true,
|
||||
'SSLVersion' => ssl_version,
|
||||
'SSLCipher' => cipher,
|
||||
'Timeout' => @timeout
|
||||
)
|
||||
rescue ::Exception => e
|
||||
return :rejected
|
||||
ensure
|
||||
if scan_client
|
||||
scan_client.close
|
||||
end
|
||||
end
|
||||
|
||||
return :accepted
|
||||
end
|
||||
|
||||
# Retrieve the X509 Cert from the target service,
|
||||
# @param ssl_version [Symbol] The SSL version to use (:SSLv2, :SSLv3, :TLSv1)
|
||||
# @param cipher [String] The SSL Cipher to use
|
||||
# @return [OpenSSL::X509::Certificate] if the certificate was retrieved
|
||||
# @return [Nil] if the cert couldn't be retrieved
|
||||
def get_cert(ssl_version, cipher)
|
||||
validate_params(ssl_version,cipher)
|
||||
begin
|
||||
scan_client = Rex::Socket::Tcp.create(
|
||||
'PeerHost' => @host,
|
||||
'PeerPort' => @port,
|
||||
'SSL' => true,
|
||||
'SSLVersion' => ssl_version,
|
||||
'SSLCipher' => cipher,
|
||||
'Timeout' => @timeout
|
||||
)
|
||||
cert = scan_client.peer_cert
|
||||
if cert.kind_of? OpenSSL::X509::Certificate
|
||||
return cert
|
||||
else
|
||||
return nil
|
||||
end
|
||||
rescue ::Exception => e
|
||||
return nil
|
||||
ensure
|
||||
if scan_client
|
||||
scan_client.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
# Validates that the SSL Version and Cipher are valid both seperately and
|
||||
# together as part of an SSL Context.
|
||||
# @param ssl_version [Symbol] The SSL version to use (:SSLv2, :SSLv3, :TLSv1)
|
||||
# @param cipher [String] The SSL Cipher to use
|
||||
# @raise [StandardError] If an invalid or unsupported SSL Version was supplied
|
||||
# @raise [StandardError] If the cipher is not valid for that version of SSL
|
||||
def validate_params(ssl_version, cipher)
|
||||
raise StandardError, "The scanner configuration is invalid" unless valid?
|
||||
unless @supported_versions.include? ssl_version
|
||||
raise StandardError, "SSL Version must be one of: #{@supported_versions.to_s}"
|
||||
end
|
||||
if ssl_version == :SSLv2 and sslv2 == false
|
||||
raise StandardError, "Your OS hates freedom! Your OpenSSL libs are compiled without SSLv2 support!"
|
||||
else
|
||||
unless OpenSSL::SSL::SSLContext.new(ssl_version).ciphers.flatten.include? cipher
|
||||
raise StandardError, "Must be a valid SSL Cipher for #{ssl_version}!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_opensslv2
|
||||
begin
|
||||
OpenSSL::SSL::SSLContext.new(:SSLv2)
|
||||
rescue
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -136,6 +136,8 @@ Gem::Specification.new do |spec|
|
||||
spec.add_runtime_dependency 'rex-ole'
|
||||
# Rex Socket Abstraction Layer
|
||||
spec.add_runtime_dependency 'rex-socket'
|
||||
# Library for scanning a server's SSL/TLS capabilities
|
||||
spec.add_runtime_dependency 'rex-sslscan'
|
||||
|
||||
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
|
||||
# NoMethodError undefined method `dlopen' for Fiddle:Module
|
||||
|
@ -1,527 +0,0 @@
|
||||
# -*- coding:binary -*-
|
||||
require 'rex/sslscan/result'
|
||||
|
||||
RSpec.describe Rex::SSLScan::Result do
|
||||
|
||||
subject{Rex::SSLScan::Result.new}
|
||||
|
||||
it { is_expected.to respond_to :accepted }
|
||||
it { is_expected.to respond_to :cert }
|
||||
it { is_expected.to respond_to :ciphers }
|
||||
it { is_expected.to respond_to :rejected }
|
||||
it { is_expected.to respond_to :sslv2 }
|
||||
it { is_expected.to respond_to :sslv3 }
|
||||
it { is_expected.to respond_to :standards_compliant? }
|
||||
it { is_expected.to respond_to :strong_ciphers }
|
||||
it { is_expected.to respond_to :supports_ssl? }
|
||||
it { is_expected.to respond_to :supports_sslv2? }
|
||||
it { is_expected.to respond_to :supports_sslv3? }
|
||||
it { is_expected.to respond_to :supports_tlsv1? }
|
||||
it { is_expected.to respond_to :supports_weak_ciphers? }
|
||||
it { is_expected.to respond_to :tlsv1 }
|
||||
it { is_expected.to respond_to :weak_ciphers }
|
||||
|
||||
context "with no values set" do
|
||||
it "should return nil for the cert" do
|
||||
expect(subject.cert).to eq nil
|
||||
end
|
||||
|
||||
it "should return an empty set for ciphers" do
|
||||
expect(subject.ciphers).to be_empty
|
||||
end
|
||||
|
||||
it "should return an empty array for accepted" do
|
||||
expect(subject.accepted).to eq []
|
||||
end
|
||||
|
||||
it "should return an empty array for rejected" do
|
||||
expect(subject.rejected).to eq []
|
||||
end
|
||||
|
||||
it "should return an empty array for #sslv2" do
|
||||
expect(subject.sslv2).to eq []
|
||||
end
|
||||
|
||||
it "should return an empty array for #sslv3" do
|
||||
expect(subject.sslv3).to eq []
|
||||
end
|
||||
|
||||
it "should return an empty array for #tlsv1" do
|
||||
expect(subject.tlsv1).to eq []
|
||||
end
|
||||
|
||||
it "should return an empty array for #weak_ciphers" do
|
||||
expect(subject.weak_ciphers).to eq []
|
||||
end
|
||||
|
||||
it "should return an empty array for #strong_ciphers" do
|
||||
expect(subject.strong_ciphers).to eq []
|
||||
end
|
||||
|
||||
it "should return false for #supports_ssl?" do
|
||||
expect(subject.supports_ssl?).to eq false
|
||||
end
|
||||
|
||||
it "should return false for #supports_ssl?v2" do
|
||||
expect(subject.supports_sslv2?).to eq false
|
||||
end
|
||||
|
||||
it "should return false for #supports_sslv3?" do
|
||||
expect(subject.supports_sslv3?).to eq false
|
||||
end
|
||||
|
||||
it "should return false for #supports_tlsv1?" do
|
||||
expect(subject.supports_tlsv1?).to eq false
|
||||
end
|
||||
|
||||
it "should return false for #supports_weak_ciphers?" do
|
||||
expect(subject.supports_weak_ciphers?).to eq false
|
||||
end
|
||||
|
||||
it "should return true for #standards_compliant?" do
|
||||
expect(subject.standards_compliant?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context "setting the cert" do
|
||||
it "should accept nil" do
|
||||
subject.cert = nil
|
||||
expect(subject.cert).to eq nil
|
||||
end
|
||||
|
||||
it "should accept an X509 cert" do
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
subject.cert = cert
|
||||
expect(subject.cert).to eq cert
|
||||
end
|
||||
|
||||
it "should raise an exception for anything else" do
|
||||
expect{subject.cert = "foo"}.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
context "adding a cipher result" do
|
||||
context "should raise an exception if" do
|
||||
it "given an invalid SSL version" do
|
||||
expect{subject.add_cipher(:ssl3, 'AES256-SHA', 256, :accepted )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given SSL version as a string" do
|
||||
expect{subject.add_cipher('sslv3', 'AES256-SHA', 256, :accepted )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given an invalid SSL cipher" do
|
||||
expect{subject.add_cipher(:SSLv3, 'FOO256-SHA', 256, :accepted )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given an unsupported cipher for the version" do
|
||||
expect{subject.add_cipher(:SSLv3, 'DES-CBC3-MD5', 256, :accepted )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given a non-number for key length" do
|
||||
expect{subject.add_cipher(:SSLv3, 'AES256-SHA', "256", :accepted )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given a decimal key length" do
|
||||
expect{subject.add_cipher(:SSLv3, 'AES256-SHA', 25.6, :accepted )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given an invalid status" do
|
||||
expect{subject.add_cipher(:SSLv3, 'AES256-SHA', 256, :good )}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "given status as a string" do
|
||||
expect{subject.add_cipher(:SSLv3, 'AES256-SHA', 256, "accepted" )}.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
context "that was accepted" do
|
||||
it "should add an SSLv2 cipher result to the SSLv2 Accepted array or generate an SSLv2 exception" do
|
||||
begin
|
||||
subject.add_cipher(:SSLv2, "DES-CBC3-MD5", 168, :accepted)
|
||||
expect(subject.accepted(:SSLv2)).to include({
|
||||
:version => :SSLv2,
|
||||
:cipher=>"DES-CBC3-MD5",
|
||||
:key_length=>168,
|
||||
:weak=> false,
|
||||
:status => :accepted})
|
||||
rescue ArgumentError => e
|
||||
expect(e.message).to eq "unknown SSL method `SSLv2'."
|
||||
end
|
||||
end
|
||||
|
||||
it "should add an SSLv3 cipher result to the SSLv3 Accepted array" do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
expect(subject.accepted(:SSLv3)).to include({
|
||||
:version => :SSLv3,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :accepted})
|
||||
end
|
||||
|
||||
it "should add an TLSv1 cipher result to the TLSv1 Accepted array" do
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
expect(subject.accepted(:TLSv1)).to include({
|
||||
:version => :TLSv1,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :accepted})
|
||||
end
|
||||
|
||||
it "should successfully add multiple entries in a row" do
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
expect(subject.accepted(:SSLv3)).to include({
|
||||
:version => :SSLv3,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :accepted})
|
||||
expect(subject.accepted(:SSLv3)).to include({
|
||||
:version => :SSLv3,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :accepted})
|
||||
end
|
||||
|
||||
it "should not add duplicate entries" do
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
expect(subject.accepted(:SSLv3).count).to eq 1
|
||||
end
|
||||
end
|
||||
context "that was rejected" do
|
||||
it "should add an SSLv2 cipher result to the SSLv2 Rejected array or generate an SSLv2 exception" do
|
||||
begin
|
||||
subject.add_cipher(:SSLv2, "DES-CBC3-MD5", 168, :rejected)
|
||||
expect(subject.rejected(:SSLv2)).to include({
|
||||
:version => :SSLv2,
|
||||
:cipher=>"DES-CBC3-MD5",
|
||||
:key_length=>168,
|
||||
:weak=> false,
|
||||
:status => :rejected})
|
||||
rescue ArgumentError => e
|
||||
expect(e.message).to eq "unknown SSL method `SSLv2'."
|
||||
end
|
||||
end
|
||||
|
||||
it "should add an SSLv3 cipher result to the SSLv3 Rejected array" do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :rejected)
|
||||
expect(subject.rejected(:SSLv3)).to include({
|
||||
:version => :SSLv3,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :rejected})
|
||||
end
|
||||
|
||||
it "should add an TLSv1 cipher result to the TLSv1 Rejected array" do
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :rejected)
|
||||
expect(subject.rejected(:TLSv1)).to include({
|
||||
:version => :TLSv1,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :rejected})
|
||||
end
|
||||
|
||||
it "should successfully add multiple entries in a row" do
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :rejected)
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :rejected)
|
||||
expect(subject.rejected(:SSLv3)).to include({
|
||||
:version => :SSLv3,
|
||||
:cipher=>"AES256-SHA",
|
||||
:key_length=>256,
|
||||
:weak=> false,
|
||||
:status => :rejected})
|
||||
expect(subject.rejected(:SSLv3)).to include({
|
||||
:version => :SSLv3,
|
||||
:cipher=>"AES128-SHA",
|
||||
:key_length=>128,
|
||||
:weak=> false,
|
||||
:status => :rejected})
|
||||
end
|
||||
|
||||
it "should not add duplicate entries" do
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :rejected)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :rejected)
|
||||
expect(subject.rejected(:SSLv3).count).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "enumerating all accepted ciphers" do
|
||||
before(:example) do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
end
|
||||
|
||||
context "with no version selected" do
|
||||
it "should return an array of cipher detail hashes" do
|
||||
subject.each_accepted do |cipher_details|
|
||||
expect(cipher_details).to include(:version, :cipher, :key_length, :status, :weak)
|
||||
end
|
||||
end
|
||||
|
||||
it "should return all of the accepted cipher details" do
|
||||
count = 0
|
||||
subject.each_accepted do |cipher_details|
|
||||
count = count+1
|
||||
end
|
||||
expect(count).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
context "when specifying one SSL version" do
|
||||
it "should raise an exception if not given a symbol" do
|
||||
expect{ subject.each_accepted('sslv2')}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "should raise an exception if given an invalid SSL version" do
|
||||
expect{ subject.each_accepted(:TLSv3)}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "should return only ciphers matching the version" do
|
||||
subject.each_accepted(:SSLv3) do |cipher_details|
|
||||
expect(cipher_details[:version]).to eq :SSLv3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when specifying multiple SSL Versions in an array" do
|
||||
it "should return all versions if no valid versions were supplied" do
|
||||
count = 0
|
||||
subject.each_accepted([:TLSv3, :TLSv4]) do |cipher_details|
|
||||
count = count+1
|
||||
end
|
||||
expect(count).to eq 3
|
||||
end
|
||||
|
||||
it "should return only the ciphers for the specified version" do
|
||||
subject.each_accepted([:SSLv3,:TLSv1]) do |cipher_details|
|
||||
expect(cipher_details[:version]).not_to eq :SSLv2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "enumerating all rejected ciphers" do
|
||||
before(:example) do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :rejected)
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :rejected)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :rejected)
|
||||
end
|
||||
|
||||
context "with no version selected" do
|
||||
it "should return an array of cipher detail hashes" do
|
||||
subject.each_rejected do |cipher_details|
|
||||
expect(cipher_details).to include(:version, :cipher, :key_length, :status, :weak)
|
||||
end
|
||||
end
|
||||
|
||||
it "should return all of the rejected cipher details" do
|
||||
count = 0
|
||||
subject.each_rejected do |cipher_details|
|
||||
count = count+1
|
||||
end
|
||||
expect(count).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
context "when specifying one SSL version" do
|
||||
it "should raise an exception if not given a symbol" do
|
||||
expect{ subject.each_rejected('sslv2')}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "should raise an exception if given an invalid SSL version" do
|
||||
expect{ subject.each_rejected(:TLSv3)}.to raise_error(ArgumentError)
|
||||
end
|
||||
|
||||
it "should return only ciphers matching the version" do
|
||||
subject.each_rejected(:SSLv3) do |cipher_details|
|
||||
expect(cipher_details[:version]).to eq :SSLv3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when specifying multiple SSL Versions in an array" do
|
||||
it "should return all versions if no valid versions were supplied" do
|
||||
count = 0
|
||||
subject.each_rejected([:TLSv3, :TLSv4]) do |cipher_details|
|
||||
count = count+1
|
||||
end
|
||||
expect(count).to eq 3
|
||||
end
|
||||
|
||||
it "should return only the ciphers for the specified version" do
|
||||
subject.each_rejected([:SSLv3,:TLSv1]) do |cipher_details|
|
||||
expect(cipher_details[:version]).not_to eq :SSLv2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "checking SSL support" do
|
||||
context "for SSLv2" do
|
||||
it "should return false if there are no accepted ciphers" do
|
||||
expect(subject.supports_sslv2?).to eq false
|
||||
end
|
||||
it "should return true if there are accepted ciphers or raise an SSLv2 exception" do
|
||||
begin
|
||||
subject.add_cipher(:SSLv2, "DES-CBC3-MD5", 168, :accepted)
|
||||
expect(subject.supports_sslv2?).to eq true
|
||||
rescue ArgumentError => e
|
||||
expect(e.message).to eq "unknown SSL method `SSLv2'."
|
||||
end
|
||||
end
|
||||
end
|
||||
context "for SSLv3" do
|
||||
it "should return false if there are no accepted ciphers" do
|
||||
expect(subject.supports_sslv3?).to eq false
|
||||
end
|
||||
it "should return true if there are accepted ciphers" do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
expect(subject.supports_sslv3?).to eq true
|
||||
end
|
||||
end
|
||||
context "for TLSv1" do
|
||||
it "should return false if there are no accepted ciphers" do
|
||||
expect(subject.supports_tlsv1?).to eq false
|
||||
end
|
||||
it "should return true if there are accepted ciphers" do
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
expect(subject.supports_tlsv1?).to eq true
|
||||
end
|
||||
end
|
||||
context "for SSL at large" do
|
||||
it "should return false if there are no accepted ciphers" do
|
||||
expect(subject.supports_ssl?).to eq false
|
||||
end
|
||||
it "should return true if there are accepted ciphers" do
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
expect(subject.supports_ssl?).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "checking for weak ciphers" do
|
||||
context "when weak ciphers are supported" do
|
||||
before(:example) do
|
||||
subject.add_cipher(:SSLv3, "EXP-RC4-MD5", 40, :accepted)
|
||||
subject.add_cipher(:SSLv3, "DES-CBC-SHA", 56, :accepted)
|
||||
end
|
||||
it "should return an array of weak ciphers from #weak_ciphers" do
|
||||
weak = subject.weak_ciphers
|
||||
expect(weak.class).to eq Array
|
||||
weak.each do |cipher|
|
||||
expect(cipher[:weak]).to eq true
|
||||
end
|
||||
expect(weak.count).to eq 2
|
||||
end
|
||||
|
||||
it "should return true from #supports_weak_ciphers" do
|
||||
expect(subject.supports_weak_ciphers?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context "when no weak ciphers are supported" do
|
||||
before(:example) do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
end
|
||||
it "should return an empty array from #weak_ciphers" do
|
||||
expect(subject.weak_ciphers).to eq []
|
||||
end
|
||||
|
||||
it "should return false from #supports_weak_ciphers" do
|
||||
expect(subject.supports_weak_ciphers?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "checking for standards compliance" do
|
||||
it "should return true if there is no SSL support" do
|
||||
expect(subject.standards_compliant?).to eq true
|
||||
end
|
||||
|
||||
it "should return false if SSLv2 is supported or raise an SSLv2 exception" do
|
||||
begin
|
||||
subject.add_cipher(:SSLv2, "DES-CBC3-MD5", 168, :accepted)
|
||||
expect(subject.standards_compliant?).to eq false
|
||||
rescue ArgumentError => e
|
||||
expect(e.message).to eq "unknown SSL method `SSLv2'."
|
||||
end
|
||||
end
|
||||
|
||||
it "should return false if weak ciphers are supported" do
|
||||
subject.add_cipher(:SSLv3, "EXP-RC2-CBC-MD5", 40, :accepted)
|
||||
expect(subject.standards_compliant?).to eq false
|
||||
end
|
||||
|
||||
it "should return true if SSLv2 and Weak Ciphers are disabled" do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
expect(subject.standards_compliant?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context "when printing the results" do
|
||||
context "when OpenSSL is compiled without SSLv2" do
|
||||
before(:example) do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
subject.openssl_sslv2 = false
|
||||
end
|
||||
it "should warn the user" do
|
||||
expect(subject.to_s).to include "*** WARNING: Your OS hates freedom! Your OpenSSL libs are compiled without SSLv2 support!"
|
||||
end
|
||||
end
|
||||
|
||||
context "when we have SSL results" do
|
||||
before(:example) do
|
||||
subject.add_cipher(:SSLv3, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:TLSv1, "AES256-SHA", 256, :accepted)
|
||||
subject.add_cipher(:SSLv3, "AES128-SHA", 128, :accepted)
|
||||
subject.add_cipher(:SSLv3, "EXP-RC2-CBC-MD5", 40, :accepted)
|
||||
|
||||
cert = OpenSSL::X509::Certificate.new
|
||||
key = OpenSSL::PKey::RSA.new 2048
|
||||
cert.version = 2 #
|
||||
cert.serial = 1
|
||||
cert.subject = OpenSSL::X509::Name.parse "/DC=org/DC=ruby-lang/CN=Ruby CA"
|
||||
cert.issuer = cert.subject
|
||||
cert.public_key = key.public_key
|
||||
cert.not_before = Time.now
|
||||
cert.not_after = cert.not_before + 2 * 365 * 24 * 60 * 60 # 2
|
||||
|
||||
subject.cert = cert
|
||||
end
|
||||
|
||||
it "should contain the certificate" do
|
||||
expect(subject.to_s).to include "Issuer: DC=org, DC=ruby-lang, CN=Ruby CA"
|
||||
expect(subject.to_s).to include "Subject: DC=org, DC=ruby-lang, CN=Ruby CA"
|
||||
end
|
||||
|
||||
it "should have a table with our SSL Cipher Results" do
|
||||
expect(subject.to_s).to include "Accepted * SSLv3 40 EXP-RC2-CBC-MD5"
|
||||
expect(subject.to_s).to include "Accepted SSLv3 128 AES128-SHA"
|
||||
expect(subject.to_s).to include "Accepted SSLv3 256 AES256-SHA"
|
||||
expect(subject.to_s).to include "Accepted TLSv1 256 AES256-SHA"
|
||||
end
|
||||
end
|
||||
|
||||
it "should return an appropriate message when SSL is not supported" do
|
||||
expect(subject).to receive(:supports_ssl?).and_return(false)
|
||||
expect(subject.to_s).to eq "Server does not appear to support SSL on this port!"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -1,111 +0,0 @@
|
||||
# -*- coding:binary -*-
|
||||
require 'rex/sslscan/scanner'
|
||||
require 'rex/thread_factory'
|
||||
require 'rex/text'
|
||||
require 'rex/compat'
|
||||
|
||||
RSpec.describe Rex::SSLScan::Scanner do
|
||||
|
||||
subject{Rex::SSLScan::Scanner.new("google.com", 443)}
|
||||
|
||||
it { is_expected.to respond_to :host }
|
||||
it { is_expected.to respond_to :port }
|
||||
it { is_expected.to respond_to :timeout }
|
||||
it { is_expected.to respond_to :valid? }
|
||||
|
||||
context "when validating the scanner config" do
|
||||
it "should return true when given a valid config" do
|
||||
expect(subject.valid?).to eq true
|
||||
end
|
||||
|
||||
it "should return false if given an invalid host" do
|
||||
subject.host = nil
|
||||
expect(subject.valid?).to eq false
|
||||
end
|
||||
|
||||
it "should return false if given an invalid port" do
|
||||
subject.port = nil
|
||||
expect(subject.valid?).to eq false
|
||||
end
|
||||
|
||||
it "should return false if given an invalid timeout" do
|
||||
subject.timeout = nil
|
||||
expect(subject.valid?).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context "when testing a single cipher" do
|
||||
context "an exception should be raised if" do
|
||||
it "has an invalid scanner configuration" do
|
||||
subject.host =nil
|
||||
expect{ subject.test_cipher(:SSLv2, "AES128-SHA")}.to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "is given an invalid SSL version" do
|
||||
expect{ subject.test_cipher(:SSLv5, "AES128-SHA")}.to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "is given an invalid cipher" do
|
||||
expect{ subject.test_cipher(:SSLv2, "FOO128-SHA")}.to raise_error(StandardError)
|
||||
end
|
||||
|
||||
it "is given an invalid cipher for the SSL Version" do
|
||||
expect{ subject.test_cipher(:SSLv3, 'DES-CBC3-MD5')}.to raise_error(StandardError)
|
||||
end
|
||||
end
|
||||
|
||||
context ":rejected should be returned if" do
|
||||
it "scans a server that doesn't support the supplied SSL version" do
|
||||
expect(subject.test_cipher(:SSLv3, "DES-CBC-SHA")).to eq :rejected
|
||||
end
|
||||
|
||||
it "scans a server that doesn't support the cipher" do
|
||||
expect(subject.test_cipher(:SSLv3, "DHE-DSS-AES256-SHA")).to eq :rejected
|
||||
end
|
||||
end
|
||||
|
||||
# XXX Reenable this spec when a way to use older versions of OpenSSL
|
||||
# directly is available.
|
||||
# context ":accepted should be returned if" do
|
||||
# it "scans a server that accepts the given cipher" do
|
||||
# expect(subject.test_cipher(:SSLv3, "AES256-SHA")).to eq :accepted
|
||||
# end
|
||||
# end
|
||||
end
|
||||
|
||||
context "when retrieving the cert" do
|
||||
it "should return nil if it can't connect" do
|
||||
expect(subject.get_cert(:SSLv3, "DES-CBC-SHA")).to eq nil
|
||||
end
|
||||
|
||||
# XXX Reenable this spec when a way to use older versions of OpenSSL
|
||||
# directly is available.
|
||||
# it "should return an X509 cert if it can connect" do
|
||||
# expect(subject.get_cert(:SSLv3, "AES256-SHA")).to be_a OpenSSL::X509::Certificate
|
||||
# end
|
||||
end
|
||||
|
||||
context "when scanning https://google.com" do
|
||||
it "should return a Result object" do
|
||||
result = subject.scan
|
||||
expect(result).to be_a Rex::SSLScan::Result
|
||||
end
|
||||
|
||||
context "if SSLv2 is not available locally" do
|
||||
before(:example) do
|
||||
expect(subject).to receive(:check_opensslv2).and_return(false)
|
||||
subject.send(:initialize, 'google.com', 443)
|
||||
end
|
||||
it "should mark SSLv2 as unsupported" do
|
||||
expect(subject.supported_versions).not_to include :SSLv2
|
||||
expect(subject.sslv2).to eq false
|
||||
end
|
||||
|
||||
it "should not test any SSLv2 ciphers" do
|
||||
res = subject.scan
|
||||
expect(res.sslv2).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue
Block a user