yawast 0.6.0 → 0.7.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -1
- data/CHANGELOG.md +16 -1
- data/LICENSE +1 -1
- data/bin/yawast +8 -0
- data/lib/commands/dns.rb +5 -0
- data/lib/scanner/core.rb +11 -0
- data/lib/scanner/generic.rb +16 -1
- data/lib/scanner/plugins/dns/caa.rb +6 -0
- data/lib/scanner/plugins/dns/generic.rb +30 -4
- data/lib/scanner/plugins/http/directory_search.rb +2 -0
- data/lib/scanner/plugins/http/file_presence.rb +1 -0
- data/lib/scanner/plugins/servers/apache.rb +23 -2
- data/lib/scanner/plugins/servers/iis.rb +3 -0
- data/lib/scanner/plugins/spider/spider.rb +65 -0
- data/lib/scanner/plugins/ssl/ssl.rb +78 -8
- data/lib/scanner/plugins/ssl/ssl_labs/analyze.rb +12 -2
- data/lib/scanner/plugins/ssl/ssl_labs/info.rb +10 -3
- data/lib/scanner/plugins/ssl/sweet32.rb +38 -16
- data/lib/scanner/ssl.rb +6 -1
- data/lib/scanner/ssl_labs.rb +63 -16
- data/lib/shared/http.rb +13 -1
- data/lib/shared/output.rb +151 -0
- data/lib/util.rb +4 -0
- data/lib/version.rb +1 -1
- data/lib/yawast.rb +6 -1
- data/test/test_internalssl.rb +1 -1
- data/yawast.gemspec +2 -0
- metadata +34 -4
@@ -30,11 +30,17 @@ module Yawast
|
|
30
30
|
code = res.code.to_i
|
31
31
|
|
32
32
|
# check for error in the response - if we don't, we'll wait forever for nothing
|
33
|
-
|
33
|
+
begin
|
34
|
+
json = JSON.parse body
|
35
|
+
rescue => e
|
36
|
+
raise StandardError, "Invalid response from SSL Labs: '#{e.message}'"
|
37
|
+
end
|
34
38
|
if json.key?('errors')
|
35
39
|
raise InvocationError, "API returned: #{json['errors']}"
|
36
40
|
end
|
37
41
|
|
42
|
+
Yawast::Shared::Output.log_json 'ssl', 'ssl_labs', body
|
43
|
+
|
38
44
|
# check the response code, make sure it's 200 - otherwise, we should stop now
|
39
45
|
if code != 200
|
40
46
|
case code
|
@@ -57,7 +63,11 @@ module Yawast
|
|
57
63
|
end
|
58
64
|
|
59
65
|
def self.extract_status(body)
|
60
|
-
|
66
|
+
begin
|
67
|
+
json = JSON.parse body
|
68
|
+
rescue => e
|
69
|
+
raise StandardError, "Invalid response from SSL Labs: '#{e.message}'"
|
70
|
+
end
|
61
71
|
|
62
72
|
return json['status']
|
63
73
|
end
|
@@ -17,10 +17,17 @@ module Yawast
|
|
17
17
|
|
18
18
|
def self.extract_msg(body)
|
19
19
|
ret = Array.new
|
20
|
-
json = JSON.parse body
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
begin
|
22
|
+
json = JSON.parse body
|
23
|
+
rescue => e
|
24
|
+
raise Exception, "Invalid response from SSL Labs: '#{e.message}'"
|
25
|
+
end
|
26
|
+
|
27
|
+
unless json['messages'].nil?
|
28
|
+
json['messages'].each do |msg|
|
29
|
+
ret.push msg
|
30
|
+
end
|
24
31
|
end
|
25
32
|
|
26
33
|
return ret
|
@@ -4,15 +4,21 @@ module Yawast
|
|
4
4
|
module SSL
|
5
5
|
class Sweet32
|
6
6
|
def self.get_tdes_session_msg_count(uri, limit = 10000)
|
7
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'limit', limit
|
8
|
+
|
7
9
|
# this method will send a number of HEAD requests to see
|
8
10
|
# if the connection is eventually killed.
|
9
11
|
unless check_tdes
|
10
|
-
#if the OpenSSL install doesn't support 3DES, bailout
|
12
|
+
# if the OpenSSL install doesn't support 3DES, bailout
|
11
13
|
Yawast::Utilities.puts_error "Your copy of OpenSSL doesn't support 3DES cipher suites - SWEET32 test aborted."
|
12
14
|
puts ' See here for more information: https://github.com/adamcaudill/yawast/wiki/OpenSSL-&-3DES-Compatibility'
|
15
|
+
|
16
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', '3des_supported', false
|
13
17
|
return
|
14
18
|
end
|
15
19
|
|
20
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', '3des_supported', true
|
21
|
+
|
16
22
|
puts 'TLS Session Request Limit: Checking number of requests accepted using 3DES suites...'
|
17
23
|
|
18
24
|
count = 0
|
@@ -22,14 +28,14 @@ module Yawast
|
|
22
28
|
req.keep_alive_timeout = 600
|
23
29
|
headers = Yawast::Shared::Http.get_headers
|
24
30
|
|
25
|
-
#we will use HEAD by default, but allow GET if we have issues with HEAD
|
31
|
+
# we will use HEAD by default, but allow GET if we have issues with HEAD
|
26
32
|
use_head = true
|
27
33
|
|
28
|
-
#force 3DES - this is to ensure that 3DES specific limits are caught
|
34
|
+
# force 3DES - this is to ensure that 3DES specific limits are caught
|
29
35
|
req.ciphers = ['3DES']
|
30
36
|
cipher = nil
|
31
37
|
|
32
|
-
#attempt to find a version that supports 3DES
|
38
|
+
# attempt to find a version that supports 3DES
|
33
39
|
versions = OpenSSL::SSL::SSLContext::METHODS.find_all { |v| !v.to_s.include?('_client') && !v.to_s.include?('_server')}
|
34
40
|
versions.each do |version|
|
35
41
|
if version.to_s != 'SSLv23'
|
@@ -48,7 +54,7 @@ module Yawast
|
|
48
54
|
|
49
55
|
cipher = http.instance_variable_get(:@socket).io.cipher[0]
|
50
56
|
rescue
|
51
|
-
#check if we are using HEAD or GET. If we've already switched to GET, no need to do this again.
|
57
|
+
# check if we are using HEAD or GET. If we've already switched to GET, no need to do this again.
|
52
58
|
if use_head
|
53
59
|
head = http.request_get(uri.path, headers)
|
54
60
|
|
@@ -58,10 +64,10 @@ module Yawast
|
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
61
|
-
#check to see if this is on Cloudflare - they break Keep-Alive limits, creating a false positive
|
67
|
+
# check to see if this is on Cloudflare - they break Keep-Alive limits, creating a false positive
|
62
68
|
head.each do |k, v|
|
63
69
|
if k.downcase == 'server'
|
64
|
-
if v == 'cloudflare
|
70
|
+
if v == 'cloudflare'
|
65
71
|
puts 'Cloudflare server found: SWEET32 mitigated: https://support.cloudflare.com/hc/en-us/articles/231510928'
|
66
72
|
end
|
67
73
|
end
|
@@ -69,14 +75,19 @@ module Yawast
|
|
69
75
|
end
|
70
76
|
|
71
77
|
print "Using #{version} (#{cipher})"
|
78
|
+
|
79
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'tls_version', version
|
80
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'tls_cipher', cipher
|
81
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'use_head_req', use_head
|
82
|
+
|
72
83
|
break
|
73
84
|
rescue
|
74
|
-
#we don't care
|
85
|
+
# we don't care
|
75
86
|
end
|
76
87
|
end
|
77
88
|
end
|
78
89
|
|
79
|
-
#reset the req object
|
90
|
+
# reset the req object
|
80
91
|
req = Yawast::Shared::Http.get_http(uri)
|
81
92
|
req.use_ssl = uri.scheme == 'https'
|
82
93
|
req.keep_alive_timeout = 600
|
@@ -84,7 +95,6 @@ module Yawast
|
|
84
95
|
req.ciphers = [*cipher]
|
85
96
|
|
86
97
|
req.start do |http|
|
87
|
-
#cache the number of hits
|
88
98
|
limit.times do |i|
|
89
99
|
if use_head
|
90
100
|
http.head(uri.path, headers)
|
@@ -92,7 +102,7 @@ module Yawast
|
|
92
102
|
http.request_get(uri.path, headers)
|
93
103
|
end
|
94
104
|
|
95
|
-
#
|
105
|
+
# HACK: to detect transparent disconnects
|
96
106
|
if http.instance_variable_get(:@ssl_context).session_cache_stats[:cache_hits] != 0
|
97
107
|
raise 'TLS Reconnected'
|
98
108
|
end
|
@@ -113,24 +123,34 @@ module Yawast
|
|
113
123
|
Yawast::Utilities.puts_info "TLS Session Request Limit: Connection terminated after #{count} requests (#{e.message})"
|
114
124
|
end
|
115
125
|
|
126
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'vulnerable', false
|
127
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'requests', count
|
128
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'exception', e.message
|
129
|
+
|
116
130
|
return
|
117
131
|
end
|
118
132
|
|
119
133
|
puts
|
120
134
|
limit_formatted = limit.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse
|
121
135
|
Yawast::Utilities.puts_vuln "TLS Session Request Limit: Connection not terminated after #{limit_formatted} requests; possibly vulnerable to SWEET32"
|
136
|
+
|
137
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'vulnerable', true
|
138
|
+
Yawast::Shared::Output.log_value 'ssl', 'sweet32', 'requests', count
|
122
139
|
end
|
123
140
|
|
124
141
|
def self.check_tdes
|
142
|
+
ret = false
|
125
143
|
puts 'Confirming your OpenSSL supports 3DES cipher suites...'
|
126
144
|
|
127
|
-
#find all versions that don't include '_server' or '_client'
|
145
|
+
# find all versions that don't include '_server' or '_client'
|
128
146
|
versions = OpenSSL::SSL::SSLContext::METHODS.find_all { |v| !v.to_s.include?('_client') && !v.to_s.include?('_server')}
|
129
147
|
|
130
148
|
versions.each do |version|
|
131
|
-
#ignore SSLv23, as it's an auto-negotiate, which just adds noise
|
149
|
+
# ignore SSLv23, as it's an auto-negotiate, which just adds noise
|
132
150
|
if version.to_s != 'SSLv23' && version.to_s != 'SSLv2'
|
133
|
-
#try to get the list of ciphers supported for each version
|
151
|
+
# try to get the list of ciphers supported for each version
|
152
|
+
Yawast::Shared::Output.log_append_value 'openssl', 'tls_versions', version.to_s
|
153
|
+
|
134
154
|
ciphers = nil
|
135
155
|
|
136
156
|
get_ciphers_failed = false
|
@@ -144,8 +164,10 @@ module Yawast
|
|
144
164
|
if ciphers != nil
|
145
165
|
ciphers.each do |cipher|
|
146
166
|
if cipher[0].include?('3DES') || cipher[0].include?('CBC3')
|
147
|
-
|
167
|
+
ret = true
|
148
168
|
end
|
169
|
+
|
170
|
+
Yawast::Shared::Output.log_append_value 'openssl', 'tls_ciphers', version.to_s, cipher[0]
|
149
171
|
end
|
150
172
|
elsif !get_ciphers_failed
|
151
173
|
Yawast::Utilities.puts_info "\t#{version}: No cipher suites available."
|
@@ -154,7 +176,7 @@ module Yawast
|
|
154
176
|
end
|
155
177
|
|
156
178
|
puts ''
|
157
|
-
|
179
|
+
ret
|
158
180
|
end
|
159
181
|
end
|
160
182
|
end
|
data/lib/scanner/ssl.rb
CHANGED
@@ -8,6 +8,11 @@ module Yawast
|
|
8
8
|
class Ssl
|
9
9
|
def self.info(uri, check_ciphers, tdes_session_count)
|
10
10
|
begin
|
11
|
+
puts
|
12
|
+
puts 'DEPRECATED: The Internal SSL Scanner (--internalssl) is deprecated and will not be updated.'
|
13
|
+
puts 'DEPRECATED: Use a tool such as testssl.sh or sslyze instead.'
|
14
|
+
puts
|
15
|
+
|
11
16
|
socket = TCPSocket.new(uri.host, uri.port)
|
12
17
|
|
13
18
|
ctx = OpenSSL::SSL::SSLContext.new
|
@@ -89,7 +94,7 @@ module Yawast
|
|
89
94
|
#It looks like a change to Ruby's OpenSSL wrapper is needed to actually fix this right.
|
90
95
|
|
91
96
|
if cert.issuer == cert.subject
|
92
|
-
Yawast::Utilities.puts_vuln "\t\tCertificate Is Self-
|
97
|
+
Yawast::Utilities.puts_vuln "\t\tCertificate Is Self-Signed"
|
93
98
|
else
|
94
99
|
Yawast::Utilities.puts_warn "\t\tCertificate Chain Is Incomplete"
|
95
100
|
end
|
data/lib/scanner/ssl_labs.rb
CHANGED
@@ -36,7 +36,14 @@ module Yawast
|
|
36
36
|
puts "\tSSL Labs: https://www.ssllabs.com/ssltest/analyze.html?d=#{uri.host}&hideResults=on"
|
37
37
|
puts
|
38
38
|
|
39
|
-
|
39
|
+
json = nil
|
40
|
+
begin
|
41
|
+
json = JSON.parse data_body
|
42
|
+
rescue => e
|
43
|
+
raise Exception, "Invalid response from SSL Labs: '#{e.message}'"
|
44
|
+
end
|
45
|
+
|
46
|
+
process_results uri, json, tdes_session_count
|
40
47
|
rescue => e
|
41
48
|
puts
|
42
49
|
Yawast::Utilities.puts_error "SSL Labs Error: #{e.message}"
|
@@ -45,24 +52,34 @@ module Yawast
|
|
45
52
|
|
46
53
|
def self.process_results(uri, body, tdes_session_count)
|
47
54
|
begin
|
48
|
-
body['endpoints'].
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
if !body['endpoints'].nil?
|
56
|
+
body['endpoints'].each do |ep|
|
57
|
+
Yawast::Utilities.puts_info "IP: #{ep['ipAddress']} - Grade: #{ep['grade']}"
|
58
|
+
puts
|
59
|
+
|
60
|
+
begin
|
61
|
+
if ep['statusMessage'] == 'Ready'
|
62
|
+
get_cert_info ep, body
|
63
|
+
get_config_info ep
|
64
|
+
get_proto_info ep
|
65
|
+
else
|
66
|
+
Yawast::Utilities.puts_error "Error getting information for IP: #{ep['ipAddress']}: #{ep['statusMessage']}"
|
67
|
+
end
|
68
|
+
rescue => e
|
69
|
+
Yawast::Utilities.puts_error "Error getting information for IP: #{ep['ipAddress']}: #{e.message}"
|
59
70
|
end
|
60
|
-
rescue => e
|
61
|
-
Yawast::Utilities.puts_error "Error getting information for IP: #{ep['ipAddress']}: #{e.message}"
|
62
|
-
end
|
63
71
|
|
64
|
-
|
72
|
+
Yawast::Scanner::Plugins::SSL::Sweet32.get_tdes_session_msg_count(uri) if tdes_session_count
|
73
|
+
|
74
|
+
puts
|
75
|
+
end
|
76
|
+
else
|
77
|
+
Yawast::Utilities.puts_error 'SSL Labs Error: No Endpoint Data Received.'
|
65
78
|
|
79
|
+
#TODO - Remove this before release
|
80
|
+
puts
|
81
|
+
puts "DEBUG DATA (send to adam@adamcaudill.com): #{body}"
|
82
|
+
puts
|
66
83
|
puts
|
67
84
|
end
|
68
85
|
rescue => e
|
@@ -259,11 +276,37 @@ module Yawast
|
|
259
276
|
puts "\t\t Path #{path_count}:"
|
260
277
|
puts "\t\t Root Stores: #{trust_paths[key]}"
|
261
278
|
|
279
|
+
# cert chain issues
|
280
|
+
if chain['issues'] & (1<<1) != 0
|
281
|
+
Yawast::Utilities.puts_warn "\t\tCertificate Chain Issue: incomplete chain"
|
282
|
+
end
|
283
|
+
|
284
|
+
if chain['issues'] & (1<<2) != 0
|
285
|
+
Yawast::Utilities.puts_warn "\t\tCertificate Chain Issue: chain contains unrelated/duplicate certificates"
|
286
|
+
end
|
287
|
+
|
288
|
+
if chain['issues'] & (1<<3) != 0
|
289
|
+
Yawast::Utilities.puts_warn "\t\tCertificate Chain Issue: incorrect order"
|
290
|
+
end
|
291
|
+
|
292
|
+
if chain['issues'] & (1<<4) != 0
|
293
|
+
Yawast::Utilities.puts_warn "\t\tCertificate Chain Issue: contains anchor"
|
294
|
+
end
|
295
|
+
|
296
|
+
if cert['issues'] & (1<<5) != 0
|
297
|
+
Yawast::Utilities.puts_warn "\t\tCertificate Chain Issue: untrusted"
|
298
|
+
end
|
299
|
+
|
262
300
|
key.each do |path_cert|
|
263
301
|
body['certs'].each do |c|
|
264
302
|
if c['id'] == path_cert
|
265
303
|
Yawast::Utilities.puts_info "\t\t\t#{c['subject']}"
|
266
304
|
Yawast::Utilities.puts_info "\t\t\t Signature: #{c['sigAlg']} Key: #{c['keyAlg']}-#{c['keySize']}"
|
305
|
+
|
306
|
+
if Yawast::Scanner::Plugins::SSL::SSL.check_symantec_root(c['sha256Hash'])
|
307
|
+
Yawast::Utilities.puts_vuln "\t\t\t Untrusted Symantec Root"
|
308
|
+
end
|
309
|
+
|
267
310
|
Yawast::Utilities.puts_info "\t\t\t https://crt.sh/?q=#{c['sha1Hash']}"
|
268
311
|
|
269
312
|
if chain['certIds'].find_index(c['sha256Hash']) != nil
|
@@ -287,7 +330,11 @@ module Yawast
|
|
287
330
|
protos = Hash.new
|
288
331
|
ep['details']['protocols'].each do |proto|
|
289
332
|
if proto['name'] == 'SSL'
|
333
|
+
# show a vuln for SSLvX
|
290
334
|
Yawast::Utilities.puts_vuln "\t\t\t#{proto['name']} #{proto['version']}"
|
335
|
+
elsif proto['name'] == 'TLS' && proto['version'] == '1.0'
|
336
|
+
# show a warn for TLSv1.0
|
337
|
+
Yawast::Utilities.puts_warn "\t\t\t#{proto['name']} #{proto['version']}"
|
291
338
|
else
|
292
339
|
Yawast::Utilities.puts_info "\t\t\t#{proto['name']} #{proto['version']}"
|
293
340
|
end
|
data/lib/shared/http.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'securerandom'
|
2
2
|
require 'json'
|
3
|
+
require 'oj'
|
3
4
|
|
4
5
|
module Yawast
|
5
6
|
module Shared
|
@@ -33,7 +34,7 @@ module Yawast
|
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
|
-
def self.
|
37
|
+
def self.get_with_code(uri, headers = nil)
|
37
38
|
body = ''
|
38
39
|
|
39
40
|
begin
|
@@ -41,11 +42,19 @@ module Yawast
|
|
41
42
|
req.use_ssl = uri.scheme == 'https'
|
42
43
|
res = req.request_get(uri, get_headers(headers))
|
43
44
|
body = res.read_body
|
45
|
+
code = res.code
|
46
|
+
|
47
|
+
Yawast::Shared::Output.log_json 'debug', 'http_get', uri, Oj.dump(res)
|
44
48
|
rescue
|
45
49
|
# do nothing for now
|
46
50
|
end
|
47
51
|
|
48
52
|
body
|
53
|
+
return {:body => body, :code => code}
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.get(uri, headers = nil)
|
57
|
+
return get_with_code(uri, headers)[:body]
|
49
58
|
end
|
50
59
|
|
51
60
|
def self.get_json(uri)
|
@@ -79,6 +88,9 @@ module Yawast
|
|
79
88
|
req = get_http(uri)
|
80
89
|
req.use_ssl = uri.scheme == 'https'
|
81
90
|
res = req.head(uri, get_headers)
|
91
|
+
|
92
|
+
Yawast::Shared::Output.log_json 'debug', 'http_get_status_code', uri, Oj.dump(res)
|
93
|
+
|
82
94
|
res.code
|
83
95
|
end
|
84
96
|
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module Yawast
|
5
|
+
module Shared
|
6
|
+
class Output
|
7
|
+
def self.setup(uri, options)
|
8
|
+
return if @setup
|
9
|
+
|
10
|
+
@setup = true
|
11
|
+
|
12
|
+
time = Time.new.to_i.to_s
|
13
|
+
@file = options.output
|
14
|
+
|
15
|
+
# get the absolute path
|
16
|
+
@file = File.absolute_path @file
|
17
|
+
|
18
|
+
# see if this is a file or directory
|
19
|
+
if File.directory? @file
|
20
|
+
# in this case, the user just gave us a directory, se we will create a file name
|
21
|
+
@file = File.join(@file, uri.hostname + '_' + time + '.json')
|
22
|
+
else
|
23
|
+
# this means that it's a file, or doesn't exist
|
24
|
+
# so, let's see if it exists, if so, warn
|
25
|
+
if File.exist? @file
|
26
|
+
puts 'WARNING: Output file already exists; it will be replaced.'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
puts "Saving output to '#{@file}'"
|
31
|
+
puts
|
32
|
+
|
33
|
+
@data = {}
|
34
|
+
|
35
|
+
# add the initial entries to the output
|
36
|
+
log_value 'start_time', time
|
37
|
+
log_value 'yawast_version', VERSION
|
38
|
+
log_value 'ruby_version', "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
|
39
|
+
log_value 'openssl_version', OpenSSL::OPENSSL_VERSION
|
40
|
+
log_value 'platform', RUBY_PLATFORM
|
41
|
+
log_value 'target_uri', uri
|
42
|
+
log_value 'options', options.__hash__
|
43
|
+
log_value 'encoding', __ENCODING__
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.log_value(super_parent = nil, parent = nil, key, value)
|
47
|
+
return unless @setup
|
48
|
+
|
49
|
+
target = get_target super_parent, parent
|
50
|
+
|
51
|
+
target[key] = encode_utf8(value.to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.log_append_value(super_parent = nil, parent = nil, key, value)
|
55
|
+
return unless @setup
|
56
|
+
|
57
|
+
target = get_target super_parent, parent
|
58
|
+
|
59
|
+
if target[key].nil?
|
60
|
+
target[key] = []
|
61
|
+
end
|
62
|
+
|
63
|
+
# add value, after checking if it's already included
|
64
|
+
target[key].push encode_utf8(value.to_s) unless target[key].include? encode_utf8(value.to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.log_json(super_parent = nil, parent = nil, key, json_block)
|
68
|
+
return unless @setup
|
69
|
+
|
70
|
+
target = get_target super_parent, parent
|
71
|
+
|
72
|
+
target[key] = JSON.parse(json_block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.log_hash(super_parent = nil, parent = nil, key, hash)
|
76
|
+
return unless @setup
|
77
|
+
|
78
|
+
target = get_target super_parent, parent
|
79
|
+
|
80
|
+
target[key] = hash
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.encode_utf8(str)
|
84
|
+
str = str.dup
|
85
|
+
|
86
|
+
str = str.force_encoding('UTF-8') if [Encoding::ASCII_8BIT, Encoding::US_ASCII].include?(str.encoding)
|
87
|
+
|
88
|
+
str
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.get_target(super_parent = nil, parent = nil)
|
92
|
+
target = @data
|
93
|
+
|
94
|
+
# fix parent vs super confusion
|
95
|
+
if parent.nil? && !super_parent.nil?
|
96
|
+
parent = super_parent
|
97
|
+
super_parent = nil
|
98
|
+
end
|
99
|
+
|
100
|
+
unless super_parent.nil?
|
101
|
+
if target[super_parent].nil?
|
102
|
+
target[super_parent] = {}
|
103
|
+
end
|
104
|
+
|
105
|
+
target = target[super_parent]
|
106
|
+
end
|
107
|
+
|
108
|
+
unless parent.nil?
|
109
|
+
if target[parent].nil?
|
110
|
+
target[parent] = {}
|
111
|
+
end
|
112
|
+
|
113
|
+
target = target[parent]
|
114
|
+
end
|
115
|
+
|
116
|
+
target
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.escape_hash(hash)
|
120
|
+
hash.each_pair do |k,v|
|
121
|
+
if v.is_a?(Hash)
|
122
|
+
escape_hash(v)
|
123
|
+
else
|
124
|
+
if v.is_a?(String)
|
125
|
+
unless v.valid_encoding?
|
126
|
+
hash[k] = Base64.encode64 v
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.write_file
|
134
|
+
return unless @setup
|
135
|
+
|
136
|
+
# note the ending time
|
137
|
+
log_value 'end_time', Time.new.to_i.to_s
|
138
|
+
|
139
|
+
begin
|
140
|
+
json = JSON.pretty_generate @data
|
141
|
+
rescue JSON::GeneratorError
|
142
|
+
# this means that we don't have valid data to encode - need to perform some cleanup
|
143
|
+
@data = escape_hash @data
|
144
|
+
json = JSON.pretty_generate @data
|
145
|
+
end
|
146
|
+
|
147
|
+
File.open(@file, 'w') { |file| file.write(json) }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/util.rb
CHANGED
@@ -8,18 +8,22 @@ module Yawast
|
|
8
8
|
|
9
9
|
def self.puts_error(msg)
|
10
10
|
puts_msg('[E]'.red, msg)
|
11
|
+
Yawast::Shared::Output.log_append_value 'messages', 'error', msg
|
11
12
|
end
|
12
13
|
|
13
14
|
def self.puts_vuln(msg)
|
14
15
|
puts_msg('[V]'.magenta, msg)
|
16
|
+
Yawast::Shared::Output.log_append_value 'messages', 'vulnerability', msg
|
15
17
|
end
|
16
18
|
|
17
19
|
def self.puts_warn(msg)
|
18
20
|
puts_msg('[W]'.yellow, msg)
|
21
|
+
Yawast::Shared::Output.log_append_value 'messages', 'warning', msg
|
19
22
|
end
|
20
23
|
|
21
24
|
def self.puts_info(msg)
|
22
25
|
puts_msg('[I]'.green, msg)
|
26
|
+
Yawast::Shared::Output.log_append_value 'messages', 'info', msg
|
23
27
|
end
|
24
28
|
end
|
25
29
|
end
|
data/lib/version.rb
CHANGED
data/lib/yawast.rb
CHANGED
@@ -36,9 +36,10 @@ module Yawast
|
|
36
36
|
puts ' \_/\_| |_/\/ \/\_| |_/\____/ \_/ '
|
37
37
|
puts ''
|
38
38
|
puts "YAWAST v#{VERSION} - #{DESCRIPTION}"
|
39
|
-
puts ' Copyright (c) 2013-
|
39
|
+
puts ' Copyright (c) 2013-2019 Adam Caudill <adam@adamcaudill.com>'
|
40
40
|
puts ' Support & Documentation: https://github.com/adamcaudill/yawast'
|
41
41
|
puts " Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}; #{OpenSSL::OPENSSL_VERSION} (#{RUBY_PLATFORM})"
|
42
|
+
puts " Started at #{Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")}"
|
42
43
|
|
43
44
|
begin
|
44
45
|
version = Yawast::Shared::Http.get_json(URI('https://rubygems.org/api/v1/versions/yawast/latest.json'))['version']
|
@@ -58,6 +59,10 @@ module Yawast
|
|
58
59
|
trap 'SIGINT' do
|
59
60
|
puts
|
60
61
|
puts 'Scan cancelled by user.'
|
62
|
+
|
63
|
+
# attempt to save the output
|
64
|
+
Yawast::Shared::Output.write_file
|
65
|
+
|
61
66
|
exit 0
|
62
67
|
end
|
63
68
|
end
|
data/test/test_internalssl.rb
CHANGED
@@ -10,7 +10,7 @@ class TestInternalSSL < Minitest::Test
|
|
10
10
|
uri = URI.parse 'https://self-signed.badssl.com/'
|
11
11
|
Yawast::Scanner::Ssl.info uri, false, false
|
12
12
|
|
13
|
-
assert stdout_value.include?('Certificate Is Self-
|
13
|
+
assert stdout_value.include?('Certificate Is Self-Signed'), 'self-signed certificate warning not found'
|
14
14
|
|
15
15
|
restore_stdout
|
16
16
|
end
|
data/yawast.gemspec
CHANGED
@@ -22,6 +22,8 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.add_runtime_dependency 'public_suffix', '~> 2.0'
|
23
23
|
s.add_runtime_dependency 'sslshake', '~> 1.1'
|
24
24
|
s.add_runtime_dependency 'dnsruby', '~> 1.60'
|
25
|
+
s.add_runtime_dependency 'nokogiri', '~> 1.8'
|
26
|
+
s.add_runtime_dependency 'oj', '~> 3.6'
|
25
27
|
|
26
28
|
s.bindir = 'bin'
|
27
29
|
s.files = `git ls-files`.split("\n")
|