RubySSLChecker

From Rory.wiki

Jump to: navigation, search

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
Personal tools