yawast 0.6.0 → 0.7.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 +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")
|