yawast 0.7.0.beta2 → 0.7.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +3 -0
- data/README.md +5 -1
- data/lib/resources/common_file.txt +208 -0
- data/lib/scanner/core.rb +3 -2
- data/lib/scanner/plugins/applications/cms/generic.rb +11 -1
- data/lib/scanner/plugins/applications/framework/rails.rb +39 -0
- data/lib/scanner/plugins/applications/generic/password_reset.rb +40 -14
- data/lib/scanner/plugins/dns/caa.rb +1 -1
- data/lib/scanner/plugins/http/generic.rb +18 -8
- data/lib/scanner/plugins/servers/apache.rb +113 -15
- data/lib/scanner/plugins/servers/generic.rb +8 -0
- data/lib/scanner/plugins/servers/iis.rb +26 -3
- data/lib/scanner/plugins/servers/nginx.rb +33 -0
- data/lib/scanner/plugins/servers/python.rb +8 -0
- data/lib/scanner/plugins/spider/spider.rb +7 -3
- data/lib/scanner/vuln_scan.rb +18 -5
- data/lib/shared/http.rb +1 -5
- data/lib/shared/output.rb +10 -7
- data/lib/version.rb +1 -1
- data/test/data/dir.txt +9 -0
- data/test/data/etc_passwd.txt +16 -0
- data/test/data/nginx_status_page.txt +4 -0
- data/test/test_app_fw_rails.rb +28 -0
- data/test/test_scan_apache.rb +23 -0
- data/test/test_scan_nginx.rb +33 -0
- data/yawast.gemspec +0 -1
- metadata +13 -18
- data/test/test_scan_nginx_banner.rb +0 -17
@@ -17,12 +17,17 @@ module Yawast
|
|
17
17
|
puts "\t\t\"curl -X PROPFIND #{uri}\""
|
18
18
|
|
19
19
|
puts ''
|
20
|
-
end
|
21
20
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
22
|
+
'http_propfind_enabled',
|
23
|
+
{vulnerable: true, body: res.body, code: res.code,
|
24
|
+
content_type: res['Content-Type'], length: res.body.length}
|
25
|
+
else
|
26
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
27
|
+
'http_propfind_enabled',
|
28
|
+
{vulnerable: false, body: res.body, code: res.code,
|
29
|
+
content_type: res['Content-Type'], length: res.body.length}
|
30
|
+
end
|
26
31
|
end
|
27
32
|
end
|
28
33
|
|
@@ -38,10 +43,15 @@ module Yawast
|
|
38
43
|
puts "\t\t\"curl -X TRACE #{uri}\""
|
39
44
|
|
40
45
|
puts ''
|
41
|
-
end
|
42
46
|
|
43
|
-
|
44
|
-
|
47
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
48
|
+
'http_trace_enabled',
|
49
|
+
{vulnerable: true, body: res.body, code: res.code}
|
50
|
+
else
|
51
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
52
|
+
'http_trace_enabled',
|
53
|
+
{vulnerable: false, body: res.body, code: res.code}
|
54
|
+
end
|
45
55
|
end
|
46
56
|
end
|
47
57
|
|
@@ -9,6 +9,16 @@ module Yawast
|
|
9
9
|
module Servers
|
10
10
|
class Apache
|
11
11
|
def self.check_banner(banner)
|
12
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
13
|
+
'apache_openssl_version_exposed',
|
14
|
+
{vulnerable: false, version: nil}
|
15
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
16
|
+
'apache_httpd_version_exposed',
|
17
|
+
{vulnerable: false, version: nil}
|
18
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
19
|
+
'apache_httpd_modules_exposed',
|
20
|
+
{vulnerable: false, modules: nil}
|
21
|
+
|
12
22
|
# don't bother if this doesn't look like Apache
|
13
23
|
return unless banner.include? 'Apache'
|
14
24
|
@apache = true
|
@@ -26,23 +36,33 @@ module Yawast
|
|
26
36
|
# print the server info no matter what we do next
|
27
37
|
Yawast::Utilities.puts_info "Apache Server: #{server}"
|
28
38
|
modules.delete_at 0
|
39
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
40
|
+
'apache_httpd_version_exposed',
|
41
|
+
{vulnerable: true, version: server}
|
29
42
|
|
30
43
|
if modules.count.positive?
|
31
44
|
Yawast::Utilities.puts_warn 'Apache Server: Module listing enabled'
|
32
45
|
modules.each { |mod| Yawast::Utilities.puts_warn "\t\t#{mod}" }
|
33
46
|
puts ''
|
47
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
48
|
+
'apache_httpd_modules_exposed',
|
49
|
+
{vulnerable: true, modules: banner}
|
34
50
|
|
35
51
|
# check for special items
|
36
52
|
modules.each do |mod|
|
37
53
|
if mod.include? 'OpenSSL'
|
38
54
|
Yawast::Utilities.puts_warn "OpenSSL Version Disclosure: #{mod}"
|
39
55
|
puts ''
|
56
|
+
|
57
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
58
|
+
'apache_openssl_version_exposed',
|
59
|
+
{vulnerable: true, version: mod}
|
40
60
|
end
|
41
61
|
end
|
42
62
|
end
|
43
63
|
end
|
44
64
|
|
45
|
-
def self.check_all(uri)
|
65
|
+
def self.check_all(uri, links = nil)
|
46
66
|
# run all the defined checks
|
47
67
|
check_server_status(uri.copy)
|
48
68
|
check_server_info(uri.copy)
|
@@ -50,6 +70,10 @@ module Yawast
|
|
50
70
|
check_tomcat_version(uri.copy)
|
51
71
|
check_tomcat_put_rce(uri.copy)
|
52
72
|
check_struts2_samples(uri.copy)
|
73
|
+
|
74
|
+
unless links.nil?
|
75
|
+
check_cve_2019_0232(links)
|
76
|
+
end
|
53
77
|
end
|
54
78
|
|
55
79
|
def self.check_server_status(uri)
|
@@ -61,6 +85,10 @@ module Yawast
|
|
61
85
|
end
|
62
86
|
|
63
87
|
def self.check_tomcat_version(uri)
|
88
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
89
|
+
'apache_tomcat_version_exposed',
|
90
|
+
{vulnerable: false, version: nil, body: nil}
|
91
|
+
|
64
92
|
begin
|
65
93
|
req = Yawast::Shared::Http.get_http(uri)
|
66
94
|
req.use_ssl = uri.scheme == 'https'
|
@@ -73,11 +101,17 @@ module Yawast
|
|
73
101
|
|
74
102
|
if !version.nil? && !version[0].nil?
|
75
103
|
Yawast::Utilities.puts_warn "Apache Tomcat Version Found: #{version[0]}"
|
76
|
-
Yawast::Shared::Output.
|
104
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
105
|
+
'apache_tomcat_version_exposed',
|
106
|
+
{vulnerable: true, version: version[0], body: res.body}
|
77
107
|
|
78
108
|
puts "\t\t\"curl -X XYZ #{uri}\""
|
79
109
|
|
80
110
|
puts ''
|
111
|
+
else
|
112
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
113
|
+
'apache_tomcat_version_exposed',
|
114
|
+
{vulnerable: false, version: nil, body: res.body}
|
81
115
|
end
|
82
116
|
end
|
83
117
|
end
|
@@ -89,6 +123,13 @@ module Yawast
|
|
89
123
|
end
|
90
124
|
|
91
125
|
def self.check_tomcat_manager_paths(uri, base_path, manager)
|
126
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
127
|
+
'apache_tomcat_manager_exposed',
|
128
|
+
{vulnerable: false, uri: nil}
|
129
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
130
|
+
'apache_tomcat_host_manager_exposed',
|
131
|
+
{vulnerable: false, uri: nil}
|
132
|
+
|
92
133
|
uri.path = "/#{base_path}/html"
|
93
134
|
uri.query = '' unless uri.query.nil?
|
94
135
|
|
@@ -97,7 +138,9 @@ module Yawast
|
|
97
138
|
if ret.include? '<tt>conf/tomcat-users.xml</tt>'
|
98
139
|
# this will get Tomcat 7+
|
99
140
|
Yawast::Utilities.puts_warn "Apache Tomcat #{manager} page found: #{uri}"
|
100
|
-
Yawast::Shared::Output.
|
141
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
142
|
+
'apache_tomcat_manager_exposed',
|
143
|
+
{vulnerable: true, uri: uri}
|
101
144
|
check_tomcat_manager_passwords uri, manager
|
102
145
|
|
103
146
|
puts ''
|
@@ -109,7 +152,9 @@ module Yawast
|
|
109
152
|
|
110
153
|
if ret.include? '<tt>conf/tomcat-users.xml</tt>'
|
111
154
|
Yawast::Utilities.puts_warn "Apache Tomcat #{manager} page found: #{uri}"
|
112
|
-
Yawast::Shared::Output.
|
155
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
156
|
+
'apache_tomcat_host_manager_exposed',
|
157
|
+
{vulnerable: true, uri: uri}
|
113
158
|
check_tomcat_manager_passwords uri, manager
|
114
159
|
|
115
160
|
puts ''
|
@@ -118,6 +163,9 @@ module Yawast
|
|
118
163
|
end
|
119
164
|
|
120
165
|
def self.check_tomcat_manager_passwords(uri, manager)
|
166
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
167
|
+
'apache_tomcat_manager_weak_pass',
|
168
|
+
{vulnerable: false, uri: nil, credentials: nil}
|
121
169
|
# check for known passwords
|
122
170
|
check_tomcat_manager_pwd_check uri, manager, 'tomcat:tomcat'
|
123
171
|
check_tomcat_manager_pwd_check uri, manager, 'tomcat:password'
|
@@ -133,7 +181,9 @@ module Yawast
|
|
133
181
|
ret.include?('<font size="+2">Tomcat Virtual Host Manager</font>')
|
134
182
|
Yawast::Utilities.puts_vuln "Apache Tomcat #{manager} weak password: #{credentials}"
|
135
183
|
|
136
|
-
Yawast::Shared::Output.
|
184
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
185
|
+
'apache_tomcat_manager_weak_pass',
|
186
|
+
{vulnerable: true, uri: uri, credentials: credentials}
|
137
187
|
end
|
138
188
|
end
|
139
189
|
|
@@ -142,13 +192,9 @@ module Yawast
|
|
142
192
|
uri.path = "/#{SecureRandom.hex}.jsp/"
|
143
193
|
uri.query = '' unless uri.query.nil?
|
144
194
|
|
145
|
-
Yawast::Shared::Output.log_value 'apache', 'cve_2017_12615', 'path', uri
|
146
|
-
|
147
195
|
# we'll use this to verify that it actually worked
|
148
196
|
check_value = SecureRandom.hex
|
149
197
|
|
150
|
-
Yawast::Shared::Output.log_value 'apache', 'cve_2017_12615', 'check_value', check_value
|
151
|
-
|
152
198
|
# upload the JSP file
|
153
199
|
req_data = "<% out.println(\"#{check_value}\");%>"
|
154
200
|
Yawast::Shared::Http.put(uri, req_data)
|
@@ -157,13 +203,15 @@ module Yawast
|
|
157
203
|
uri.path = uri.path.chomp('/')
|
158
204
|
res = Yawast::Shared::Http.get(uri)
|
159
205
|
|
160
|
-
Yawast::Shared::Output.log_value 'apache', 'cve_2017_12615', 'body', res
|
161
|
-
|
162
206
|
if res.include? check_value
|
163
207
|
Yawast::Utilities.puts_vuln "Apache Tomcat PUT RCE (CVE-2017-12615): #{uri}"
|
164
|
-
Yawast::Shared::Output.
|
208
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
209
|
+
'apache_tomcat_cve_2017_12615',
|
210
|
+
{vulnerable: true, uri: uri, check_value: check_value, body: res}
|
165
211
|
else
|
166
|
-
Yawast::Shared::Output.
|
212
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
213
|
+
'apache_tomcat_cve_2017_12615',
|
214
|
+
{vulnerable: false, uri: uri, check_value: check_value, body: res}
|
167
215
|
end
|
168
216
|
end
|
169
217
|
|
@@ -188,16 +236,66 @@ module Yawast
|
|
188
236
|
end
|
189
237
|
end
|
190
238
|
|
239
|
+
def self.check_cve_2019_0232(links)
|
240
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
241
|
+
'apache_tomcat_cve_2019_0232',
|
242
|
+
{vulnerable: false, uri: nil, body: nil}
|
243
|
+
|
244
|
+
# create a list of possible targets - this would be links that include "cgi-bin"
|
245
|
+
targets = []
|
246
|
+
links.each do |link|
|
247
|
+
targets.push link if link.include? '/cgi-bin/'
|
248
|
+
end
|
249
|
+
|
250
|
+
# check to see if we have any targets
|
251
|
+
unless targets.count.zero?
|
252
|
+
# we have targets
|
253
|
+
targets.each do |target|
|
254
|
+
# now, we need to build the URI we'll use
|
255
|
+
target = if target.include? '?'
|
256
|
+
target + '&dir'
|
257
|
+
else
|
258
|
+
target + '?dir'
|
259
|
+
end
|
260
|
+
|
261
|
+
# now, send the request and see if it includes "<DIR>"
|
262
|
+
target_uri = URI.parse target
|
263
|
+
body = Yawast::Shared::Http.get(target_uri)
|
264
|
+
|
265
|
+
if body.include? '<DIR>'
|
266
|
+
# we have a hit
|
267
|
+
|
268
|
+
Yawast::Utilities.puts_vuln "Apache Tomcat RCE (CVE-2019-0232): #{uri}"
|
269
|
+
|
270
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
271
|
+
'apache_tomcat_cve_2019_0232',
|
272
|
+
{vulnerable: true, uri: target_uri, body: body}
|
273
|
+
|
274
|
+
break
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
191
280
|
def self.check_page_for_string(uri, path, search)
|
192
281
|
uri.path = path
|
193
282
|
uri.query = '' unless uri.query.nil?
|
194
283
|
|
195
|
-
|
284
|
+
body = Yawast::Shared::Http.get(uri)
|
196
285
|
|
197
|
-
if
|
286
|
+
if body.include? search
|
198
287
|
Yawast::Utilities.puts_vuln "#{search} page found: #{uri}"
|
199
288
|
Yawast::Shared::Output.log_value 'apache', 'page_search', search, uri
|
289
|
+
|
290
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
291
|
+
"apache_#{path.tr('-', '_').tr('/', '')}_found",
|
292
|
+
{vulnerable: true, uri: uri, body: body}
|
293
|
+
|
200
294
|
puts ''
|
295
|
+
else
|
296
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
297
|
+
"apache_#{path.tr('-', '_').chomp('/')}_found",
|
298
|
+
{vulnerable: false, uri: uri, body: body}
|
201
299
|
end
|
202
300
|
end
|
203
301
|
end
|
@@ -6,6 +6,10 @@ module Yawast
|
|
6
6
|
module Servers
|
7
7
|
class Generic
|
8
8
|
def self.check_banner_php(banner)
|
9
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
10
|
+
'php_version_exposed',
|
11
|
+
{vulnerable: false, version: nil}
|
12
|
+
|
9
13
|
# don't bother if this doesn't include PHP
|
10
14
|
return unless banner.include? 'PHP/'
|
11
15
|
|
@@ -15,6 +19,10 @@ module Yawast
|
|
15
19
|
if mod.include? 'PHP/'
|
16
20
|
Yawast::Utilities.puts_warn "PHP Version: #{mod}"
|
17
21
|
puts ''
|
22
|
+
|
23
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
24
|
+
'php_version_exposed',
|
25
|
+
{vulnerable: true, version: mod}
|
18
26
|
end
|
19
27
|
end
|
20
28
|
end
|
@@ -6,12 +6,20 @@ module Yawast
|
|
6
6
|
module Servers
|
7
7
|
class Iis
|
8
8
|
def self.check_banner(banner)
|
9
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
10
|
+
'iis_version_exposed',
|
11
|
+
{vulnerable: false, version: nil}
|
12
|
+
|
9
13
|
# don't bother if this doesn't include IIS
|
10
14
|
return unless banner.include? 'Microsoft-IIS/'
|
11
15
|
@iis = true
|
12
16
|
|
13
17
|
Yawast::Utilities.puts_warn "IIS Version: #{banner}"
|
14
18
|
puts ''
|
19
|
+
|
20
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
21
|
+
'iis_version_exposed',
|
22
|
+
{vulnerable: true, version: banner}
|
15
23
|
end
|
16
24
|
|
17
25
|
def self.check_all(uri, head)
|
@@ -34,6 +42,14 @@ module Yawast
|
|
34
42
|
if k.downcase == search
|
35
43
|
Yawast::Utilities.puts_warn "#{message} Version: #{v}"
|
36
44
|
puts ''
|
45
|
+
|
46
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
47
|
+
"asp_net_#{search.tr('-', '_')}_version_exposed",
|
48
|
+
{vulnerable: true, version: v}
|
49
|
+
else
|
50
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
51
|
+
"asp_net_#{search.tr('-', '_')}_version_exposed",
|
52
|
+
{vulnerable: false, version: nil}
|
37
53
|
end
|
38
54
|
end
|
39
55
|
end
|
@@ -47,10 +63,17 @@ module Yawast
|
|
47
63
|
headers['Accept'] = '*/*'
|
48
64
|
res = req.request(Debug.new('/', headers))
|
49
65
|
|
50
|
-
|
66
|
+
if res.code == 200
|
67
|
+
Yawast::Utilities.puts_vuln 'ASP.NET Debugging Enabled'
|
51
68
|
|
52
|
-
|
53
|
-
|
69
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
70
|
+
'asp_net_debug_enabled',
|
71
|
+
{vulnerable: true, body: res.body, code: res.code}
|
72
|
+
else
|
73
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
74
|
+
'asp_net_debug_enabled',
|
75
|
+
{vulnerable: false, body: res.body, code: res.code}
|
76
|
+
end
|
54
77
|
end
|
55
78
|
end
|
56
79
|
end
|
@@ -6,11 +6,44 @@ module Yawast
|
|
6
6
|
module Servers
|
7
7
|
class Nginx
|
8
8
|
def self.check_banner(banner)
|
9
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
10
|
+
'nginx_version_exposed',
|
11
|
+
{vulnerable: false, version: nil}
|
12
|
+
|
9
13
|
# don't bother if this doesn't include nginx
|
10
14
|
return unless banner.include? 'nginx/'
|
11
15
|
|
12
16
|
Yawast::Utilities.puts_warn "nginx Version: #{banner}"
|
13
17
|
puts ''
|
18
|
+
|
19
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
20
|
+
'nginx_version_exposed',
|
21
|
+
{vulnerable: true, version: banner}
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.check_all(uri)
|
25
|
+
check_status_page uri.copy
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.check_status_page(uri)
|
29
|
+
uri.path = '/status'
|
30
|
+
uri.query = '' unless uri.query.nil?
|
31
|
+
|
32
|
+
body = Yawast::Shared::Http.get(uri)
|
33
|
+
|
34
|
+
if body.include? 'Active connections:'
|
35
|
+
Yawast::Utilities.puts_vuln "Nginx status page found: #{uri}"
|
36
|
+
|
37
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
38
|
+
'nginx_status_found',
|
39
|
+
{vulnerable: true, uri: uri, body: body}
|
40
|
+
|
41
|
+
puts ''
|
42
|
+
else
|
43
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
44
|
+
'nginx_status_found',
|
45
|
+
{vulnerable: false, uri: uri, body: body}
|
46
|
+
end
|
14
47
|
end
|
15
48
|
end
|
16
49
|
end
|
@@ -6,11 +6,19 @@ module Yawast
|
|
6
6
|
module Servers
|
7
7
|
class Python
|
8
8
|
def self.check_banner(banner)
|
9
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
10
|
+
'python_version_exposed',
|
11
|
+
{vulnerable: false, version: nil}
|
12
|
+
|
9
13
|
# don't bother if this doesn't include Python
|
10
14
|
return unless banner.include? 'Python/'
|
11
15
|
|
12
16
|
Yawast::Utilities.puts_warn "Python Version: #{banner}"
|
13
17
|
puts ''
|
18
|
+
|
19
|
+
Yawast::Shared::Output.log_hash 'vulnerabilities',
|
20
|
+
'python_version_exposed',
|
21
|
+
{vulnerable: true, version: banner}
|
14
22
|
end
|
15
23
|
end
|
16
24
|
end
|
@@ -7,7 +7,7 @@ module Yawast
|
|
7
7
|
module Plugins
|
8
8
|
module Spider
|
9
9
|
class Spider
|
10
|
-
def self.spider(uri)
|
10
|
+
def self.spider(uri, silent = false)
|
11
11
|
@uri = uri.copy
|
12
12
|
|
13
13
|
@workers = []
|
@@ -15,7 +15,7 @@ module Yawast
|
|
15
15
|
|
16
16
|
@links = []
|
17
17
|
@links.push @uri.to_s
|
18
|
-
puts 'Spidering site...'
|
18
|
+
puts 'Spidering site...' unless silent
|
19
19
|
get_links @uri
|
20
20
|
|
21
21
|
results = Thread.new do
|
@@ -23,7 +23,9 @@ module Yawast
|
|
23
23
|
while true
|
24
24
|
if @results.length.positive?
|
25
25
|
out = @results.pop(true)
|
26
|
-
|
26
|
+
|
27
|
+
Yawast::Utilities.puts_info out unless silent
|
28
|
+
|
27
29
|
Yawast::Shared::Output.log_append_value 'spider', 'get', out
|
28
30
|
end
|
29
31
|
end
|
@@ -36,6 +38,8 @@ module Yawast
|
|
36
38
|
results.terminate
|
37
39
|
|
38
40
|
puts
|
41
|
+
|
42
|
+
@links
|
39
43
|
end
|
40
44
|
|
41
45
|
def self.get_links(uri)
|