yawast 0.2.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|