pwn 0.5.372 → 0.5.373
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/.rubocop.yml +4 -4
- data/README.md +3 -3
- data/lib/pwn/plugins/burp_suite.rb +158 -59
- data/lib/pwn/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d53fa832e82071124a716c089d39ecff41d53ea434454c38f26091a2f20e0c0
|
4
|
+
data.tar.gz: 48c9ab88c5b07a6ff83b7f8e6f4598181fc1fd779ae517a034b0c29a85d9700b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d060f1a0ebf1330018ca27bbfce143336958ed2416181b22383438ce8a61739bafe7b725592a78cc6f11ad5ec1e887ec3474ea33f7c94d9e38dee68cb0ff29b2
|
7
|
+
data.tar.gz: eb35a40d3f69cb8ec3ad884d6502371c119094ec5358a0eb4224c193e0d11dc5768486c3019c75f263be0f526c6075d77e150e82404fba1604bbdd3bbf7dbb4d
|
data/.rubocop.yml
CHANGED
@@ -8,19 +8,19 @@ Lint/UselessRescue:
|
|
8
8
|
Metrics/AbcSize:
|
9
9
|
Max: 537.6
|
10
10
|
Metrics/BlockLength:
|
11
|
-
Max:
|
11
|
+
Max: 292
|
12
12
|
Metrics/BlockNesting:
|
13
|
-
Max:
|
13
|
+
Max: 6
|
14
14
|
Metrics/ClassLength:
|
15
15
|
Max: 134
|
16
16
|
Metrics/CyclomaticComplexity:
|
17
|
-
Max:
|
17
|
+
Max: 157
|
18
18
|
Metrics/MethodLength:
|
19
19
|
Max: 485
|
20
20
|
Metrics/ModuleLength:
|
21
21
|
Max: 1000
|
22
22
|
Metrics/PerceivedComplexity:
|
23
|
-
Max:
|
23
|
+
Max: 156
|
24
24
|
Style/HashEachMethods:
|
25
25
|
Enabled: true
|
26
26
|
Style/HashSyntax:
|
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.
|
40
|
+
pwn[v0.5.373]:001 >>> PWN.help
|
41
41
|
```
|
42
42
|
|
43
43
|
[](https://youtu.be/G7iLUY4FzsI)
|
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
|
|
52
52
|
$ gem uninstall --all --executables pwn
|
53
53
|
$ gem install --verbose pwn
|
54
54
|
$ pwn
|
55
|
-
pwn[v0.5.
|
55
|
+
pwn[v0.5.373]: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-3.4.4@pwn
|
|
62
62
|
$ rvmsudo gem uninstall --all --executables pwn
|
63
63
|
$ rvmsudo gem install --verbose pwn
|
64
64
|
$ pwn
|
65
|
-
pwn[v0.5.
|
65
|
+
pwn[v0.5.373]: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:
|
@@ -460,9 +460,14 @@ module PWN
|
|
460
460
|
debug = opts[:debug] || false
|
461
461
|
|
462
462
|
# Parse the OpenAPI JSON or YAML specification file
|
463
|
-
# If the
|
464
|
-
openapi =
|
465
|
-
|
463
|
+
# If the openapi_spec is YAML, convert it to JSON
|
464
|
+
openapi = if openapi_spec.end_with?('.json')
|
465
|
+
JSON.parse(File.read(openapi_spec), symbolize_names: true)
|
466
|
+
elsif openapi_spec.end_with?('.yaml', '.yml')
|
467
|
+
YAML.safe_load_file(openapi_spec, permitted_classes: [Symbol, Date, Time], aliases: true, symbolize_names: true)
|
468
|
+
else
|
469
|
+
raise "ERROR: Unsupported file extension for #{openapi_spec}. Expected .json, .yaml, or .yml."
|
470
|
+
end
|
466
471
|
|
467
472
|
# Initialize result array
|
468
473
|
sitemap_arr = []
|
@@ -477,6 +482,19 @@ module PWN
|
|
477
482
|
# Valid HTTP methods for validation
|
478
483
|
valid_methods = %w[get post put patch delete head options trace connect]
|
479
484
|
|
485
|
+
# Helper lambda to resolve $ref in schemas
|
486
|
+
resolve_ref = lambda do |openapi, ref|
|
487
|
+
return nil unless ref&.start_with?('#/')
|
488
|
+
|
489
|
+
parts = ref.sub('#/', '').split('/')
|
490
|
+
resolved = openapi
|
491
|
+
parts.each do |part|
|
492
|
+
resolved = resolved[part.to_sym]
|
493
|
+
return nil unless resolved
|
494
|
+
end
|
495
|
+
resolved
|
496
|
+
end
|
497
|
+
|
480
498
|
# Iterate through each server
|
481
499
|
servers.each do |server|
|
482
500
|
server_url = server[:url]
|
@@ -609,6 +627,88 @@ module PWN
|
|
609
627
|
all_parameters = path_parameters + operation_parameters
|
610
628
|
warn("[DEBUG] All parameters for #{method_str.upcase} #{full_path}: #{all_parameters.inspect}") if debug && !all_parameters.empty?
|
611
629
|
|
630
|
+
# Determine response code from operation[:responses].keys
|
631
|
+
fallback_response_code = 200
|
632
|
+
response_keys = operation[:responses].keys
|
633
|
+
response_key = response_keys.find { |key| key.to_s.to_i.between?(100, 599) } || fallback_response_code.to_s
|
634
|
+
response_code = response_key.to_s.to_i
|
635
|
+
|
636
|
+
# Construct response body from operation responses schema example, schema $ref example, etc.
|
637
|
+
response_obj = operation[:responses][response_key] || {}
|
638
|
+
content = response_obj[:content] || {}
|
639
|
+
content_type = content.keys.first&.to_s || 'text/plain'
|
640
|
+
|
641
|
+
response_body = ''
|
642
|
+
unless [204, 304].include?(response_code)
|
643
|
+
content_obj = content[content_type.to_sym] || {}
|
644
|
+
example = content_obj[:example]
|
645
|
+
if example.nil? && content_obj[:examples].is_a?(Hash)
|
646
|
+
ex_key = content_obj[:examples].keys.first
|
647
|
+
if ex_key
|
648
|
+
ex = content_obj[:examples][ex_key]
|
649
|
+
if ex[:$ref]
|
650
|
+
resolved_ex = resolve_ref.call(openapi, ex[:$ref])
|
651
|
+
example = resolved_ex[:value] if resolved_ex
|
652
|
+
else
|
653
|
+
example = ex[:value]
|
654
|
+
end
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
if example.nil?
|
659
|
+
schema = content_obj[:schema]
|
660
|
+
if schema
|
661
|
+
if schema[:$ref]
|
662
|
+
ref = schema[:$ref]
|
663
|
+
if ref.start_with?('#/')
|
664
|
+
parts = ref.sub('#/', '').split('/')
|
665
|
+
resolved = openapi
|
666
|
+
parts.each do |part|
|
667
|
+
resolved = resolved[part.to_sym]
|
668
|
+
break unless resolved
|
669
|
+
end
|
670
|
+
schema = resolved if resolved
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
example = schema[:example]
|
675
|
+
if example.nil? && schema[:examples].is_a?(Hash)
|
676
|
+
ex_key = schema[:examples].keys.first
|
677
|
+
if ex_key
|
678
|
+
ex = schema[:examples][ex_key]
|
679
|
+
if ex[:$ref]
|
680
|
+
resolved_ex = resolve_ref.call(openapi, ex[:$ref])
|
681
|
+
example = resolved_ex[:value] if resolved_ex
|
682
|
+
else
|
683
|
+
example = ex[:value]
|
684
|
+
end
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
response_body = example || response_obj[:description] || "INFO: Unable to resolve response body from #{openapi_spec} => { 'http_method': '#{method_str.upcase}', 'path': '#{full_path}', 'response_code': '#{response_code}' }"
|
691
|
+
end
|
692
|
+
|
693
|
+
# Try to extract query samples from response example if it's a links object
|
694
|
+
query_hash = nil
|
695
|
+
if response_body.is_a?(Hash) && response_body[:links]
|
696
|
+
href = response_body.dig(:links, :self, :href)
|
697
|
+
# href ||= response_body[:links].values.first&.dig(:href) rescue nil
|
698
|
+
if href.nil? && response_body[:links].is_a?(Hash) && !response_body[:links].empty?
|
699
|
+
first_value = response_body[:links].values.first
|
700
|
+
href = first_value[:href] if first_value.is_a?(Hash)
|
701
|
+
end
|
702
|
+
if href
|
703
|
+
begin
|
704
|
+
parsed_uri = URI.parse(href)
|
705
|
+
query_hash = URI.decode_www_form(parsed_uri.query).to_h if parsed_uri.path.end_with?(path_str) && parsed_uri.query
|
706
|
+
rescue URI::InvalidURIError => e
|
707
|
+
warn("[DEBUG] Invalid href in response example: #{href} - #{e.message}") if debug
|
708
|
+
end
|
709
|
+
end
|
710
|
+
end
|
711
|
+
|
612
712
|
# Process path parameters for substitution
|
613
713
|
request_path = full_path.dup
|
614
714
|
query_params = []
|
@@ -617,21 +717,67 @@ module PWN
|
|
617
717
|
next unless param.is_a?(Hash) && param[:name] && param[:in]
|
618
718
|
|
619
719
|
param_name = param[:name].to_s
|
720
|
+
|
721
|
+
# Get param_value with precedence: param.examples > param.example > schema.examples > schema.example > 'FUZZ'
|
722
|
+
param_value = if param[:examples].is_a?(Hash) && !param[:examples].empty?
|
723
|
+
first_ex = param[:examples].values.first
|
724
|
+
if first_ex.is_a?(Hash)
|
725
|
+
if first_ex[:$ref]
|
726
|
+
# Resolve $ref for example if present
|
727
|
+
resolved_ex = resolve_ref.call(openapi, first_ex[:$ref])
|
728
|
+
resolved_ex[:value] if resolved_ex
|
729
|
+
else
|
730
|
+
first_ex[:value]
|
731
|
+
end
|
732
|
+
else
|
733
|
+
first_ex
|
734
|
+
end || 'FUZZ'
|
735
|
+
elsif param.key?(:example)
|
736
|
+
param[:example]
|
737
|
+
else
|
738
|
+
schema = param[:schema]
|
739
|
+
if schema
|
740
|
+
if schema[:$ref]
|
741
|
+
resolved_schema = resolve_ref.call(openapi, schema[:$ref])
|
742
|
+
schema = resolved_schema if resolved_schema
|
743
|
+
end
|
744
|
+
if schema[:examples].is_a?(Hash) && !schema[:examples].empty?
|
745
|
+
first_ex = schema[:examples].values.first
|
746
|
+
if first_ex.is_a?(Hash)
|
747
|
+
if first_ex[:$ref]
|
748
|
+
resolved_ex = resolve_ref.call(openapi, first_ex[:$ref])
|
749
|
+
resolved_ex[:value] if resolved_ex
|
750
|
+
else
|
751
|
+
first_ex[:value]
|
752
|
+
end
|
753
|
+
else
|
754
|
+
first_ex
|
755
|
+
end || 'FUZZ'
|
756
|
+
elsif schema.key?(:example)
|
757
|
+
schema[:example]
|
758
|
+
else
|
759
|
+
'FUZZ'
|
760
|
+
end
|
761
|
+
else
|
762
|
+
'FUZZ'
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
# If still 'FUZZ' and it's a query param, try to get from response example query_hash
|
767
|
+
param_value = query_hash[param_name] if param_value == 'FUZZ' && param[:in] == 'query' && query_hash&.key?(param_name)
|
768
|
+
|
620
769
|
case param[:in]
|
621
770
|
when 'header'
|
622
771
|
# Aggregate remaining HTTP header names from spec,
|
623
772
|
# reference as keys, and assign their respective
|
624
773
|
# values to the request_headers hash
|
625
774
|
param_key = param_name.downcase
|
626
|
-
param_value = param[:schema]&.dig(:example) || 'FUZZ'
|
627
775
|
request_headers[param_key] = param_value.to_s
|
628
776
|
when 'path'
|
629
|
-
# Substitute path parameter with
|
630
|
-
param_value = param[:schema]&.dig(:example) || 'FUZZ'
|
777
|
+
# Substitute path parameter with the resolved value
|
631
778
|
request_path.gsub!("{#{param_name}}", param_value.to_s)
|
632
779
|
when 'query'
|
633
780
|
# Collect query parameters
|
634
|
-
param_value = param[:schema]&.dig(:example) || 'FUZZ'
|
635
781
|
query_params.push("#{URI.encode_www_form_component(param_name)}=#{URI.encode_www_form_component(param_value.to_s)}")
|
636
782
|
end
|
637
783
|
end
|
@@ -653,12 +799,6 @@ module PWN
|
|
653
799
|
request = request_lines.join("\r\n")
|
654
800
|
encoded_request = Base64.strict_encode64(request)
|
655
801
|
|
656
|
-
# Determine response code from operation[:responses].keys
|
657
|
-
fallback_response_code = 200
|
658
|
-
response_keys = operation[:responses].keys
|
659
|
-
response_key = response_keys.find { |key| key.to_s.to_i.between?(100, 599) } || fallback_response_code.to_s
|
660
|
-
response_code = response_key.to_s.to_i
|
661
|
-
|
662
802
|
response_status = case response_code
|
663
803
|
when 200 then '200 OK'
|
664
804
|
when 201 then '201 Created'
|
@@ -680,52 +820,11 @@ module PWN
|
|
680
820
|
else "#{fallback_response_code} OK"
|
681
821
|
end
|
682
822
|
|
683
|
-
#
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
response_body = ''
|
689
|
-
unless [204, 304].include?(response_code)
|
690
|
-
content_obj = content[content_type.to_sym] || {}
|
691
|
-
example = content_obj[:example]
|
692
|
-
if example.nil? && content_obj[:examples].is_a?(Hash)
|
693
|
-
ex_key = content_obj[:examples].keys.first
|
694
|
-
example = content_obj[:examples][ex_key][:value] if ex_key
|
695
|
-
end
|
696
|
-
|
697
|
-
if example.nil?
|
698
|
-
schema = content_obj[:schema]
|
699
|
-
if schema
|
700
|
-
if schema[:$ref]
|
701
|
-
ref = schema[:$ref]
|
702
|
-
if ref.start_with?('#/')
|
703
|
-
parts = ref.sub('#/', '').split('/')
|
704
|
-
resolved = openapi
|
705
|
-
parts.each do |part|
|
706
|
-
resolved = resolved[part.to_sym]
|
707
|
-
break unless resolved
|
708
|
-
end
|
709
|
-
schema = resolved if resolved
|
710
|
-
end
|
711
|
-
end
|
712
|
-
|
713
|
-
example = schema[:example]
|
714
|
-
if example.nil? && schema[:examples].is_a?(Hash)
|
715
|
-
ex_key = schema[:examples].keys.first
|
716
|
-
example = schema[:examples][ex_key][:value] if ex_key
|
717
|
-
end
|
718
|
-
end
|
719
|
-
end
|
720
|
-
|
721
|
-
response_body = example || response_obj[:description] || "INFO: Unable to resolve response body from #{openapi_spec} => { 'http_method': '#{method_str.upcase}', 'path': '#{request_path}', 'response_code': '#{response_code}' }"
|
722
|
-
|
723
|
-
# Serialize based on content_type
|
724
|
-
if content_type =~ /json/i && (response_body.is_a?(Hash) || response_body.is_a?(Array))
|
725
|
-
response_body = JSON.generate(response_body)
|
726
|
-
else
|
727
|
-
response_body = response_body.to_s
|
728
|
-
end
|
823
|
+
# Serialize response_body based on content_type
|
824
|
+
if content_type =~ /json/i && (response_body.is_a?(Hash) || response_body.is_a?(Array))
|
825
|
+
response_body = JSON.generate(response_body)
|
826
|
+
else
|
827
|
+
response_body = response_body.to_s
|
729
828
|
end
|
730
829
|
|
731
830
|
response_lines = [
|
data/lib/pwn/version.rb
CHANGED