pwn 0.5.612 → 0.5.613

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8beaa60314e788a7122a92098c1ea4897cf488ef88efd1030f1f704656dbe1ff
4
- data.tar.gz: 94fff44f3cdc34c997fb920f63affa133cab0de4d309acf4c09f4451adbbdcf6
3
+ metadata.gz: 0563c6746728c394749e21798be5d36bceea0ce56035d9335c502859d6759e26
4
+ data.tar.gz: 9120b0f1dd304181819f92c818aada0410e3e597cb8b5786014d29af645bf414
5
5
  SHA512:
6
- metadata.gz: d9869f0a43c78a53238b1ef1ab58f54f27f1da575ad101aff4c172e813ec7878b92465f5d0f8984bce33a1a6227ec3a67c80ee63e1643683deaa45e2860e9c52
7
- data.tar.gz: 1407422fb0cc44c84232f37f664a07eb0a80bafaf748e8aadbe310652640ed83526e1ce48d34a1d0302d4537c27ca71983c44595824b39080693157ba038322f
6
+ metadata.gz: e568e242a20341084cd27c12f899b31125eb7b91c07c0df0b54782798c293b2787ab78fcdd1ff1f751cd7e83f928777586e534e1e3c0a7d7bbf2f0a0dd355dfe
7
+ data.tar.gz: 0e833b7e1ccab11809488dff111ca65c5f41919e7f5f6d62653898f0daf0a72ad86d154814467a74e8b8a2d79c0764a551c616acc1ec2d868b00108878589088
data/README.md CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
37
37
  $ ./install.sh
38
38
  $ ./install.sh ruby-gem
39
39
  $ pwn
40
- pwn[v0.5.612]:001 >>> PWN.help
40
+ pwn[v0.5.613]:001 >>> PWN.help
41
41
  ```
42
42
 
43
43
  [![Installing the pwn Security Automation Framework](https://raw.githubusercontent.com/0dayInc/pwn/master/documentation/pwn_install.png)](https://youtu.be/G7iLUY4FzsI)
@@ -52,7 +52,7 @@ $ rvm use ruby-4.0.5@pwn
52
52
  $ gem uninstall --all --executables pwn
53
53
  $ gem install --verbose pwn
54
54
  $ pwn
55
- pwn[v0.5.612]:001 >>> PWN.help
55
+ pwn[v0.5.613]:001 >>> PWN.help
56
56
  ```
57
57
 
58
58
  If you're using a multi-user install of RVM do:
@@ -62,7 +62,7 @@ $ rvm use ruby-4.0.5@pwn
62
62
  $ rvmsudo gem uninstall --all --executables pwn
63
63
  $ rvmsudo gem install --verbose pwn
64
64
  $ pwn
65
- pwn[v0.5.612]:001 >>> PWN.help
65
+ pwn[v0.5.613]:001 >>> PWN.help
66
66
  ```
67
67
 
68
68
  PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
data/lib/pwn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PWN
4
- VERSION = '0.5.612'
4
+ VERSION = '0.5.613'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pwn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.612
4
+ version: 0.5.613
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -1983,7 +1983,6 @@ files:
1983
1983
  - lib/pwn/plugins/vsphere.rb
1984
1984
  - lib/pwn/plugins/xxd.rb
1985
1985
  - lib/pwn/plugins/zaproxy.rb
1986
- - lib/pwn/plugins/zaproxy.rb.bak
1987
1986
  - lib/pwn/reports.rb
1988
1987
  - lib/pwn/reports/fuzz.rb
1989
1988
  - lib/pwn/reports/html_footer.rb
@@ -1,837 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'cgi'
4
- require 'fileutils'
5
- require 'pty'
6
- require 'securerandom'
7
- require 'json'
8
- require 'uri'
9
-
10
- module PWN
11
- module Plugins
12
- # This plugin converts images to readable text
13
- # TODO: Convert all rest requests to POST instead of GET
14
- module Zaproxy
15
- @@logger = PWN::Plugins::PWNLogger.create
16
-
17
- # Supported Method Parameters::
18
- # zap_rest_call(
19
- # zap_obj: 'required zap_obj returned from #start method',
20
- # rest_call: 'required rest call to make per the schema',
21
- # http_method: 'optional HTTP method (defaults to GET)
22
- # http_body: 'optional HTTP body sent in HTTP methods that support it e.g. POST'
23
- # )
24
-
25
- private_class_method def self.zap_rest_call(opts = {})
26
- zap_obj = opts[:zap_obj]
27
- rest_call = opts[:rest_call].to_s.scrub
28
- http_method = opts[:http_method] ||= :get
29
- http_method = http_method.to_s.downcase.to_sym unless http_method.is_a?(Symbol)
30
- params = opts[:params]
31
- http_body = opts[:http_body].to_s.scrub
32
-
33
- rest_client = zap_obj[:rest_browser]::Request
34
- mitm_rest_api = zap_obj[:mitm_rest_api]
35
-
36
- base_zap_api_uri = "http://#{mitm_rest_api}"
37
-
38
- case http_method
39
- when :get
40
- response = rest_client.execute(
41
- method: :get,
42
- url: "#{base_zap_api_uri}/#{rest_call}",
43
- headers: {
44
- params: params
45
- },
46
- verify_ssl: false
47
- )
48
-
49
- when :post
50
- response = rest_client.execute(
51
- method: :post,
52
- url: "#{base_zap_api_uri}/#{rest_call}",
53
- headers: {
54
- content_type: 'application/json; charset=UTF-8'
55
- },
56
- payload: http_body,
57
- verify_ssl: false
58
- )
59
-
60
- else
61
- raise @@logger.error("Unsupported HTTP Method #{http_method} for #{self} Plugin")
62
- end
63
-
64
- sleep 3
65
-
66
- response
67
- rescue StandardError, SystemExit, Interrupt => e
68
- stop(zap_obj: zap_obj) unless zap_obj.nil?
69
- raise e
70
- end
71
-
72
- # Supported Method Parameters::
73
- # zap_obj = PWN::Plugins::Zaproxy.start(
74
- # api_key: 'required - api key for API authorization',
75
- # zap_bin_path: 'optional - path to zap.sh file'
76
- # headless: 'optional - run zap headless if set to true',
77
- # browser_type: 'optional - defaults to :firefox. See PWN::Plugins::TransparentBrowser.help for a list of types',
78
- # proxy: 'optional - change local zap proxy listener (defaults to http://127.0.0.1:<Random 1024-65535>)',
79
- # )
80
-
81
- public_class_method def self.start(opts = {})
82
- zap_obj = {}
83
- api_key = opts[:api_key]
84
- raise 'ERROR: api_key must be provided' if api_key.nil?
85
-
86
- zap_obj[:api_key] = api_key
87
-
88
- zap_bin_path = opts[:zap_bin_path] ||= '/usr/share/zaproxy/zap.sh'
89
- raise "ERROR: #{zap_bin_path} not found." unless File.exist?(zap_bin_path)
90
-
91
- zap_bin = File.basename(zap_bin_path)
92
- zap_root = File.dirname(zap_bin_path)
93
-
94
- headless = opts[:headless] || false
95
- browser_type = opts[:browser_type] ||= :firefox
96
- browser_type = browser_type.to_s.downcase.to_sym unless browser_type.is_a?(Symbol)
97
- browser_type = :headless if headless
98
- zap_ip = opts[:zap_ip] ||= '127.0.0.1'
99
- zap_port = opts[:zap_port] ||= PWN::Plugins::Sock.get_random_unused_port
100
-
101
- zap_rest_ip = zap_ip
102
- zap_rest_port = zap_port
103
-
104
- browser_obj1 = PWN::Plugins::TransparentBrowser.open(browser_type: :rest)
105
- rest_browser = browser_obj1[:browser]
106
-
107
- zap_obj[:mitm_proxy] = "#{zap_ip}:#{zap_port}"
108
- zap_obj[:mitm_rest_api] = zap_obj[:mitm_proxy]
109
- zap_obj[:rest_browser] = rest_browser
110
-
111
- browser_obj2 = PWN::Plugins::TransparentBrowser.open(
112
- browser_type: browser_type,
113
- proxy: "http://#{zap_obj[:mitm_proxy]}",
114
- devtools: true
115
- )
116
-
117
- zap_obj[:mitm_browser] = browser_obj2
118
-
119
- timestamp = Time.now.strftime('%Y-%m-%d_%H-%M-%S%z')
120
- session_path = "/tmp/zaproxy-#{timestamp}.session"
121
- zap_obj[:session_path] = session_path
122
-
123
- if headless
124
- # TODO: Ensure Default Context still exists and is default context
125
- zaproxy_cmd = "cd #{zap_root} && ./#{zap_bin} -daemon -newsession #{session_path}"
126
- else
127
- zaproxy_cmd = "cd #{zap_root} && ./#{zap_bin} -newsession #{session_path}"
128
- end
129
-
130
- zaproxy_cmd = "#{zaproxy_cmd} -host #{zap_ip} -port #{zap_port}"
131
-
132
- zap_obj[:pid] = Process.spawn(zaproxy_cmd, pgroup: true)
133
- # Wait for pwn_burp_port to open prior to returning burp_obj
134
- loop do
135
- s = TCPSocket.new(zap_rest_ip, zap_rest_port)
136
- s.close
137
- break
138
- rescue Errno::ECONNREFUSED
139
- print '.'
140
- sleep 3
141
- next
142
- end
143
-
144
- zap_obj
145
- rescue Selenium::WebDriver::Error::SessionNotCreatedError, StandardError, SystemExit, Interrupt => e
146
- stop(zap_obj: zap_obj) unless zap_obj.nil?
147
- raise e
148
- end
149
-
150
- # Supported Method Parameters::
151
- # PWN::Plugins::Zaproxy.import_openapi_to_sitemap(
152
- # zap_obj: 'required - zap_obj returned from #open method',
153
- # openapi_spec: 'required - path to OpenAPI JSON or YAML spec file',
154
- # additional_http_headers: 'optional - hash of additional HTTP headers to include in requests (default: {})',
155
- # target_regex: 'optional - url regex to inject additional_http_headers into (e.g. https://test.domain.local.*)'
156
- # )
157
-
158
- public_class_method def self.import_openapi_to_sitemap(opts = {})
159
- zap_obj = opts[:zap_obj]
160
- api_key = zap_obj[:api_key].to_s.scrub
161
- openapi_spec = opts[:openapi_spec]
162
- raise "ERROR: openapi_spec file #{openapi_spec} does not exist" unless File.exist?(openapi_spec)
163
-
164
- openapi_spec_root = File.dirname(openapi_spec)
165
- Dir.chdir(openapi_spec_root)
166
-
167
- additional_http_headers = opts[:additional_http_headers] ||= {}
168
- raise 'ERROR: additional_http_headers must be a Hash' unless additional_http_headers.is_a?(Hash)
169
-
170
- target_regex = opts[:target_regex]
171
-
172
- if additional_http_headers.any?
173
- inject_additional_http_headers(
174
- zap_obj: zap_obj,
175
- target_regex: target_regex,
176
- headers: additional_http_headers
177
- )
178
- end
179
-
180
- params = {
181
- apikey: api_key,
182
- file: openapi_spec
183
- }
184
-
185
- response = zap_rest_call(
186
- zap_obj: zap_obj,
187
- rest_call: 'JSON/openapi/action/importFile/',
188
- params: params
189
- )
190
-
191
- JSON.parse(response.body, symbolize_names: true)
192
- rescue StandardError, SystemExit, Interrupt => e
193
- stop(zap_obj: zap_obj) unless zap_obj.nil?
194
- raise e
195
- end
196
-
197
- # Supported Method Parameters::
198
- # json_sitemap = PWN::Plugins::Zaproxy.get_sitemap(
199
- # zap_obj: 'required - zap_obj returned from #open method',
200
- # keyword: 'optional - string to search for in the sitemap entries (defaults to nil)',
201
- # return_as: 'optional - :base64 or :har (defaults to :base64)'
202
- # )
203
-
204
- public_class_method def self.get_sitemap(opts = {})
205
- zap_obj = opts[:zap_obj]
206
- api_key = zap_obj[:api_key].to_s.scrub
207
- keyword = opts[:keyword]
208
- return_as = opts[:return_as] ||= :base64
209
- raise 'ERROR: return_as must be :base64 or :har' unless %i[base64 har].include?(return_as)
210
-
211
- entries = []
212
- start = 0
213
- count = 1000
214
-
215
- # Get all entries in sitemap
216
- loop do
217
- params = { apikey: api_key, start: start, count: count }
218
-
219
- response = zap_rest_call(
220
- zap_obj: zap_obj,
221
- rest_call: 'OTHER/exim/other/exportHar/',
222
- params: params
223
- )
224
-
225
- har_data = JSON.parse(response.body, symbolize_names: true)
226
- new_entries = har_data[:log][:entries]
227
- break if new_entries.empty?
228
-
229
- entries += new_entries
230
- start += count
231
- end
232
-
233
- if keyword
234
- entries = har_sitemap.select do |site|
235
- json_request = site[:request].to_json
236
- json_request.include?(keyword)
237
- end
238
- end
239
-
240
- if return_as == :base64
241
- # Deduplicate entries based on method + url
242
- base64_entries = []
243
- entries.each do |entry|
244
- entry_hash = {}
245
- req = entry[:request]
246
- key = [req[:method], req[:url]]
247
-
248
- # Build full request string
249
- req_line = "#{req[:method]} #{req[:url]} #{req[:httpVersion]}\r\n"
250
- req_headers = req[:headers].map { |h| "#{h[:name]}: #{h[:value]}\r\n" }.join
251
- req_body = ''
252
- if req[:postData] && req[:postData][:text]
253
- req_body = req[:postData][:text]
254
- req_body = Base64.decode64(req_body) if req[:postData][:encoding] == 'base64'
255
- end
256
- full_req = "#{req_line}#{req_headers}\r\n#{req_body}".force_encoding('ASCII-8BIT')
257
- encoded_req = Base64.strict_encode64(full_req)
258
-
259
- # Build full response string
260
- res = entry[:response]
261
- res_line = "#{res[:httpVersion]} #{res[:status]} #{res[:statusText]}\r\n"
262
- res_headers = res[:headers].map { |h| "#{h[:name]}: #{h[:value]}\r\n" }.join
263
- res_body = ''
264
- if res[:content] && res[:content][:text]
265
- res_body = res[:content][:text]
266
- res_body = Base64.decode64(res_body) if res[:content][:encoding] == 'base64'
267
- end
268
- full_res = "#{res_line}#{res_headers}\r\n#{res_body}".force_encoding('ASCII-8BIT')
269
- encoded_res = Base64.strict_encode64(full_res)
270
-
271
- # Extract http_service
272
- uri = URI.parse(req[:url])
273
- http_service = {
274
- host: uri.host,
275
- port: uri.port,
276
- protocol: uri.scheme
277
- }
278
-
279
- # Add to array
280
- entry_hash[:request] = encoded_req
281
- entry_hash[:response] = encoded_res
282
- entry_hash[:http_service] = http_service
283
- base64_entries.push(entry_hash)
284
- end
285
- entries = base64_entries
286
- end
287
-
288
- entries.uniq
289
- rescue StandardError, SystemExit, Interrupt => e
290
- stop(zap_obj: zap_obj) unless zap_obj.nil?
291
- raise e
292
- end
293
-
294
- # Supported Method Parameters::
295
- # PWN::Plugins::Zaproxy.add_to_scope(
296
- # zap_obj: 'required - zap_obj returned from #open method',
297
- # target_regex: 'required - url regex to add to scope (e.g. https://test.domain.local.*)',
298
- # context_name: 'optional - context name to add target_regex to (defaults to Default Context)'
299
- # )
300
-
301
- public_class_method def self.add_to_scope(opts = {})
302
- zap_obj = opts[:zap_obj]
303
- api_key = zap_obj[:api_key].to_s.scrub
304
- target_regex = opts[:target_regex]
305
- raise 'ERROR: target_url must be provided' if target_regex.nil?
306
-
307
- context_name = opts[:context_name] ||= 'Default Context'
308
-
309
- params = {
310
- apikey: api_key,
311
- contextName: context_name,
312
- regex: target_regex
313
- }
314
-
315
- response = zap_rest_call(
316
- zap_obj: zap_obj,
317
- rest_call: 'JSON/context/action/includeInContext/',
318
- params: params
319
- )
320
-
321
- JSON.parse(response.body, symbolize_names: true)
322
- rescue StandardError, SystemExit, Interrupt => e
323
- stop(zap_obj: zap_obj) unless zap_obj.nil?
324
- raise e
325
- end
326
-
327
- # Supported Method Parameters::
328
- # PWN::Plugins::Zaproxy.requester(
329
- # zap_obj: 'required - zap_obj returned from #open method',
330
- # har_entry: 'required - har entry (e.g. from #get_sitemap method method)',
331
- # redirect: 'optional - follow redirects if set to true (defaults to false)'
332
- # )
333
-
334
- public_class_method def self.requester(opts = {})
335
- zap_obj = opts[:zap_obj]
336
- api_key = zap_obj[:api_key].to_s.scrub
337
- har_entry = opts[:har_entry]
338
- raise 'ERROR: har_entry must be provided and be a valid HAR entry' unless har_entry.is_a?(Hash) && har_entry.key?(:request) && har_entry.key?(:response)
339
-
340
- redirect = opts[:redirect] || false
341
- raise 'ERROR: redirect must be a boolean' unless redirect.is_a?(TrueClass) || redirect.is_a?(FalseClass)
342
-
343
- har_json = har_entry.to_json
344
- params = {
345
- apikey: api_key,
346
- request: har_json,
347
- followRedirects: redirect.to_s
348
- }
349
-
350
- response = zap_rest_call(
351
- zap_obj: zap_obj,
352
- rest_call: 'OTHER/exim/other/sendHarRequest/',
353
- params: params
354
- )
355
-
356
- JSON.parse(response.body, symbolize_names: true)
357
- rescue StandardError, SystemExit, Interrupt => e
358
- stop(zap_obj: zap_obj) unless zap_obj.nil?
359
- raise e
360
- end
361
-
362
- # Supported Method Parameters::
363
- # PWN::Plugins::Zaproxy.spider(
364
- # zap_obj: 'required - zap_obj returned from #open method',
365
- # target_url: 'required - url to spider'
366
- # )
367
-
368
- public_class_method def self.spider(opts = {})
369
- zap_obj = opts[:zap_obj]
370
- target_url = opts[:target_url].to_s.scrub
371
- api_key = zap_obj[:api_key].to_s.scrub
372
-
373
- # target_domain_name = URI.parse(target_url).host
374
-
375
- params = {
376
- apikey: api_key,
377
- url: target_url,
378
- maxChildren: 9,
379
- recurse: 3,
380
- contextName: '',
381
- subtreeOnly: target_url
382
- }
383
-
384
- response = zap_rest_call(
385
- zap_obj: zap_obj,
386
- rest_call: 'JSON/spider/action/scan/',
387
- params: params
388
- )
389
-
390
- spider = JSON.parse(response.body, symbolize_names: true)
391
- spider_id = spider[:scan].to_i
392
-
393
- loop do
394
- params = {
395
- apikey: api_key,
396
- scanId: spider_id
397
- }
398
-
399
- response = zap_rest_call(
400
- zap_obj: zap_obj,
401
- rest_call: 'JSON/spider/view/status/',
402
- params: params
403
- )
404
-
405
- spider = JSON.parse(response.body, symbolize_names: true)
406
- status = spider[:status].to_i
407
- puts "Spider ID: #{spider_id} => #{status}% Complete"
408
- break if status == 100
409
- end
410
- rescue StandardError, SystemExit, Interrupt => e
411
- stop(zap_obj: zap_obj) unless zap_obj.nil?
412
- raise e
413
- end
414
-
415
- # Supported Method Parameters::
416
- # PWN::Plugins::Zaproxy.inject_additional_http_headers(
417
- # zap_obj: 'required - zap_obj returned from #open method',
418
- # target_regex: 'required - url regex to inject headers into (e.g. https://test.domain.local.*)',
419
- # headers: 'required - hash of additional headers to inject into each request',
420
- # )
421
-
422
- public_class_method def self.inject_additional_http_headers(opts = {})
423
- zap_obj = opts[:zap_obj]
424
- api_key = zap_obj[:api_key].to_s.scrub
425
- target_regex = opts[:target_regex]
426
- raise 'ERROR: target_regex must be provided' if target_regex.nil?
427
-
428
- headers = opts[:headers] ||= {}
429
- raise 'ERROR: headers must be provided' if headers.empty? || !headers.is_a?(Hash)
430
-
431
- # Check if replacer rule already exists
432
- params = { apikey: api_key }
433
- response = zap_rest_call(
434
- zap_obj: zap_obj,
435
- rest_call: 'JSON/replacer/view/rules/',
436
- params: params
437
- )
438
- replacer = JSON.parse(response.body, symbolize_names: true)
439
-
440
- replacer_resp_arr = []
441
- headers.each_key do |header_key|
442
- this_header = { header: header_key }
443
- rule_exists = replacer[:rules].any? { |rule| rule[:description] == header_key.to_s && rule[:url] == target_regex }
444
-
445
- if rule_exists
446
- # Remove existing rule first
447
- puts "Removing existing replacer rule for header key: #{header_key}..."
448
- params = {
449
- apikey: api_key,
450
- description: header_key
451
- }
452
- zap_rest_call(
453
- zap_obj: zap_obj,
454
- rest_call: 'JSON/replacer/action/removeRule/',
455
- params: params
456
- )
457
- end
458
-
459
- puts "Adding replacer rule for header key: #{header_key}..."
460
- params = {
461
- apikey: api_key,
462
- description: header_key,
463
- enabled: 'true',
464
- matchType: 'REQ_HEADER',
465
- matchRegex: 'false',
466
- matchString: header_key,
467
- replacement: headers[header_key],
468
- initiators: '',
469
- url: target_regex
470
- }
471
-
472
- response = zap_rest_call(
473
- zap_obj: zap_obj,
474
- rest_call: 'JSON/replacer/action/addRule/',
475
- params: params
476
- )
477
-
478
- json_resp = JSON.parse(response.body, symbolize_names: true)
479
- this_header[:Result] = json_resp[:Result]
480
- replacer_resp_arr.push(this_header)
481
- end
482
-
483
- replacer_resp_arr
484
- rescue StandardError, SystemExit, Interrupt => e
485
- stop(zap_obj: zap_obj) unless zap_obj.nil?
486
- raise e
487
- end
488
-
489
- # Supported Method Parameters::
490
- # PWN::Plugins::Zaproxy.active_scan(
491
- # zap_obj: 'required - zap_obj returned from #open method',
492
- # target_url: 'required - url to scan',
493
- # exclude_paths: 'optional - array of paths to exclude from scan (default: [])',
494
- # scan_policy: 'optional - scan policy to use (defaults to Default Policy)'
495
- # )
496
-
497
- public_class_method def self.active_scan(opts = {})
498
- zap_obj = opts[:zap_obj]
499
- api_key = zap_obj[:api_key].to_s.scrub
500
- target_url = opts[:target_url]
501
- raise 'ERROR: target_url must be provided' if target_url.nil?
502
-
503
- exclude_paths = opts[:exclude_paths] ||= []
504
- scan_policy = opts[:scan_policy] ||= 'Default Policy'
505
-
506
- exclude_paths.each do |exclude_path|
507
- # Remove trailing .* from target_url if it exists
508
- target_url = target_url.delete_suffix('.*') if target_url.end_with?('.*')
509
- exclude_path_regex = "#{target_url}#{exclude_path}.*"
510
- params = {
511
- apikey: api_key,
512
- regex: exclude_path_regex
513
- }
514
- zap_rest_call(
515
- zap_obj: zap_obj,
516
- rest_call: 'JSON/ascan/action/excludeFromScan/',
517
- params: params
518
- )
519
- puts "Excluding #{exclude_path_regex} from Active Scan"
520
- end
521
-
522
- # TODO: Implement adding target_url to scope so that inScopeOnly can be changed to true
523
- params = {
524
- apikey: api_key,
525
- url: target_url,
526
- recurse: true,
527
- inScopeOnly: true,
528
- scanPolicyName: scan_policy
529
- }
530
-
531
- response = zap_rest_call(
532
- zap_obj: zap_obj,
533
- rest_call: 'JSON/ascan/action/scan/',
534
- params: params
535
- )
536
-
537
- active_scan = JSON.parse(response.body, symbolize_names: true)
538
- active_scan_id = active_scan[:scan].to_i
539
-
540
- loop do
541
- params = {
542
- apikey: api_key,
543
- scanId: active_scan_id
544
- }
545
-
546
- response = zap_rest_call(
547
- zap_obj: zap_obj,
548
- rest_call: 'JSON/ascan/view/status/',
549
- params: params
550
- )
551
-
552
- active_scan = JSON.parse(response.body, symbolize_names: true)
553
- status = active_scan[:status].to_i
554
- puts "Active Scan ID: #{active_scan_id} => #{status}% Complete"
555
- break if status == 100
556
- end
557
- rescue StandardError, SystemExit, Interrupt => e
558
- stop(zap_obj: zap_obj) unless zap_obj.nil?
559
- raise e
560
- end
561
-
562
- # Supported Method Parameters::
563
- # PWN::Plugins::Zaproxy.get_alerts(
564
- # zap_obj: 'required - zap_obj returned from #open method',
565
- # target_url: 'required - base url to return alerts'
566
- # )
567
-
568
- public_class_method def self.get_alerts(opts = {})
569
- zap_obj = opts[:zap_obj]
570
- api_key = zap_obj[:api_key].to_s.scrub
571
- target_url = opts[:target_url]
572
-
573
- params = {
574
- apikey: api_key,
575
- url: target_url
576
- }
577
-
578
- response = zap_rest_call(
579
- zap_obj: zap_obj,
580
- rest_call: 'JSON/core/view/alerts/',
581
- params: params
582
- )
583
-
584
- JSON.parse(response.body, symbolize_names: true)
585
- rescue StandardError, SystemExit, Interrupt => e
586
- stop(zap_obj: zap_obj) unless zap_obj.nil?
587
- raise e
588
- end
589
-
590
- # Supported Method Parameters::
591
- # report_path = PWN::Plugins::Zaproxy.generate_scan_report(
592
- # zap_obj: 'required - zap_obj returned from #open method',
593
- # output_dir: 'required - directory to save report',
594
- # report_type: 'required - <:html|:markdown|:xml>'
595
- # )
596
-
597
- public_class_method def self.generate_scan_report(opts = {})
598
- zap_obj = opts[:zap_obj]
599
- api_key = zap_obj[:api_key].to_s.scrub
600
- output_dir = opts[:output_dir]
601
- raise "ERROR: output_dir #{output_dir} does not exist." unless Dir.exist?(output_dir)
602
-
603
- report_type = opts[:report_type]
604
-
605
- valid_report_types_arr = %i[html markdown xml]
606
- raise "ERROR: Invalid report_type => #{report_type}" unless valid_report_types_arr.include?(report_type)
607
-
608
- case report_type
609
- when :html
610
- report_path = "#{output_dir}/zaproxy_active_scan_results.html"
611
- rest_call = 'OTHER/core/other/htmlreport/'
612
- when :markdown
613
- report_path = "#{output_dir}/zaproxy_active_scan_results.md"
614
- rest_call = 'OTHER/core/other/mdreport/'
615
- when :xml
616
- report_path = "#{output_dir}/zaproxy_active_scan_results.xml"
617
- rest_call = 'OTHER/core/other/xmlreport/'
618
- end
619
-
620
- params = {
621
- apikey: api_key
622
- }
623
-
624
- response = zap_rest_call(
625
- zap_obj: zap_obj,
626
- rest_call: rest_call,
627
- params: params
628
- )
629
-
630
- File.open(report_path, 'w') do |f|
631
- f.puts response.body
632
- end
633
-
634
- report_path
635
- rescue StandardError, SystemExit, Interrupt => e
636
- stop(zap_obj: zap_obj) unless zap_obj.nil?
637
- raise e
638
- end
639
-
640
- # Supported Method Parameters::
641
- # PWN::Plugins::Zaproxy.breakpoint(
642
- # zap_obj: 'required - zap_obj returned from #open method',
643
- # regex_type: 'required - :url, :request_header, :request_body, :response_header or :response_body',
644
- # regex_pattern: 'required - regex pattern to search for respective regex_type',
645
- # enabled: 'optional - boolean (defaults to true)'
646
- # )
647
-
648
- public_class_method def self.breakpoint(opts = {})
649
- zap_obj = opts[:zap_obj]
650
- api_key = zap_obj[:api_key].to_s.scrub
651
-
652
- case opts[:regex_type].to_sym
653
- when :url, :request_header, :request_body, :response_header, :response_body
654
- regex_type = opts[:regex_type].to_sym
655
- else
656
- raise "Unknown regex_type: #{opts[:regex_type].to_sym}\noptions are :url, :request_header, :request_body, :response_header or :response_body"
657
- end
658
- regex_pattern = opts[:regex_pattern]
659
- enabled = opts[:enabled]
660
-
661
- enabled = true if enabled.nil?
662
- enabled ? (action = 'addHttpBreakpoint') : (action = 'removeHttpBreakpoint')
663
-
664
- zap_rest_call(
665
- zap_obj: zap_obj,
666
- rest_call: "JSON/break/action/#{action}/?zapapiformat=JSON&apikey=#{api_key}&string=#{regex_pattern}&location=#{regex_type}&match=regex&inverse=false&ignorecase=true",
667
- http_method: :get
668
- )
669
- rescue StandardError, SystemExit, Interrupt => e
670
- stop(zap_obj: zap_obj) unless zap_obj.nil?
671
- raise e
672
- end
673
-
674
- # Supported Method Parameters::
675
- # PWN::Plugins::Zaproxy.tamper(
676
- # zap_obj: 'required - zap_obj returned from #open method',
677
- # domain: 'required - FQDN to tamper (e.g. test.domain.local)',
678
- # enabled: 'optional - boolean (defaults to true)'
679
- # )
680
-
681
- public_class_method def self.tamper(opts = {})
682
- zap_obj = opts[:zap_obj]
683
- api_key = zap_obj[:api_key].to_s.scrub
684
- domain = opts[:domain]
685
- enabled = opts[:enabled]
686
-
687
- enabled = true if enabled.nil?
688
- enabled ? (action = 'addHttpBreakpoint') : (action = 'removeHttpBreakpoint')
689
-
690
- zap_rest_call(
691
- zap_obj: zap_obj,
692
- rest_call: "JSON/break/action/#{action}/?zapapiformat=JSON&apikey=#{api_key}&string=#{domain}&location=url&match=contains&inverse=false&ignorecase=true",
693
- http_method: :get
694
- )
695
-
696
- zap_rest_call(
697
- zap_obj: zap_obj,
698
- rest_call: "JSON/break/action/break/?zapapiformat=JSON&apikey=#{api_key}&type=http-request&state=#{enabled}",
699
- http_method: :get
700
- )
701
- rescue StandardError, SystemExit, Interrupt => e
702
- stop(zap_obj: zap_obj) unless zap_obj.nil?
703
- raise e
704
- end
705
-
706
- # Supported Method Parameters::
707
- # PWN::Plugins::Zaproxy.stop(
708
- # zap_obj: 'required - zap_obj returned from #open method'
709
- # )
710
-
711
- public_class_method def self.stop(opts = {})
712
- zap_obj = opts[:zap_obj]
713
- api_key = zap_obj[:api_key]
714
- browser_obj = zap_obj[:mitm_browser]
715
-
716
- PWN::Plugins::TransparentBrowser.close(browser_obj: browser_obj)
717
-
718
- params = { apikey: api_key }
719
- zap_rest_call(
720
- zap_obj: zap_obj,
721
- rest_call: 'JSON/core/action/shutdown/',
722
- params: params
723
- )
724
-
725
- session_path = zap_obj[:session_path]
726
- session_path_files = Dir.glob("#{session_path}*")
727
- # Remove session files - need to add a slight delay between each unlink to work around file locks
728
- session_path_files.each do |f|
729
- FileUtils.rm_f(f)
730
- sleep 0.3
731
- end
732
-
733
- zap_obj = nil
734
- rescue StandardError, SystemExit, Interrupt => e
735
- raise e
736
- end
737
-
738
- # Author(s):: 0day Inc. <support@0dayinc.com>
739
-
740
- public_class_method def self.authors
741
- "AUTHOR(S):
742
- 0day Inc. <support@0dayinc.com>
743
- "
744
- end
745
-
746
- # Display Usage for this Module
747
-
748
- public_class_method def self.help
749
- puts "USAGE:
750
- zap_obj = #{self}.start(
751
- api_key: 'required - api key for API authorization',
752
- zap_bin_path: 'optional - path to zap.sh file',
753
- headless: 'optional - run zap headless if set to true',
754
- proxy: 'optional - change local zap proxy listener (defaults to http://127.0.0.1:<Random 1024-65535>)'
755
- )
756
-
757
- #{self}.spider(
758
- zap_obj: 'required - zap_obj returned from #open method',
759
- target_url: 'required - url to spider'
760
- )
761
-
762
- #{self}.import_openapi_to_sitemap(
763
- zap_obj: 'required - zap_obj returned from #open method',
764
- openapi_spec: 'required - path to OpenAPI JSON or YAML spec file',
765
- additional_http_headers: 'optional - hash of additional HTTP headers to include in requests (default: {})',
766
- target_regex: 'optional - url regex to inject additional_http_headers into (e.g. https://test.domain.local.*)'
767
- )
768
-
769
- #{self}.get_sitemap(
770
- zap_obj: 'required - zap_obj returned from #open method',
771
- keyword: 'optional - string to search for in the sitemap entries (defaults to nil)',
772
- return_as: 'optional - :base64 or :har (defaults to :base64)'
773
- )
774
-
775
- #{self}.add_to_scope(
776
- zap_obj: 'required - zap_obj returned from #open method',
777
- target_regex: 'required - url regex to add to scope (e.g. https://test.domain.local.*)',
778
- context_name: 'optional - context name to add target_regex to (defaults to Default Context)'
779
- )
780
-
781
- #{self}.requester(
782
- zap_obj: 'required - zap_obj returned from #open method',
783
- har_entry: 'required - har entry (e.g. from #get_sitemap method method)',
784
- redirect: 'optional - follow redirects if set to true (defaults to true)'
785
- )
786
-
787
- json_sitemap = #{self}.spider(
788
- zap_obj: 'required - zap_obj returned from #open method'
789
- )
790
-
791
- #{self}.inject_additional_http_headers(
792
- zap_obj: 'required - zap_obj returned from #open method',
793
- target_regex: 'required - url regex to inject headers into (e.g. https://test.domain.local.*)',
794
- headers: 'required - hash of additional headers to inject into each request'
795
- )
796
-
797
- #{self}.active_scan(
798
- zap_obj: 'required - zap_obj returned from #open method'
799
- target_url: 'required - url to scan',
800
- exclude_paths: 'optional - array of paths to exclude from scan (default: [])',
801
- scan_policy: 'optional - scan policy to use (defaults to Default Policy)'
802
- )
803
-
804
- json_alerts = #{self}.get_alerts(
805
- zap_obj: 'required - zap_obj returned from #open method'
806
- target_url: 'required - base url to return alerts'
807
- )
808
-
809
- report_path = #{self}.generate_scan_report(
810
- zap_obj: 'required - zap_obj returned from #open method',
811
- output_dir: 'required - directory to save report',
812
- report_type: 'required - <:html|:markdown|:xml>'
813
- )
814
-
815
- #{self}.breakpoint(
816
- zap_obj: 'required - zap_obj returned from #open method',
817
- regex_type: 'required - :url, :request_header, :request_body, :response_header or :response_body',
818
- regex_pattern: 'required - regex pattern to search for respective regex_type',
819
- enabled: 'optional - boolean (defaults to true)'
820
- )
821
-
822
- #{self}.tamper(
823
- zap_obj: 'required - zap_obj returned from #open method',
824
- domain: 'required - FQDN to tamper (e.g. test.domain.local)',
825
- enabled: 'optional - boolean (defaults to true)'
826
- )
827
-
828
- #{self}.stop(
829
- zap_obj: 'required - zap_obj returned from #start method'
830
- )
831
-
832
- #{self}.authors
833
- "
834
- end
835
- end
836
- end
837
- end