RubySSLChecker
From Rory.wiki
Background
I started this off after reading this post on enumerating SSL ciphers with ruby.
Essentially the script just runs through all the available SSL ciphers as provided by OpenSSL and tries to connect to the target server with each one, success means that the SSL cipher is enabled on the server, and failure with an SSL error indicates that it's not.
There were a couple of niggles getting the code working with Ruby 1.8 and 1.9 but it seems to work ok now...
The metasploit module basically replicates the same functionality as a auxiliary module.
Stand Alone Script
#!/usr/bin/env ruby # == Synopsis # This script is designed to test web servers for available ciphers and reqport back on which ones are enabled # Just a cleaned up and slightly expanded version of the cool code http://gursevkalra.blogspot.com/2009/09/ruby-and-openssl-based-ssl-cipher.html here # # # TODO: take file with different inputs, sort out an XML output for the reporting # == Author # Rory McCune (based on code by Gursev Kalra) require 'net/https' module Net class HTTP def set_context=(value) @ssl_context = OpenSSL::SSL::SSLContext.new(value) end #Not needed in later version (1.9+) as there's already accessors for that. if RUBY_VERSION =~ /^1\.8/ ssl_context_accessor :ciphers end end end class RSSLTester VERSION = '0.1' def initialize(arguments) require 'rubygems' require 'logger' require 'optparse' require 'ostruct' @log = Logger.new('SSLTester-log') @log.level = Logger::DEBUG @log.debug("Log created at " + Time.now.to_s) @options = OpenStruct.new @options.report_file = nil @options.servers = [] @options.xml = nil @options.only_show_weak = false @options.dont_show_rejected = false opts = OptionParser.new do |opts| opts.banner = 'Ruby SSL Tester' opts.on("-sTARGET","--server TARGET","Server:port to run against (port is 443 unless specified)") do |server| @options.servers << server end opts.on("-fFile","--file FILE","File containing servers to run analyse") do |file| @log.debug("trying to open a file of #{file}") begin input = File.open(file).readlines rescue Errno::ENOENT @log.fatal("Input file #{file} could not be opened") puts "Couldn't open the input file, sure it's there?" exit end @log.debug("File had #{input.length.to_s} lines") input.each {|line| line.chomp!} @options.servers = @options.servers + input end opts.on("-rREPORT","--report REPORT","Report file name (defaults to ssl-report)") do |report| @options.report_file = report end opts.on("-xXML","--xml XML","create xml report with filename as supplier") do |xml| @options.xml = xml end opts.on("-w","--only_show_weak","Show only weak (<128 bit Ciphers)") do @options.only_show_weak = true end opts.on("--dont_show_rejected","Don't show rejected ciphers") do @options.dont_show_rejected = true end opts.on("-h", "--help", "-?","--?", "Get Help") do |help| puts opts exit end opts.on("-v","--version", "Get version")do |ver| puts "Ruby SSL Tester Version #{VERSION}" exit end end if arguments.length == 0 puts opts exit end opts.parse!(arguments) if @options.report_file begin @report_file = File.new(@options.report_file,'a+') rescue => e @log.fatal("had a problem creating the report file #{@options.report_file}") @log.fatal("Error was #{e.to_s}, type was #{e.class}") puts "Had a problem creating the report file, sure your rights are ok?" exit end @log.info("created report file of " + @options.report_file) @standard_report_required = true end if @options.xml begin @xml_report_file = File.new(@options.xml, 'a+') rescue => e @log.fatal("had a problem creating the XML report file #{@options.xml}") @log.fatal("Error was #{e.to_s}, type was #{e.class}") puts "Had a problem creating the XML report file, sure your rights are ok?" exit end @log.info("create xml report file of " + @options.xml) @xml_report_required = true end end def do_analysis @options.servers.each do |server| next unless server.length > 4 ip, port = server.split(':') port = 443 unless port @log.debug("About to analyze " + ip) analyse_server(ip,port) end end def analyse_server(server,port) @log.debug("started Analysing #{server} on port #{port}") protocol_versions = [:SSLv2, :SSLv3, :TLSv1] # Protocol versions support #protocol_versions = OpenSSL::SSL::SSLContext::METHODS results = Hash.new protocol_versions.each do |version| @log.debug("started testing cipbers in #{version.to_s}") cipher_set = OpenSSL::SSL::SSLContext.new(version).ciphers results[version] = Array.new cipher_set.each do |cipher_name, ignore_me_cipher_version, bits, ignore_me_algorithm_bits| next if @options.only_show_weak && bits.to_i > 127 begin request = Net::HTTP.new(server, port) @log.debug("making request to #{server} on #{port}") request.use_ssl = true request.set_context = version request.verify_mode = OpenSSL::SSL::VERIFY_NONE request.ciphers = cipher_name if RUBY_VERSION =~ /^1\.9/ request.ssl_version = version end beginresponse = request.get("/") results[version] << ['Accepted',bits,cipher_name] rescue OpenSSL::SSL::SSLError => e unless @options.dont_show_rejected results[version] << ['Rejected',bits,cipher_name] end rescue => e puts "Something went a bit wrong there " + e @log.warn("Error on #{version}, #{cipher_name} - #{e}") end end end if @standard_report_required standard_report(server,results) end if @xml_report_required xml_report(server,results) end end def standard_report(server,results) @report_file.puts "\n\n=======================" @report_file.puts "SSL Test for #{server}" @report_file.puts "=======================" results.each do |version,cipher_results| @report_file.puts "=======================" @report_file.puts version @report_file.puts "=======================" cipher_results.each do |res| @report_file.puts "#{res[0]}\t#{res[1]} bits\t#{res[2]}" end end end def xml_report(server,results) begin require 'builder' rescue LoadError @log.warn("XML Builder gem not available can't do XML reports") puts "XML builder gem not available (try gem install builder) can't do XML reports" return end xml = Builder::XmlMarkup.new(:target => @xml_report_file, :indent => 2) xml.instruct! xml.server(:server => server) do results.each do |version,cipher_results| xml.cipher(:version => version) do cipher_results.each do |res| xml.suiteResult("#{res[2]}, #{res[1]}", :suite => res[0]) end end end end end end if __FILE__ == $0 analysis = RSSLTester.new(ARGV) analysis.do_analysis end
Metasploit Auxiliary Module
require 'msf/core' require 'net/https' class Metasploit3 < Msf::Auxiliary def initialize super( 'Name' => 'SSL Ciphers check', 'Version' => '', 'Description' => 'Display the enabled SSL ciphers on the Remote host based on code by Gursev Kalra', 'Author' => ['Rory McCune'], 'License' => MSF_LICENSE ) register_options( [ OptString.new('RHOST', [ true, "Host to Test",'']), OptInt.new('PORT', [ true, "Port to run against", 443]), OptBool.new('SHOWREJECTED',[ true, "Show results of all checks including rejected?", true]), OptBool.new('ONLYWEAKCIPHERS',[ true, "Only show result for ciphers less than 128 bits?", false]) ], self.class) end def run target_host = datastore['RHOST'] port = datastore['PORT'] protocol_versions = [:SSLv2, :SSLv3, :TLSv1] results = Hash.new protocol_versions.each do |version| cipher_set = OpenSSL::SSL::SSLContext.new(version).ciphers print_status("\n\n#{version}\n---------") cipher_set.each do |cipher_name, ignore_me_cipher_version, bits, ignore_me_algorithm_bits| next if datastore['ONLYWEAKCIPHERS'] && bits.to_i > 127 begin request = Net::HTTP.new(target_host, port) def request.set_context=(value) @ssl_context = OpenSSL::SSL::SSLContext.new(value) end #These two only needed for Ruby 1.8.x if RUBY_VERSION =~ /^1\.8/ def request.ciphers return nil unless self.ssl_context @ssl_context.ciphers end def request.ciphers=(val) @ssl_context ||= OpenSSL::SSL::SSLContext.new @ssl_context.ciphers = val end end #Need to add an additional parameter for Ruby 1.9.x if RUBY_VERSION =~ /^1\.9/ request.ssl_version = version end request.use_ssl = true request.set_context = version request.verify_mode = OpenSSL::SSL::VERIFY_NONE request.ciphers = cipher_name beginresponse = request.get("/") print_status("Accepted\t #{bits} bits\t#{cipher_name}") rescue OpenSSL::SSL::SSLError => e if datastore['SHOWREJECTED'] print_error("Rejected\t #{bits} bits\t#{cipher_name}") end rescue => e print_error("Something went a bit wrong there " + e) end end end end end
