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.
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