Skip to content

Commit f054317

Browse files
committed
Merge branch 'dmaloney-r7-feature/ssl/add_cipher_support' into rapid7
2 parents b9de915 + 27f43d3 commit f054317

File tree

7 files changed

+1058
-5
lines changed

7 files changed

+1058
-5
lines changed

lib/rex.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ module Rex
8888
# Platforms
8989
require 'rex/platforms'
9090

91+
# SSLScan
92+
require 'rex/sslscan/scanner'
93+
require 'rex/sslscan/result'
94+
9195

9296
# Overload the Kernel.sleep() function to be thread-safe
9397
Kernel.class_eval("

lib/rex/socket/parameters.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,10 +140,15 @@ def initialize(hash)
140140
self.ssl = false
141141
end
142142

143-
if (hash['SSLVersion'] and hash['SSLVersion'].to_s =~ /^(SSL2|SSL3|TLS1)$/i)
143+
supported_ssl_versions = ['SSL2', 'SSL23', 'TLS1', 'SSL3', :SSLv2, :SSLv3, :SSLv23, :TLSv1]
144+
if (hash['SSLVersion'] and supported_ssl_versions.include? hash['SSLVersion'])
144145
self.ssl_version = hash['SSLVersion']
145146
end
146147

148+
if (hash['SSLCipher'])
149+
self.ssl_cipher = hash['SSLCipher']
150+
end
151+
147152
if (hash['SSLCert'] and ::File.file?(hash['SSLCert']))
148153
begin
149154
self.ssl_cert = ::File.read(hash['SSLCert'])
@@ -338,6 +343,11 @@ def v6?
338343
#
339344
attr_accessor :ssl_version
340345
#
346+
# What specific SSL Cipher(s) to use, may be a string containing the cipher name
347+
# or an array of strings containing cipher names e.g. ["DHE-RSA-AES256-SHA", "DHE-DSS-AES256-SHA"]
348+
#
349+
attr_accessor :ssl_cipher
350+
#
341351
# The SSL certificate, in pem format, stored as a string. See +SslTcpServer#make_ssl+
342352
#
343353
attr_accessor :ssl_cert

lib/rex/socket/ssl_tcp.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# -*- coding: binary -*-
22
require 'rex/socket'
3-
43
###
54
#
65
# This class provides methods for interacting with an SSL TCP client
@@ -60,11 +59,11 @@ def initsock(params = nil)
6059
version = :SSLv3
6160
if(params)
6261
case params.ssl_version
63-
when 'SSL2'
62+
when 'SSL2', :SSLv2
6463
version = :SSLv2
65-
when 'SSL23'
64+
when 'SSL23', :SSLv23
6665
version = :SSLv23
67-
when 'TLS1'
66+
when 'TLS1', :TLSv1
6867
version = :TLSv1
6968
end
7069
end
@@ -81,6 +80,9 @@ def initsock(params = nil)
8180
# VERIFY_PEER
8281
self.sslctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
8382
self.sslctx.options = OpenSSL::SSL::OP_ALL
83+
if params.ssl_cipher
84+
self.sslctx.ciphers = params.ssl_cipher
85+
end
8486

8587
# Set the verification callback
8688
self.sslctx.verify_callback = Proc.new do |valid, store|

lib/rex/sslscan/result.rb

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
2+
require 'rex/socket'
3+
require 'rex/ui/text/table'
4+
5+
module Rex::SSLScan
6+
class Result
7+
8+
attr_accessor :openssl_sslv2
9+
10+
attr_reader :ciphers
11+
attr_reader :supported_versions
12+
13+
def initialize()
14+
@cert = nil
15+
@ciphers = Set.new
16+
@supported_versions = [:SSLv2, :SSLv3, :TLSv1]
17+
end
18+
19+
def cert
20+
@cert
21+
end
22+
23+
def cert=(input)
24+
unless input.kind_of? OpenSSL::X509::Certificate or input.nil?
25+
raise ArgumentError, "Must be an X509 Cert!"
26+
end
27+
@cert = input
28+
end
29+
30+
def sslv2
31+
@ciphers.reject{|cipher| cipher[:version] != :SSLv2 }
32+
end
33+
34+
def sslv3
35+
@ciphers.reject{|cipher| cipher[:version] != :SSLv3 }
36+
end
37+
38+
def tlsv1
39+
@ciphers.reject{|cipher| cipher[:version] != :TLSv1 }
40+
end
41+
42+
def weak_ciphers
43+
accepted.reject{|cipher| cipher[:weak] == false }
44+
end
45+
46+
def strong_ciphers
47+
accepted.reject{|cipher| cipher[:weak] }
48+
end
49+
50+
# Returns all accepted ciphers matching the supplied version
51+
# @param version [Symbol, Array] The SSL Version to filter on
52+
# @raise [ArgumentError] if the version supplied is invalid
53+
# @return [Array] An array of accepted cipher details matching the supplied versions
54+
def accepted(version = :all)
55+
enum_ciphers(:accepted, version)
56+
end
57+
58+
# Returns all rejected ciphers matching the supplied version
59+
# @param version [Symbol, Array] The SSL Version to filter on
60+
# @raise [ArgumentError] if the version supplied is invalid
61+
# @return [Array] An array of rejected cipher details matching the supplied versions
62+
def rejected(version = :all)
63+
enum_ciphers(:rejected, version)
64+
end
65+
66+
def each_accepted(version = :all)
67+
accepted(version).each do |cipher_result|
68+
yield cipher_result
69+
end
70+
end
71+
72+
def each_rejected(version = :all)
73+
rejected(version).each do |cipher_result|
74+
yield cipher_result
75+
end
76+
end
77+
78+
def supports_sslv2?
79+
!(accepted(:SSLv2).empty?)
80+
end
81+
82+
def supports_sslv3?
83+
!(accepted(:SSLv3).empty?)
84+
end
85+
86+
def supports_tlsv1?
87+
!(accepted(:TLSv1).empty?)
88+
end
89+
90+
def supports_ssl?
91+
supports_sslv2? or supports_sslv3? or supports_tlsv1?
92+
end
93+
94+
def supports_weak_ciphers?
95+
!(weak_ciphers.empty?)
96+
end
97+
98+
def standards_compliant?
99+
if supports_ssl?
100+
return false if supports_sslv2?
101+
return false if supports_weak_ciphers?
102+
end
103+
true
104+
end
105+
106+
# Adds the details of a cipher test to the Result object.
107+
# @param version [Symbol] the SSL Version
108+
# @param cipher [String] the SSL cipher
109+
# @param key_length [Fixnum] the length of encryption key
110+
# @param status [Symbol] :accepted or :rejected
111+
def add_cipher(version, cipher, key_length, status)
112+
unless @supported_versions.include? version
113+
raise ArgumentError, "Must be a supported SSL Version"
114+
end
115+
unless OpenSSL::SSL::SSLContext.new(version).ciphers.flatten.include? cipher
116+
raise ArgumentError, "Must be a valid SSL Cipher for #{version}!"
117+
end
118+
unless key_length.kind_of? Fixnum
119+
raise ArgumentError, "Must supply a valid key length"
120+
end
121+
unless [:accepted, :rejected].include? status
122+
raise ArgumentError, "Status must be either :accepted or :rejected"
123+
end
124+
125+
strong_cipher_ctx = OpenSSL::SSL::SSLContext.new(version)
126+
# OpenSSL Directive For Strong Ciphers
127+
# See: http://www.rapid7.com/vulndb/lookup/ssl-weak-ciphers
128+
strong_cipher_ctx.ciphers = "ALL:!aNULL:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM"
129+
130+
if strong_cipher_ctx.ciphers.flatten.include? cipher
131+
weak = false
132+
else
133+
weak = true
134+
end
135+
136+
cipher_details = {:version => version, :cipher => cipher, :key_length => key_length, :weak => weak, :status => status}
137+
@ciphers << cipher_details
138+
end
139+
140+
def to_s
141+
unless supports_ssl?
142+
return "Server does not appear to support SSL on this port!"
143+
end
144+
table = Rex::Ui::Text::Table.new(
145+
'Header' => 'SSL Ciphers',
146+
'Indent' => 1,
147+
'Columns' => ['Status', 'Weak', 'SSL Version', 'Key Length', 'Cipher'],
148+
'SortIndex' => -1
149+
)
150+
ciphers.each do |cipher|
151+
if cipher[:weak]
152+
weak = '*'
153+
else
154+
weak = ' '
155+
end
156+
table << [cipher[:status].to_s.capitalize, weak , cipher[:version], cipher[:key_length], cipher[:cipher]]
157+
end
158+
159+
# Sort by SSL Version, then Key Length, and then Status
160+
table.rows.sort_by!{|row| [row[0],row[2],row[3]]}
161+
text = "#{table.to_s}"
162+
if @cert
163+
text << " \n\n #{@cert.to_text}"
164+
end
165+
if openssl_sslv2 == false
166+
text << "\n\n *** WARNING: Your OS hates freedom! Your OpenSSL libs are compiled without SSLv2 support!"
167+
end
168+
text
169+
end
170+
171+
protected
172+
173+
# @param state [Symbol] Either :accepted or :rejected
174+
# @param version [Symbol, Array] The SSL Version to filter on (:SSLv2, :SSLv3, :TLSv1, :all)
175+
# @return [Set] The Set of cipher results matching the filter criteria
176+
def enum_ciphers(state, version = :all)
177+
case version
178+
when Symbol
179+
case version
180+
when :all
181+
return @ciphers.select{|cipher| cipher[:status] == state}
182+
when :SSLv2, :SSLv3, :TLSv1
183+
return @ciphers.select{|cipher| cipher[:status] == state and cipher[:version] == version}
184+
else
185+
raise ArgumentError, "Invalid SSL Version Supplied: #{version}"
186+
end
187+
when Array
188+
version = version.reject{|v| !(@supported_versions.include? v)}
189+
if version.empty?
190+
return @ciphers.select{|cipher| cipher[:status] == state}
191+
else
192+
return @ciphers.select{|cipher| cipher[:status] == state and version.include? cipher[:version]}
193+
end
194+
else
195+
raise ArgumentError, "Was expecting Symbol or Array and got #{version.class}"
196+
end
197+
end
198+
199+
end
200+
end

0 commit comments

Comments
 (0)