yawast 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +7 -0
  5. data/README.md +454 -0
  6. data/Rakefile +9 -0
  7. data/bin/yawast +69 -0
  8. data/lib/commands/cms.rb +10 -0
  9. data/lib/commands/head.rb +12 -0
  10. data/lib/commands/scan.rb +11 -0
  11. data/lib/commands/ssl.rb +11 -0
  12. data/lib/commands/utils.rb +36 -0
  13. data/lib/resources/common.txt +1960 -0
  14. data/lib/scanner/apache.rb +72 -0
  15. data/lib/scanner/cms.rb +14 -0
  16. data/lib/scanner/core.rb +95 -0
  17. data/lib/scanner/generic.rb +323 -0
  18. data/lib/scanner/iis.rb +63 -0
  19. data/lib/scanner/nginx.rb +13 -0
  20. data/lib/scanner/obj_presence.rb +63 -0
  21. data/lib/scanner/php.rb +19 -0
  22. data/lib/scanner/ssl.rb +237 -0
  23. data/lib/scanner/ssl_labs.rb +491 -0
  24. data/lib/shared/http.rb +67 -0
  25. data/lib/string_ext.rb +16 -0
  26. data/lib/uri_ext.rb +5 -0
  27. data/lib/util.rb +25 -0
  28. data/lib/yawast.rb +57 -0
  29. data/test/base.rb +43 -0
  30. data/test/data/apache_server_info.txt +486 -0
  31. data/test/data/apache_server_status.txt +184 -0
  32. data/test/data/cms_none_body.txt +242 -0
  33. data/test/data/cms_wordpress_body.txt +467 -0
  34. data/test/data/iis_server_header.txt +13 -0
  35. data/test/data/tomcat_release_notes.txt +172 -0
  36. data/test/data/wordpress_readme_html.txt +86 -0
  37. data/test/test_cmd_util.rb +35 -0
  38. data/test/test_helper.rb +5 -0
  39. data/test/test_object_presence.rb +36 -0
  40. data/test/test_scan_apache_banner.rb +58 -0
  41. data/test/test_scan_apache_server_info.rb +22 -0
  42. data/test/test_scan_apache_server_status.rb +22 -0
  43. data/test/test_scan_cms.rb +27 -0
  44. data/test/test_scan_iis_headers.rb +40 -0
  45. data/test/test_scan_nginx_banner.rb +18 -0
  46. data/test/test_shared_http.rb +40 -0
  47. data/test/test_shared_util.rb +44 -0
  48. data/test/test_string_ext.rb +15 -0
  49. data/test/test_yawast.rb +17 -0
  50. data/yawast.gemspec +35 -0
  51. metadata +283 -0
@@ -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