pwn 0.5.332 → 0.5.333

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.
@@ -56,12 +56,7 @@ module PWN
56
56
 
57
57
  burp_root = File.dirname(burp_jar_path)
58
58
 
59
- browser_type = if opts[:browser_type].nil?
60
- :firefox
61
- else
62
- opts[:browser_type]
63
- end
64
-
59
+ browser_type = opts[:browser_type] ||= :firefox
65
60
  burp_ip = opts[:burp_ip] ||= '127.0.0.1'
66
61
  burp_port = opts[:burp_port] ||= 8080
67
62
  # burp_port = opts[:burp_port] ||= PWN::Plugins::Sock.get_random_unused_port
@@ -90,7 +85,8 @@ module PWN
90
85
  # Proxy always listens on localhost...use SSH tunneling if remote access is required
91
86
  browser_obj2 = PWN::Plugins::TransparentBrowser.open(
92
87
  browser_type: browser_type,
93
- proxy: "http://#{burp_obj[:mitm_proxy]}"
88
+ proxy: "http://#{burp_obj[:mitm_proxy]}",
89
+ devtools: true
94
90
  )
95
91
 
96
92
  burp_obj[:burp_browser] = browser_obj2
@@ -122,41 +118,51 @@ module PWN
122
118
  end
123
119
 
124
120
  # Supported Method Parameters::
125
- # uri_in_scope_bool = PWN::Plugins::BurpSuite.uri_in_scope(
126
- # target_config: 'required - path to burp suite pro target config JSON file',
121
+ # uri_in_scope = PWN::Plugins::BurpSuite.in_scope(
122
+ # burp_obj: 'required - burp_obj returned by #start method',
127
123
  # uri: 'required - URI to determine if in scope'
128
124
  # )
129
125
 
130
- public_class_method def self.uri_in_scope(opts = {})
131
- target_config = opts[:target_config]
132
- raise 'ERROR: target_config does not exist' unless File.exist?(target_config)
126
+ public_class_method def self.in_scope(opts = {})
127
+ burp_obj = opts[:burp_obj]
128
+ raise 'ERROR: burp_obj parameter is required' unless burp_obj.is_a?(Hash)
133
129
 
134
130
  uri = opts[:uri]
135
131
  raise 'ERROR: uri parameter is required' if uri.nil?
136
132
 
137
- target_config_json = JSON.parse(
138
- File.read(target_config),
139
- symbolize_names: true
133
+ rest_browser = burp_obj[:rest_browser]
134
+ pwn_burp_api = burp_obj[:pwn_burp_api]
135
+ base64_encoded_uri = Base64.strict_encode64(uri.to_s.scrub.strip.chomp)
136
+
137
+ in_scope_resp = rest_browser.get(
138
+ "http://#{pwn_burp_api}/scope/#{base64_encoded_uri}",
139
+ content_type: 'application/json; charset=UTF8'
140
140
  )
141
+ json_in_scope = JSON.parse(in_scope_resp, symbolize_names: true)
142
+ json_in_scope[:value]
143
+ rescue StandardError => e
144
+ stop(burp_obj: burp_obj) unless burp_obj.nil?
145
+ raise e
146
+ end
141
147
 
142
- out_of_scope = target_config_json[:target][:scope][:exclude]
143
- out_of_scope_arr = out_of_scope.select do |os|
144
- URI.parse(uri).scheme =~ /#{os[:protocol]}/ &&
145
- URI.parse(uri).host =~ /#{os[:host]}/ &&
146
- (URI.parse(uri).path =~ /#{os[:file]}/ || URI.parse(uri).path == '')
147
- end
148
- return false unless out_of_scope_arr.empty?
148
+ # Supported Method Parameters::
149
+ # json_in_scope = PWN::Plugins::BurpSuite.add_to_scope(
150
+ # burp_obj: 'required - burp_obj returned by #start method',
151
+ # target_url: 'required - target url to add to scope'
152
+ # )
149
153
 
150
- in_scope = target_config_json[:target][:scope][:include]
151
- in_scope_arr = in_scope.select do |is|
152
- URI.parse(uri).scheme =~ /#{is[:protocol]}/ &&
153
- URI.parse(uri).host =~ /#{is[:host]}/ &&
154
- (URI.parse(uri).path =~ /#{is[:file]}/ || URI.parse(uri).path == '')
155
- end
156
- return false if in_scope_arr.empty?
154
+ public_class_method def self.add_to_scope(opts = {})
155
+ burp_obj = opts[:burp_obj]
156
+ target_url = opts[:target_url]
157
+ rest_browser = burp_obj[:rest_browser]
158
+ pwn_burp_api = burp_obj[:pwn_burp_api]
157
159
 
158
- true
160
+ post_body = { url: target_url }.to_json
161
+
162
+ in_scope = rest_browser.post("http://#{pwn_burp_api}/scope", post_body, content_type: 'application/json; charset=UTF8')
163
+ JSON.parse(in_scope, symbolize_names: true)
159
164
  rescue StandardError => e
165
+ stop(burp_obj: burp_obj) unless burp_obj.nil?
160
166
  raise e
161
167
  end
162
168
 
@@ -171,6 +177,7 @@ module PWN
171
177
  pwn_burp_api = burp_obj[:pwn_burp_api]
172
178
 
173
179
  enable_resp = rest_browser.post("http://#{pwn_burp_api}/proxy/intercept/enable", nil)
180
+ JSON.parse(enable_resp, symbolize_names: true)
174
181
  rescue StandardError => e
175
182
  stop(burp_obj: burp_obj) unless burp_obj.nil?
176
183
  raise e
@@ -187,25 +194,31 @@ module PWN
187
194
  pwn_burp_api = burp_obj[:pwn_burp_api]
188
195
 
189
196
  disable_resp = rest_browser.post("http://#{pwn_burp_api}/proxy/intercept/disable", nil)
197
+ JSON.parse(disable_resp, symbolize_names: true)
190
198
  rescue StandardError => e
191
199
  stop(burp_obj: burp_obj) unless burp_obj.nil?
192
200
  raise e
193
201
  end
194
202
 
195
203
  # Supported Method Parameters::
196
- # json_sitemap = PWN::Plugins::BurpSuite.get_current_sitemap(
204
+ # json_sitemap = PWN::Plugins::BurpSuite.get_sitemap(
197
205
  # burp_obj: 'required - burp_obj returned by #start method',
198
206
  # target_url: 'optional - target URL to filter sitemap results (defaults to entire sitemap)'
199
207
  # )
200
208
 
201
- public_class_method def self.get_current_sitemap(opts = {})
209
+ public_class_method def self.get_sitemap(opts = {})
202
210
  burp_obj = opts[:burp_obj]
203
211
  rest_browser = burp_obj[:rest_browser]
204
212
  pwn_burp_api = burp_obj[:pwn_burp_api]
205
213
  target_url = opts[:target_url]
206
214
 
215
+ base64_encoded_target_url = Base64.strict_encode64(target_url.to_s.scrub.strip.chomp) if target_url
216
+
217
+ rest_call = "http://#{pwn_burp_api}/sitemap"
218
+ rest_call = "#{rest_call}/#{base64_encoded_target_url}" if target_url
219
+
207
220
  sitemap = rest_browser.get(
208
- "http://#{pwn_burp_api}/sitemap",
221
+ rest_call,
209
222
  content_type: 'application/json; charset=UTF8'
210
223
  )
211
224
 
@@ -215,95 +228,345 @@ module PWN
215
228
  raise e
216
229
  end
217
230
 
218
- # Supported Method Parameters::
231
+ # Supported Method Parameters:
219
232
  # json_sitemap = PWN::Plugins::BurpSuite.add_to_sitemap(
220
233
  # burp_obj: 'required - burp_obj returned by #start method',
221
- # url: 'required - URL to add to sitemap',
222
- # method: 'required - HTTP method to use (e.g., GET, POST, PUT, DELETE)',
223
- # req_headers: 'optional - array of headers to include in the request (defaults to [])',
224
- # req_body: 'optional - body of the request (defaults to empty string)',
225
- # highlight: 'optional - highlight color :none|:red|:orange|:yellow|:green|:cyan|:blue|:pink|:magenta|:gray (defaults to :none)',
226
- # status_code: 'optional - HTTP status code to use in the response (defaults to 200)',
227
- # resp_headers: 'optional - array of response headers to include in the response (defaults to [])',
228
- # resp_body: 'optional - body of the response (defaults to empty JSON object "{}")',
229
- # comment: 'optional - comment to add to the sitemap entry (defaults to empty string)'
234
+ # sitemap: 'required - sitemap hash to add',
235
+ # debug: 'optional - boolean to enable sitemap debugging (default: false)'
230
236
  # )
237
+ #
238
+ # Example:
239
+ # json_sitemap = PWN::Plugins::BurpSuite.add_to_sitemap(
240
+ # burp_obj: burp_obj,
241
+ # sitemap: {
242
+ # request: 'base64_encoded_request_string',
243
+ # response: 'base64_encoded_response_string',
244
+ # highlight: 'NONE'||'RED'||'ORANGE'||'YELLOW'||'GREEN'||'CYAN'||'BLUE'||'PINK'||'MAGENTA'||'GRAY',
245
+ # comment: 'optional comment for the sitemap entry',
246
+ # http_service: {
247
+ # host: 'example.com',
248
+ # port: 80,
249
+ # protocol: 'http'
250
+ # }
251
+ # }
231
252
 
232
253
  public_class_method def self.add_to_sitemap(opts = {})
233
254
  burp_obj = opts[:burp_obj]
234
255
  rest_browser = burp_obj[:rest_browser]
235
256
  pwn_burp_api = burp_obj[:pwn_burp_api]
236
- url = opts[:url]
237
- raise ArgumentError, 'url parameter is required' if url.nil?
238
-
239
- valid_methods = %w[GET POST PUT DELETE PATCH OPTIONS HEAD TRACE CONNECT]
240
- method = opts[:method].to_s.upcase
241
- raise ArgumentError, "Invalid HTTP method: #{method}" unless valid_methods.include?(method)
242
-
243
- req_headers = opts[:req_headers] ||= []
244
- req_body = opts[:body].to_s
245
- valid_highlights = %i[none red orange yellow green cyan blue pink magenta gray]
246
- highlight = opts[:highlight] ||= :none
247
- raise ArgumentError, "Invalid highlight color: #{highlight}" unless valid_highlights.include?(highlight)
248
-
249
- status_code = opts[:status_code] ||= 200
250
- resp_headers = opts[:resp_headers] ||= []
251
- resp_body = opts[:resp_body] ||= '{}'
252
- comment = opts[:comment].to_s
253
-
254
- protocol = URI.parse(url).scheme
255
- host = URI.parse(url).host
256
- path = URI.parse(url).path
257
- port = URI.parse(url).port || (url.start_with?('https') ? 443 : 80)
258
-
259
- sitemap_message = {
260
- request: {
261
- method: method,
262
- url: url,
263
- path: path,
264
- headers: req_headers,
265
- body: req_body
266
- },
267
- response: {
268
- statusCode: status_code.to_i,
269
- headers: resp_headers,
270
- body: resp_body
271
- },
272
- http_service: {
273
- protocol: protocol,
274
- host: host,
275
- port: port
276
- },
277
- highlight: highlight,
278
- comment: comment
279
- }
280
-
281
- RestClient.post(
257
+ sitemap = opts[:sitemap] ||= {}
258
+ debug = opts[:debug] || false
259
+
260
+ # Send POST request to /sitemap
261
+ response = RestClient.post(
282
262
  "#{pwn_burp_api}/sitemap",
283
- sitemap_message.to_json,
284
- content_type: 'application/json; charset=UTF8'
263
+ sitemap.to_json,
264
+ content_type: 'application/json; charset=UTF-8'
285
265
  )
266
+
267
+ if debug
268
+ puts "\nSubmitted:"
269
+ puts sitemap.inspect
270
+ print 'Press Enter to continue...'
271
+ gets
272
+ end
273
+ # Return response body (assumed to be JSON)
274
+ JSON.parse(response.body, symbolize_names: true)
275
+ rescue RestClient::ExceptionWithResponse => e
276
+ raise StandardError, "HTTP error adding to sitemap: Status #{e.response.code}, Response: #{e.response.body}"
286
277
  rescue StandardError => e
287
- # stop(burp_obj: burp_obj) unless burp_obj.nil?
278
+ stop(burp_obj: burp_obj) unless burp_obj.nil?
288
279
  raise e
289
280
  end
290
281
 
291
- # Supported Method Parameters::
292
- # json_in_scope = PWN::Plugins::BurpSuite.add_to_scope(
282
+ # Supported Method Parameters:
283
+ # json_sitemap = PWN::Plugins::BurpSuite.import_openapi_to_sitemap(
293
284
  # burp_obj: 'required - burp_obj returned by #start method',
294
- # target_url: 'required - target url to add to scope'
285
+ # openapi_spec: 'required - path to OpenAPI JSON specification file',
286
+ # additional_http_headers: 'optional - hash of additional HTTP headers to include in requests (default: {})',
287
+ # highlight: 'optional - highlight color for the sitemap entry (default: "NONE")',
288
+ # comment: 'optional - comment for the sitemap entry (default: "")',
289
+ # debug: 'optional - boolean to enable debug logging (default: false)'
295
290
  # )
296
-
297
- public_class_method def self.add_to_scope(opts = {})
291
+ public_class_method def self.import_openapi_to_sitemap(opts = {})
298
292
  burp_obj = opts[:burp_obj]
299
- target_url = opts[:target_url]
300
- rest_browser = burp_obj[:rest_browser]
301
- pwn_burp_api = burp_obj[:pwn_burp_api]
293
+ raise 'ERROR: burp_obj parameter is required' unless burp_obj.is_a?(Hash)
302
294
 
303
- post_body = { url: target_url }.to_json
295
+ openapi_spec = opts[:openapi_spec]
296
+ raise 'ERROR: openapi_spec parameter not found' unless File.exist?(openapi_spec)
304
297
 
305
- in_scope = rest_browser.post("http://#{pwn_burp_api}/scope", post_body, content_type: 'application/json; charset=UTF8')
306
- JSON.parse(in_scope)
298
+ additional_http_headers = opts[:additional_http_headers] || {}
299
+ raise 'ERROR: additional_http_headers must be a Hash' unless additional_http_headers.is_a?(Hash)
300
+
301
+ highlight = opts[:highlight] ||= 'NONE'
302
+ comment = opts[:comment].to_s.scrub
303
+
304
+ debug = opts[:debug] || false
305
+
306
+ # Parse the OpenAPI JSON
307
+ openapi = JSON.parse(File.read(openapi_spec), symbolize_names: true)
308
+
309
+ # Initialize result array
310
+ sitemap_arr = []
311
+
312
+ # Get servers; default to empty array if not present
313
+ servers = openapi[:servers].is_a?(Array) ? openapi[:servers] : []
314
+ if servers.empty?
315
+ warn("No servers defined in #{openapi_spec}. Using default server 'http://localhost'.")
316
+ servers = [{ url: 'http://localhost', description: 'Default server' }]
317
+ end
318
+
319
+ # Valid HTTP methods for validation
320
+ valid_methods = %w[get post put patch delete head options trace connect]
321
+
322
+ # Iterate through each server
323
+ servers.each do |server|
324
+ server_url = server[:url]
325
+ unless server_url.is_a?(String)
326
+ warn("[ERROR] Invalid server URL type '#{server_url.class}' in #{openapi_spec}: Expected String, got #{server_url.inspect}")
327
+ next
328
+ end
329
+
330
+ begin
331
+ uri = URI.parse(server_url)
332
+ host = uri.host
333
+ port = uri.port
334
+ protocol = uri.scheme
335
+ server_path = uri.path&.sub(%r{^/+}, '')&.sub(%r{/+$}, '') || ''
336
+
337
+ warn("[DEBUG] Processing server: #{server_url}, host: #{host}, port: #{port}, protocol: #{protocol}, server_path: #{server_path}") if debug
338
+
339
+ # Iterate through each path and its methods
340
+ openapi[:paths]&.each do |path, methods|
341
+ # Convert path to string, handling different types
342
+ path_str = case path
343
+ when Symbol, String
344
+ path.to_s
345
+ else
346
+ warn("[ERROR] Invalid path type '#{path.class}' in #{openapi_spec}: Expected Symbol or String, got #{path.inspect}")
347
+ '/' # Fallback to root path
348
+ end
349
+
350
+ # Construct full path by prepending server path if present
351
+ full_path = server_path.empty? ? path_str : "/#{server_path}/#{path_str.sub(%r{^/+}, '')}".gsub(%r{/+}, '/')
352
+
353
+ # Initialize path-level parameters
354
+ path_parameters = []
355
+
356
+ # Process methods based on type
357
+ operations = []
358
+ if methods.is_a?(Hash)
359
+ # Extract path-level parameters
360
+ path_parameters = methods[:parameters].is_a?(Array) ? methods[:parameters] : []
361
+ warn("[DEBUG] Path-level parameters for #{full_path}: #{path_parameters.inspect}") if debug && !path_parameters.empty?
362
+
363
+ # Collect operations for valid HTTP methods
364
+ methods.each do |method, details|
365
+ method_str = case method
366
+ when Symbol, String
367
+ method.to_s.downcase
368
+ else
369
+ warn("[ERROR] Invalid method type '#{method.class}' for path '#{full_path}' in #{openapi_spec}: Expected Symbol or String, got #{method.inspect}")
370
+ nil
371
+ end
372
+
373
+ next unless method_str && valid_methods.include?(method_str)
374
+
375
+ operations << { method: method_str, details: details }
376
+ end
377
+ elsif methods.is_a?(Array)
378
+ warn("[DEBUG] Methods is an array for path '#{full_path}' in #{openapi_spec}: #{methods.inspect}") if debug
379
+
380
+ # Look for parameters in the array
381
+ param_entry = methods.find { |m| m.is_a?(Hash) && m[:parameters].is_a?(Array) }
382
+ path_parameters = param_entry[:parameters] if param_entry
383
+ warn("[DEBUG] Path-level parameters for #{full_path}: #{path_parameters.inspect}") if debug && !path_parameters.empty?
384
+
385
+ # Collect operations from array elements
386
+ methods.each do |op|
387
+ next unless op.is_a?(Hash)
388
+
389
+ # Infer method from operationId or other indicators
390
+ method_str = if op[:operationId].is_a?(String)
391
+ op_id = op[:operationId].downcase
392
+ valid_methods.find { |m| op_id.start_with?(m) }
393
+ elsif op[:method].is_a?(String) || op[:method].is_a?(Symbol)
394
+ op[:method].to_s.downcase if valid_methods.include?(op[:method].to_s.downcase)
395
+ end
396
+
397
+ if method_str
398
+ operations << { method: method_str, details: op }
399
+ else
400
+ warn("[ERROR] Could not infer valid HTTP method for operation #{op.inspect} in path '#{full_path}' in #{openapi_spec}")
401
+ end
402
+ end
403
+ else
404
+ warn("[ERROR] Invalid methods type '#{methods.class}' for path '#{full_path}' in #{openapi_spec}: Expected Hash or Array, got #{methods.inspect}")
405
+ end
406
+
407
+ # Process each operation
408
+ operations.each do |op|
409
+ method_str = op[:method]
410
+ details = op[:details]
411
+
412
+ # Handle details based on type
413
+ operation = case details
414
+ when Hash
415
+ details
416
+ when Array
417
+ # Find the first hash with responses, or use empty hash
418
+ selected = details.find { |d| d.is_a?(Hash) && d[:responses].is_a?(Hash) }
419
+ if selected
420
+ selected
421
+ else
422
+ warn("[ERROR] No valid operation hash found in array for #{method_str.upcase} #{full_path} in #{openapi_spec}: Got #{details.inspect}")
423
+ {}
424
+ end
425
+ else
426
+ warn("[ERROR] Invalid details type '#{details.class}' for #{method_str.upcase} #{full_path} in #{openapi_spec}: Expected Hash or Array, got #{details.inspect}")
427
+ {}
428
+ end
429
+
430
+ # Skip if operation is empty (indicating invalid details)
431
+ if operation.empty?
432
+ warn("[DEBUG] Skipping #{method_str.upcase} #{full_path} due to invalid operation data") if debug
433
+ next
434
+ end
435
+
436
+ # Skip if no valid responses
437
+ unless operation[:responses].is_a?(Hash)
438
+ warn("[ERROR] No valid responses for #{method_str.upcase} #{full_path} in #{openapi_spec}: Expected Hash, got #{operation[:responses].inspect}")
439
+ next
440
+ end
441
+
442
+ begin
443
+ # Combine path-level and operation-level parameters
444
+ operation_parameters = operation[:parameters].is_a?(Array) ? operation[:parameters] : []
445
+ all_parameters = path_parameters + operation_parameters
446
+ warn("[DEBUG] All parameters for #{method_str.upcase} #{full_path}: #{all_parameters.inspect}") if debug && !all_parameters.empty?
447
+
448
+ # Process path parameters for substitution
449
+ request_path = full_path.dup
450
+ query_params = []
451
+
452
+ all_parameters.each do |param|
453
+ next unless param.is_a?(Hash) && param[:name] && param[:in]
454
+
455
+ param_name = param[:name].to_s
456
+ case param[:in]
457
+ when 'path'
458
+ # Substitute path parameter with a default value (e.g., 'example')
459
+ param_value = param[:schema]&.dig(:example) || 'example'
460
+ request_path.gsub!("{#{param_name}}", param_value.to_s)
461
+ when 'query'
462
+ # Collect query parameters
463
+ param_value = param[:schema]&.dig(:example) || 'example'
464
+ query_params << "#{URI.encode_www_form_component(param_name)}=#{URI.encode_www_form_component(param_value.to_s)}"
465
+ end
466
+ end
467
+
468
+ # Append query parameters to path if any
469
+ request_path += "?#{query_params.join('&')}" if query_params.any?
470
+
471
+ # Construct HTTP request headers
472
+ request_headers = {
473
+ host: host
474
+ }
475
+ request_headers.merge!(additional_http_headers)
476
+
477
+ # Construct request lines, including all headers
478
+ request_lines = [
479
+ "#{method_str.upcase} #{request_path} HTTP/1.1"
480
+ ]
481
+ request_headers.each do |key, value|
482
+ # Capitalize header keys (e.g., 'host' to 'Host', 'authorization' to 'Authorization')
483
+ header_key = key.to_s.split('-').map(&:capitalize).join('-')
484
+ request_lines << "#{header_key}: #{value}"
485
+ end
486
+ request_lines << '' << '' # Add blank lines for HTTP request body separation
487
+
488
+ request = request_lines.join("\r\n")
489
+ encoded_request = Base64.strict_encode64(request)
490
+
491
+ # Determine response code from operation[:responses].keys
492
+ fallback_response_code = 200
493
+ response_keys = operation[:responses].keys
494
+ response_code = response_keys.find { |key| key.to_s.to_i.between?(100, 599) }.to_s.to_i
495
+ response_code ||= fallback_response_code
496
+
497
+ response_status = case response_code
498
+ when 200 then '200 OK'
499
+ when 201 then '201 Created'
500
+ when 204 then '204 No Content'
501
+ when 301 then '301 Moved Permanently'
502
+ when 302 then '302 Found'
503
+ when 303 then '303 See Other'
504
+ when 304 then '304 Not Modified'
505
+ when 307 then '307 Temporary Redirect'
506
+ when 308 then '308 Permanent Redirect'
507
+ when 400 then '400 Bad Request'
508
+ when 401 then '401 Unauthorized'
509
+ when 403 then '403 Forbidden'
510
+ when 404 then '404 Not Found'
511
+ when 500 then '500 Internal Server Error'
512
+ when 502 then '502 Bad Gateway'
513
+ when 503 then '503 Service Unavailable'
514
+ when 504 then '504 Gateway Timeout'
515
+ else "#{fallback_response_code} OK"
516
+ end
517
+
518
+ # Construct response body
519
+ response_body = operation[:responses][response_code]&.dig(:description) ||
520
+ "Endpoint #{method_str.upcase} #{request_path} response"
521
+
522
+ # Safely determine Content-Type
523
+ content_type = if operation[:responses][response_code]
524
+ content = operation[:responses][response_code][:content]
525
+ content&.keys&.first || 'text/plain'
526
+ else
527
+ 'text/plain'
528
+ end
529
+
530
+ response_lines = [
531
+ "HTTP/1.1 #{response_status}",
532
+ "Content-Type: #{content_type}",
533
+ "Content-Length: #{response_body.length}",
534
+ '',
535
+ response_body
536
+ ]
537
+ response = response_lines.join("\r\n")
538
+ encoded_response = Base64.strict_encode64(response)
539
+
540
+ # Build the hash for this endpoint
541
+ sitemap_hash = {
542
+ request: encoded_request,
543
+ response: encoded_response,
544
+ highlight: highlight.to_s.upcase,
545
+ comment: comment,
546
+ http_service: {
547
+ host: host,
548
+ port: port,
549
+ protocol: protocol
550
+ }
551
+ }
552
+
553
+ # Add to the results array
554
+ sitemap_arr.push(sitemap_hash)
555
+ warn("[DEBUG] Added sitemap entry for #{method_str.upcase} #{request_path} on #{server_url} with headers #{request_headers.inspect}") if debug
556
+ rescue StandardError => e
557
+ warn("[ERROR] Failed to process #{method_str.upcase} #{full_path} on #{server_url}: #{e.message}")
558
+ warn("[DEBUG] Operation: #{operation.inspect}, Parameters: #{all_parameters.inspect}, Headers: #{request_headers.inspect}") if debug
559
+ end
560
+ end
561
+ end
562
+ rescue URI::InvalidURIError => e
563
+ warn("[ERROR] Invalid server URL '#{server_url}' in #{openapi_spec}: #{e.message}")
564
+ end
565
+ end
566
+
567
+ sitemap_arr.each { |sitemap| add_to_sitemap(burp_obj: burp_obj, sitemap: sitemap) }
568
+
569
+ sitemap_arr
307
570
  rescue StandardError => e
308
571
  stop(burp_obj: burp_obj) unless burp_obj.nil?
309
572
  raise e
@@ -323,18 +586,14 @@ module PWN
323
586
  target_scheme = URI.parse(target_url).scheme
324
587
  target_host = URI.parse(target_url).host
325
588
  target_port = URI.parse(target_url).port.to_i
326
- # if target_scheme == 'http'
327
- # use_https = false
328
- # else
329
- # use_https = true
330
- # end
331
-
332
589
  active_scan_url_arr = []
333
- json_sitemap = get_current_sitemap(burp_obj: burp_obj)
590
+
591
+ json_sitemap = get_sitemap(burp_obj: burp_obj, target_url: target_url)
334
592
  json_sitemap.each do |site|
335
593
  json_req = site[:request]
336
- json_path = json_req[:path]
337
- b64_encoded_req = json_req[:raw]
594
+ b64_decoded_req = Base64.strict_decode64(json_req)
595
+ json_path = b64_decoded_req.split[1].to_s.scrub.strip.chomp
596
+
338
597
  json_http_svc = site[:http_service]
339
598
  json_protocol = json_http_svc[:protocol]
340
599
  json_host = json_http_svc[:host].to_s.scrub.strip.chomp
@@ -347,10 +606,16 @@ module PWN
347
606
  path: json_path
348
607
  )
349
608
 
350
- # TODO: check if the URI is in scope
351
- # next unless uri_in_scope(...)
352
- next unless json_host == target_host && json_port == target_port
609
+ uri_in_scope = in_scope(
610
+ burp_obj: burp_obj,
611
+ uri: json_uri
612
+ )
613
+
614
+ puts "Skipping #{json_uri} - not in scope. Check out #{self}.help >> #add_to_scope method" unless uri_in_scope
615
+ next unless uri_in_scope
353
616
 
617
+ # If the protocol is HTTPS, set use_https to true
618
+ use_https = false
354
619
  use_https = true if json_protocol == 'https'
355
620
 
356
621
  puts "Adding #{json_uri} to Active Scan"
@@ -359,7 +624,7 @@ module PWN
359
624
  host: json_host,
360
625
  port: json_port,
361
626
  use_https: use_https,
362
- request: b64_encoded_req
627
+ request: json_req
363
628
  }.to_json
364
629
  # Kick off an active scan for each given page in the json_sitemap results
365
630
  rest_browser.post("http://#{pwn_burp_api}/scan/active", post_body, content_type: 'application/json')
@@ -367,14 +632,14 @@ module PWN
367
632
 
368
633
  # Wait for scan completion
369
634
  scan_queue = rest_browser.get("http://#{pwn_burp_api}/scan/active")
370
- json_scan_queue = JSON.parse(scan_queue)
635
+ json_scan_queue = JSON.parse(scan_queue, symbolize_names: true)
371
636
  scan_queue_total = json_scan_queue.count
372
637
  json_scan_queue.each do |scan_item|
373
- this_scan_item_id = scan_item['id']
374
- until scan_item['status'] == 'finished'
638
+ this_scan_item_id = scan_item[:id]
639
+ until scan_item[:status] == 'finished'
375
640
  scan_item_resp = rest_browser.get("http://#{pwn_burp_api}/scan/active/#{this_scan_item_id}")
376
- scan_item = JSON.parse(scan_item_resp)
377
- scan_status = scan_item['status']
641
+ scan_item = JSON.parse(scan_item_resp, symbolize_names: true)
642
+ scan_status = scan_item[:status]
378
643
  puts "Target ID ##{this_scan_item_id} of ##{scan_queue_total}| #{scan_status}"
379
644
  sleep 3
380
645
  end
@@ -398,7 +663,7 @@ module PWN
398
663
  pwn_burp_api = burp_obj[:pwn_burp_api]
399
664
 
400
665
  scan_issues = rest_browser.get("http://#{pwn_burp_api}/scanissues")
401
- JSON.parse(scan_issues)
666
+ JSON.parse(scan_issues, symbolize_names: true)
402
667
  rescue StandardError => e
403
668
  stop(burp_obj: burp_obj) unless burp_obj.nil?
404
669
  raise e
@@ -604,45 +869,58 @@ module PWN
604
869
  browser_type: 'optional - defaults to :firefox. See PWN::Plugins::TransparentBrowser.help for a list of types'
605
870
  )
606
871
 
607
- uri_in_scope_bool = #{self}.uri_in_scope(
608
- target_config: 'required - path to burp suite pro target config JSON file',
872
+ uri_in_scope = #{self}.in_scope(
873
+ burp_obj: 'required - burp_obj returned by #start method',
609
874
  uri: 'required - URI to determine if in scope'
610
875
  )
611
876
 
612
- #{self}.enable_proxy(
613
- burp_obj: 'required - burp_obj returned by #start method'
877
+ json_in_scope = #{self}.add_to_scope(
878
+ burp_obj: 'required - burp_obj returned by #start method',
879
+ target_url: 'required - target url to add to scope'
614
880
  )
615
881
 
616
- #{self}.disable_proxy(
882
+ #{self}.enable_proxy(
617
883
  burp_obj: 'required - burp_obj returned by #start method'
618
884
  )
619
885
 
620
- json_sitemap = #{self}.get_current_sitemap(
886
+ #{self}.disable_proxy(
621
887
  burp_obj: 'required - burp_obj returned by #start method'
622
888
  )
623
889
 
624
- json_proxy_listeners = #{self}.get_proxy_listeners(
625
- burp_obj: 'required - burp_obj returned by #start method'
890
+ json_sitemap = #{self}.get_sitemap(
891
+ burp_obj: 'required - burp_obj returned by #start method',
892
+ target_url: 'optional - target URL to filter sitemap results (defaults to entire sitemap)'
626
893
  )
627
894
 
628
- json_proxy_listener = #{self}.add_proxy_listener(
895
+ json_sitemap = #{self}.add_to_sitemap(
629
896
  burp_obj: 'required - burp_obj returned by #start method',
630
- bind_address: 'required - bind address for the proxy listener (e.g., \"127.0.0.1\")',
631
- port: 'required - port for the proxy listener (e.g., 8081)',
632
- enabled: 'optional - enable the listener (defaults to true)'
897
+ sitemap: 'required - sitemap hash to add',
898
+ debug: 'optional - boolean to enable sitemap debugging (default: false)'
633
899
  )
634
900
 
635
- json_proxy_listener = #{self}.update_proxy_listener(
901
+ Example:
902
+ json_sitemap = #{self}.add_to_sitemap(
636
903
  burp_obj: 'required - burp_obj returned by #start method',
637
- id: 'required - ID of the proxy listener (e.g., \"127.0.0.1:8080\")',
638
- bind_address: 'required - new bind address for the proxy listener',
639
- port: 'required - new port for the proxy listener',
640
- enabled: 'optional - enable or disable the listener (defaults to true)'
904
+ sitemap: {
905
+ request: 'base64_encoded_request_string',
906
+ response: 'base64_encoded_response_string',
907
+ highlight: 'NONE'||'RED'||'ORANGE'||'YELLOW'||'GREEN'||'CYAN'||'BLUE'||'PINK'||'MAGENTA'||'GRAY',
908
+ comment: 'optional comment for the sitemap entry',
909
+ http_service: {
910
+ host: 'example.com',
911
+ port: 80,
912
+ protocol: 'http'
913
+ }
914
+ }
641
915
  )
642
916
 
643
- #{self}.delete_proxy_listener(
917
+ json_sitemap = #{self}.import_openapi_to_sitemap(
644
918
  burp_obj: 'required - burp_obj returned by #start method',
645
- id: 'required - ID of the proxy listener (e.g., \"127.0.0.1:8080\")'
919
+ openapi_spec: 'required - path to OpenAPI JSON specification file',
920
+ additional_http_headers: 'optional - hash of additional HTTP headers to include in requests (default: {})',
921
+ debug: 'optional - boolean to enable debug logging (default: false)',
922
+ highlight: 'optional - highlight color for the sitemap entry (default: \"NONE\")',
923
+ comment: 'optional - comment for the sitemap entry (default: \"\")',
646
924
  )
647
925
 
648
926
  active_scan_url_arr = #{self}.invoke_active_scan(