yawast 0.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/README.md +454 -0
- data/Rakefile +9 -0
- data/bin/yawast +69 -0
- data/lib/commands/cms.rb +10 -0
- data/lib/commands/head.rb +12 -0
- data/lib/commands/scan.rb +11 -0
- data/lib/commands/ssl.rb +11 -0
- data/lib/commands/utils.rb +36 -0
- data/lib/resources/common.txt +1960 -0
- data/lib/scanner/apache.rb +72 -0
- data/lib/scanner/cms.rb +14 -0
- data/lib/scanner/core.rb +95 -0
- data/lib/scanner/generic.rb +323 -0
- data/lib/scanner/iis.rb +63 -0
- data/lib/scanner/nginx.rb +13 -0
- data/lib/scanner/obj_presence.rb +63 -0
- data/lib/scanner/php.rb +19 -0
- data/lib/scanner/ssl.rb +237 -0
- data/lib/scanner/ssl_labs.rb +491 -0
- data/lib/shared/http.rb +67 -0
- data/lib/string_ext.rb +16 -0
- data/lib/uri_ext.rb +5 -0
- data/lib/util.rb +25 -0
- data/lib/yawast.rb +57 -0
- data/test/base.rb +43 -0
- data/test/data/apache_server_info.txt +486 -0
- data/test/data/apache_server_status.txt +184 -0
- data/test/data/cms_none_body.txt +242 -0
- data/test/data/cms_wordpress_body.txt +467 -0
- data/test/data/iis_server_header.txt +13 -0
- data/test/data/tomcat_release_notes.txt +172 -0
- data/test/data/wordpress_readme_html.txt +86 -0
- data/test/test_cmd_util.rb +35 -0
- data/test/test_helper.rb +5 -0
- data/test/test_object_presence.rb +36 -0
- data/test/test_scan_apache_banner.rb +58 -0
- data/test/test_scan_apache_server_info.rb +22 -0
- data/test/test_scan_apache_server_status.rb +22 -0
- data/test/test_scan_cms.rb +27 -0
- data/test/test_scan_iis_headers.rb +40 -0
- data/test/test_scan_nginx_banner.rb +18 -0
- data/test/test_shared_http.rb +40 -0
- data/test/test_shared_util.rb +44 -0
- data/test/test_string_ext.rb +15 -0
- data/test/test_yawast.rb +17 -0
- data/yawast.gemspec +35 -0
- metadata +283 -0
data/lib/scanner/ssl.rb
ADDED
@@ -0,0 +1,237 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'openssl-extensions/all'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
module Yawast
|
6
|
+
module Scanner
|
7
|
+
class Ssl
|
8
|
+
def self.info(uri, check_ciphers, sslsessioncount)
|
9
|
+
begin
|
10
|
+
socket = TCPSocket.new(uri.host, uri.port)
|
11
|
+
|
12
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
13
|
+
ctx.ciphers = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers]
|
14
|
+
|
15
|
+
ssl = OpenSSL::SSL::SSLSocket.new(socket, ctx)
|
16
|
+
ssl.hostname = uri.host
|
17
|
+
ssl.connect
|
18
|
+
|
19
|
+
cert = ssl.peer_cert
|
20
|
+
|
21
|
+
unless cert.nil?
|
22
|
+
Yawast::Utilities.puts_info 'Found X509 Certificate:'
|
23
|
+
Yawast::Utilities.puts_info "\t\tIssued To: #{cert.subject.common_name} / #{cert.subject.organization}"
|
24
|
+
Yawast::Utilities.puts_info "\t\tIssuer: #{cert.issuer.common_name} / #{cert.issuer.organization}"
|
25
|
+
Yawast::Utilities.puts_info "\t\tVersion: #{cert.version}"
|
26
|
+
Yawast::Utilities.puts_info "\t\tSerial: #{cert.serial}"
|
27
|
+
Yawast::Utilities.puts_info "\t\tSubject: #{cert.subject}"
|
28
|
+
|
29
|
+
#check to see if cert is expired
|
30
|
+
if cert.not_after > Time.now
|
31
|
+
Yawast::Utilities.puts_info "\t\tExpires: #{cert.not_after}"
|
32
|
+
else
|
33
|
+
Yawast::Utilities.puts_vuln "\t\tExpires: #{cert.not_after} (Expired)"
|
34
|
+
end
|
35
|
+
|
36
|
+
#check for SHA1 & MD5 certs
|
37
|
+
if cert.signature_algorithm.include?('md5') || cert.signature_algorithm.include?('sha1')
|
38
|
+
Yawast::Utilities.puts_vuln "\t\tSignature Algorithm: #{cert.signature_algorithm}"
|
39
|
+
else
|
40
|
+
Yawast::Utilities.puts_info "\t\tSignature Algorithm: #{cert.signature_algorithm}"
|
41
|
+
end
|
42
|
+
|
43
|
+
Yawast::Utilities.puts_info "\t\tKey: #{cert.public_key.class.to_s.gsub('OpenSSL::PKey::', '')}-#{get_x509_pub_key_strength(cert)}"
|
44
|
+
Yawast::Utilities.puts_info "\t\t\tKey Hash: #{Digest::SHA1.hexdigest(cert.public_key.to_s)}"
|
45
|
+
Yawast::Utilities.puts_info "\t\tExtensions:"
|
46
|
+
cert.extensions.each { |ext| Yawast::Utilities.puts_info "\t\t\t#{ext}" unless ext.oid == 'subjectAltName' }
|
47
|
+
|
48
|
+
#alt names
|
49
|
+
alt_names = cert.extensions.find {|e| e.oid == 'subjectAltName'}
|
50
|
+
unless alt_names.nil?
|
51
|
+
Yawast::Utilities.puts_info "\t\tAlternate Names:"
|
52
|
+
alt_names.value.split(',').each { |name| Yawast::Utilities.puts_info "\t\t\t#{name.strip.delete('DNS:')}" }
|
53
|
+
end
|
54
|
+
|
55
|
+
hash = Digest::SHA1.hexdigest(cert.to_der)
|
56
|
+
Yawast::Utilities.puts_info "\t\tHash: #{hash}"
|
57
|
+
puts "\t\t\thttps://censys.io/certificates?q=#{hash}"
|
58
|
+
puts "\t\t\thttps://crt.sh/?q=#{hash}"
|
59
|
+
puts ''
|
60
|
+
end
|
61
|
+
|
62
|
+
cert_chain = ssl.peer_cert_chain
|
63
|
+
|
64
|
+
if cert_chain.count == 1
|
65
|
+
#HACK: This is an ugly way to guess if it's a missing intermediate, or self-signed
|
66
|
+
#tIt looks like a change to Ruby's OpenSSL wrapper is needed to actually fix this right.
|
67
|
+
|
68
|
+
if cert.issuer == cert.subject
|
69
|
+
Yawast::Utilities.puts_vuln "\t\tCertificate Is Self-Singed"
|
70
|
+
else
|
71
|
+
Yawast::Utilities.puts_warn "\t\tCertificate Chain Is Incomplete"
|
72
|
+
end
|
73
|
+
|
74
|
+
puts ''
|
75
|
+
end
|
76
|
+
|
77
|
+
unless cert_chain.nil?
|
78
|
+
Yawast::Utilities.puts_info 'Certificate: Chain'
|
79
|
+
cert_chain.each do |c|
|
80
|
+
Yawast::Utilities.puts_info "\t\tIssued To: #{c.subject.common_name} / #{c.subject.organization}"
|
81
|
+
Yawast::Utilities.puts_info "\t\t\tIssuer: #{c.issuer.common_name} / #{c.issuer.organization}"
|
82
|
+
Yawast::Utilities.puts_info "\t\t\tExpires: #{c.not_after}"
|
83
|
+
Yawast::Utilities.puts_info "\t\t\tKey: #{c.public_key.class.to_s.gsub('OpenSSL::PKey::', '')}-#{get_x509_pub_key_strength(c)}"
|
84
|
+
Yawast::Utilities.puts_info "\t\t\tSignature Algorithm: #{c.signature_algorithm}"
|
85
|
+
Yawast::Utilities.puts_info "\t\t\tHash: #{Digest::SHA1.hexdigest(c.to_der)}"
|
86
|
+
puts ''
|
87
|
+
end
|
88
|
+
|
89
|
+
puts ''
|
90
|
+
end
|
91
|
+
|
92
|
+
puts "\t\tQualys SSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d=#{uri.host}&hideResults=on"
|
93
|
+
puts ''
|
94
|
+
|
95
|
+
if check_ciphers
|
96
|
+
get_ciphers(uri)
|
97
|
+
end
|
98
|
+
|
99
|
+
ssl.sysclose
|
100
|
+
|
101
|
+
get_session_msg_count(uri) if sslsessioncount
|
102
|
+
rescue => e
|
103
|
+
Yawast::Utilities.puts_error "SSL: Error Reading X509 Details: #{e.message}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.get_ciphers(uri)
|
108
|
+
puts 'Supported Ciphers (based on your OpenSSL version):'
|
109
|
+
|
110
|
+
dns = Resolv::DNS.new()
|
111
|
+
ip = dns.getaddresses(uri.host)[0]
|
112
|
+
|
113
|
+
#find all versions that don't include '_server' or '_client'
|
114
|
+
versions = OpenSSL::SSL::SSLContext::METHODS.find_all { |v| !v.to_s.include?('_client') && !v.to_s.include?('_server')}
|
115
|
+
|
116
|
+
versions.each do |version|
|
117
|
+
#ignore SSLv23, as it's an auto-negotiate, which just adds noise
|
118
|
+
if version.to_s != "SSLv23"
|
119
|
+
ciphers = OpenSSL::SSL::SSLContext.new(version).ciphers
|
120
|
+
puts "\tChecking for #{version.to_s} suites (#{ciphers.count} possible suites)"
|
121
|
+
|
122
|
+
ciphers.each do |cipher|
|
123
|
+
#try to connect and see what happens
|
124
|
+
begin
|
125
|
+
socket = TCPSocket.new(ip.to_s, uri.port)
|
126
|
+
context = OpenSSL::SSL::SSLContext.new(version)
|
127
|
+
context.ciphers = cipher[0]
|
128
|
+
ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
|
129
|
+
ssl.hostname = uri.host
|
130
|
+
|
131
|
+
ssl.connect
|
132
|
+
|
133
|
+
if cipher[2] < 112 || cipher[0].include?('RC4')
|
134
|
+
#less than 112 bits or RC4, flag as a vuln
|
135
|
+
Yawast::Utilities.puts_vuln "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
|
136
|
+
elsif cipher[2] >= 128
|
137
|
+
#secure, probably safe
|
138
|
+
Yawast::Utilities.puts_info "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
|
139
|
+
else
|
140
|
+
#weak, but not "omg!" weak.
|
141
|
+
Yawast::Utilities.puts_warn "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}"
|
142
|
+
end
|
143
|
+
|
144
|
+
ssl.sysclose
|
145
|
+
rescue OpenSSL::SSL::SSLError => e
|
146
|
+
unless e.message.include?('alert handshake failure') ||
|
147
|
+
e.message.include?('no ciphers available') ||
|
148
|
+
e.message.include?('wrong version number')
|
149
|
+
Yawast::Utilities.puts_error "\t\tVersion: #{ssl.ssl_version.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}\t(Supported But Failed)"
|
150
|
+
end
|
151
|
+
rescue => e
|
152
|
+
Yawast::Utilities.puts_error "\t\tVersion: #{''.ljust(7)}\tBits: #{cipher[2]}\tCipher: #{cipher[0]}\t(#{e.message})"
|
153
|
+
ensure
|
154
|
+
ssl.sysclose unless ssl == nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
puts ''
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.check_hsts(head)
|
164
|
+
found = ''
|
165
|
+
|
166
|
+
head.each do |k, v|
|
167
|
+
if k.downcase.include? 'strict-transport-security'
|
168
|
+
found = "#{k}: #{v}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
if found == ''
|
173
|
+
Yawast::Utilities.puts_warn 'HSTS: Not Enabled'
|
174
|
+
else
|
175
|
+
Yawast::Utilities.puts_info "HSTS: Enabled (#{found})"
|
176
|
+
end
|
177
|
+
|
178
|
+
puts ''
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.get_session_msg_count(uri)
|
182
|
+
# this method will send a number of HEAD requests to see
|
183
|
+
# if the connection is eventually killed.
|
184
|
+
puts 'TLS Session Request Limit: Checking number of requests accepted...'
|
185
|
+
|
186
|
+
count = 0
|
187
|
+
begin
|
188
|
+
req = Yawast::Shared::Http.get_http(uri)
|
189
|
+
req.use_ssl = uri.scheme == 'https'
|
190
|
+
req.keep_alive_timeout = 600
|
191
|
+
headers = Yawast::Shared::Http.get_headers
|
192
|
+
|
193
|
+
req.start do |http|
|
194
|
+
10000.times do |i|
|
195
|
+
http.head(uri.path, headers)
|
196
|
+
|
197
|
+
# hack to detect transparent disconnects
|
198
|
+
if http.instance_variable_get(:@ssl_context).session_cache_stats[:cache_hits] != 0
|
199
|
+
raise 'TLS Reconnected'
|
200
|
+
end
|
201
|
+
|
202
|
+
count += 1
|
203
|
+
|
204
|
+
if i % 20 == 0
|
205
|
+
print '.'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
rescue => e
|
210
|
+
puts
|
211
|
+
Yawast::Utilities.puts_info "TLS Session Request Limit: Connection terminated after #{count} requests (#{e.message})"
|
212
|
+
return
|
213
|
+
end
|
214
|
+
|
215
|
+
puts
|
216
|
+
Yawast::Utilities.puts_warn 'TLS Session Request Limit: Connection not terminated after 10,000 requests'
|
217
|
+
Yawast::Utilities.puts_warn 'TLS Session Request Limit: If server supports 3DES, may be affected by SWEET32'
|
218
|
+
end
|
219
|
+
|
220
|
+
#private methods
|
221
|
+
class << self
|
222
|
+
private
|
223
|
+
def get_x509_pub_key_strength(cert)
|
224
|
+
begin
|
225
|
+
if cert.public_key.class == OpenSSL::PKey::EC
|
226
|
+
cert.public_key.group.curve_name
|
227
|
+
else
|
228
|
+
cert.public_key.strength
|
229
|
+
end
|
230
|
+
rescue => e
|
231
|
+
"(Strength Unknown: #{e.message})"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|