pwn 0.5.164 → 0.5.165

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/README.md +3 -3
  4. data/bin/pwn +1 -1
  5. data/bin/pwn_android_war_dialer +2 -2
  6. data/bin/pwn_aws_describe_resources +2 -2
  7. data/bin/pwn_bdba_groups +2 -2
  8. data/bin/pwn_bdba_scan +2 -2
  9. data/bin/pwn_burp_suite_pro_active_scan +2 -2
  10. data/bin/pwn_char_base64_encoding +2 -2
  11. data/bin/pwn_char_dec_encoding +2 -2
  12. data/bin/pwn_char_hex_escaped_encoding +2 -2
  13. data/bin/pwn_char_html_entity_encoding +2 -2
  14. data/bin/pwn_char_unicode_escaped_encoding +2 -2
  15. data/bin/pwn_char_url_encoding +2 -2
  16. data/bin/pwn_crt_sh +82 -0
  17. data/bin/pwn_defectdojo_engagement_create +2 -2
  18. data/bin/pwn_defectdojo_importscan +2 -2
  19. data/bin/pwn_defectdojo_reimportscan +2 -2
  20. data/bin/pwn_diff_csv_files_w_column_exclude +4 -4
  21. data/bin/pwn_domain_reversewhois +2 -2
  22. data/bin/pwn_fuzz_net_app_proto +3 -3
  23. data/bin/pwn_gqrx_scanner +2 -2
  24. data/bin/pwn_jenkins_create_job +2 -2
  25. data/bin/pwn_jenkins_create_view +2 -2
  26. data/bin/pwn_jenkins_install_plugin +2 -2
  27. data/bin/pwn_jenkins_thinBackup_aws_s3 +2 -2
  28. data/bin/pwn_jenkins_update_plugins +2 -2
  29. data/bin/pwn_jenkins_useradd +2 -2
  30. data/bin/pwn_mail_agent +2 -2
  31. data/bin/pwn_nessus_cloud_scan_crud +2 -2
  32. data/bin/pwn_nessus_cloud_vulnscan +2 -2
  33. data/bin/pwn_nexpose +2 -2
  34. data/bin/pwn_nmap_discover_tcp_udp +2 -2
  35. data/bin/pwn_openvas_vulnscan +2 -2
  36. data/bin/pwn_owasp_zap_active_scan +2 -2
  37. data/bin/pwn_pastebin_sample_filter +2 -2
  38. data/bin/pwn_phone +3 -3
  39. data/bin/pwn_sast +3 -3
  40. data/bin/pwn_serial_check_voicemail +2 -2
  41. data/bin/pwn_serial_msr206 +2 -2
  42. data/bin/pwn_serial_son_micro_sm132_rfid +2 -2
  43. data/bin/pwn_shodan_graphql_introspection +1 -1
  44. data/bin/pwn_shodan_search +2 -2
  45. data/bin/pwn_simple_http_server +1 -1
  46. data/bin/pwn_web_cache_deception +4 -4
  47. data/bin/pwn_www_checkip +3 -1
  48. data/bin/pwn_www_uri_buster +141 -57
  49. data/bin/pwn_xss_dom_vectors +2 -2
  50. data/lib/pwn/plugins/authentication_helper.rb +2 -2
  51. data/lib/pwn/plugins/ip_info.rb +2 -0
  52. data/lib/pwn/plugins/log.rb +3 -1
  53. data/lib/pwn/plugins/repl.rb +3 -1
  54. data/lib/pwn/plugins/thread_pool.rb +12 -42
  55. data/lib/pwn/plugins/tor.rb +51 -12
  56. data/lib/pwn/plugins/transparent_browser.rb +3 -0
  57. data/lib/pwn/plugins/voice.rb +3 -3
  58. data/lib/pwn/reports/uri_buster.rb +18 -6
  59. data/lib/pwn/version.rb +1 -1
  60. metadata +8 -6
@@ -8,7 +8,7 @@ require 'yaml'
8
8
  opts = {}
9
9
  OptionParser.new do |options|
10
10
  options.banner = "USAGE:
11
- #{$PROGRAM_NAME} [opts]
11
+ #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
 
14
14
  options.on('-cYPATH', '--yaml-config=YPATH', '<Required - YAML Config Containing Access & Secret Keys for Authentication>') do |c|
@@ -29,7 +29,7 @@ OptionParser.new do |options|
29
29
  end.parse!
30
30
 
31
31
  if opts.empty?
32
- puts `#{$PROGRAM_NAME} --help`
32
+ puts `#{File.basename($PROGRAM_NAME)} --help`
33
33
  exit 1
34
34
  end
35
35
 
data/bin/pwn_nexpose CHANGED
@@ -7,7 +7,7 @@ require 'optparse'
7
7
  opts = {}
8
8
  OptionParser.new do |options|
9
9
  options.banner = "USAGE:
10
- #{$PROGRAM_NAME} [opts]
10
+ #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
13
  # Nexpose Flags
@@ -19,7 +19,7 @@ OptionParser.new do |options|
19
19
  end.parse!
20
20
 
21
21
  if opts.empty?
22
- puts `#{$PROGRAM_NAME} --help`
22
+ puts `#{File.basename($PROGRAM_NAME)} --help`
23
23
  exit 1
24
24
  end
25
25
 
@@ -9,7 +9,7 @@ require 'time'
9
9
  opts = {}
10
10
  OptionParser.new do |options|
11
11
  options.banner = "USAGE:
12
- #{$PROGRAM_NAME} [opts]
12
+ #{File.basename($PROGRAM_NAME)} [opts]
13
13
  "
14
14
 
15
15
  options.on('-fFILE', '--target-file=FILE', '<Required if --target-range excluded - File containing one supported nmap target / line e.g. foo.bar, 10.1.1.1, 192.168.1.1-20, 192.168.1.0/24, etc>') do |f|
@@ -42,7 +42,7 @@ OptionParser.new do |options|
42
42
  end.parse!
43
43
 
44
44
  if opts.empty?
45
- puts `#{$PROGRAM_NAME} --help`
45
+ puts `#{File.basename($PROGRAM_NAME)} --help`
46
46
  exit 1
47
47
  end
48
48
 
@@ -8,7 +8,7 @@ require 'yaml'
8
8
  opts = {}
9
9
  OptionParser.new do |options|
10
10
  options.banner = "USAGE:
11
- #{$PROGRAM_NAME} [opts]
11
+ #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
 
14
14
  options.on('-cYPATH', '--yaml-config=YPATH', '<Required - YAML Config Containing Username & Password for Authentication>') do |c|
@@ -29,7 +29,7 @@ OptionParser.new do |options|
29
29
  end.parse!
30
30
 
31
31
  if opts.empty?
32
- puts `#{$PROGRAM_NAME} --help`
32
+ puts `#{File.basename($PROGRAM_NAME)} --help`
33
33
  exit 1
34
34
  end
35
35
 
@@ -7,7 +7,7 @@ require 'optparse'
7
7
  opts = {}
8
8
  OptionParser.new do |options|
9
9
  options.banner = "USAGE:
10
- #{$PROGRAM_NAME} [opts]
10
+ #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
13
  options.on('-aAPIKEY', '--api_key=APIKEY', '<Required - OWASP Zap API Key (Tools>Options>API)>') do |a|
@@ -44,7 +44,7 @@ OptionParser.new do |options|
44
44
  end.parse!
45
45
 
46
46
  if opts.empty?
47
- puts `#{$PROGRAM_NAME} --help`
47
+ puts `#{File.basename($PROGRAM_NAME)} --help`
48
48
  exit 1
49
49
  end
50
50
 
@@ -7,7 +7,7 @@ require 'optparse'
7
7
  opts = {}
8
8
  OptionParser.new do |options|
9
9
  options.banner = "USAGE:
10
- #{$PROGRAM_NAME} [opts]
10
+ #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
13
  options.on('-rPATTERN', '--regex=PATTERN', "<Required - Regex Pattern for Interesting Pastes ('.*' for All)>") do |r|
@@ -20,7 +20,7 @@ OptionParser.new do |options|
20
20
  end.parse!
21
21
 
22
22
  if opts.empty?
23
- puts `#{$PROGRAM_NAME} --help`
23
+ puts `#{File.basename($PROGRAM_NAME)} --help`
24
24
  exit 1
25
25
  end
26
26
 
data/bin/pwn_phone CHANGED
@@ -11,7 +11,7 @@ require 'waveform'
11
11
  opts = {}
12
12
  OptionParser.new do |options|
13
13
  options.banner = "USAGE:
14
- #{$PROGRAM_NAME} [opts]
14
+ #{File.basename($PROGRAM_NAME)} [opts]
15
15
  "
16
16
 
17
17
  options.on('-tPATH', '--target-file=PATH', '<Required - File Containing List of Targets to Dial>') do |t|
@@ -64,7 +64,7 @@ OptionParser.new do |options|
64
64
  end.parse!
65
65
 
66
66
  if opts.empty?
67
- puts `#{$PROGRAM_NAME} --help`
67
+ puts `#{File.basename($PROGRAM_NAME)} --help`
68
68
  exit 1
69
69
  end
70
70
 
@@ -105,7 +105,7 @@ begin
105
105
  )
106
106
 
107
107
  # Generate HTML Report
108
- print "#{$PROGRAM_NAME} Generating Report..."
108
+ print "#{File.basename($PROGRAM_NAME)} Generating Report..."
109
109
  PWN::Reports::Phone.generate(
110
110
  dir_path: session_root,
111
111
  results_hash: results_hash
data/bin/pwn_sast CHANGED
@@ -8,7 +8,7 @@ require 'htmlentities'
8
8
  opts = {}
9
9
  OptionParser.new do |options|
10
10
  options.banner = "USAGE:
11
- #{$PROGRAM_NAME} [opts]
11
+ #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
 
14
14
  options.on('-uGITURI', '--uri-source-root=GITURI', '<Required - HTTP URI of Git Repo Scanned e.g. https://github.com/0dayInc/pwn/tree/master>') do |u|
@@ -41,7 +41,7 @@ OptionParser.new do |options|
41
41
  end.parse!
42
42
 
43
43
  if opts.empty?
44
- puts `#{$PROGRAM_NAME} --help`
44
+ puts `#{File.basename($PROGRAM_NAME)} --help`
45
45
  exit 1
46
46
  end
47
47
 
@@ -155,7 +155,7 @@ begin
155
155
  end
156
156
 
157
157
  # Generate HTML Report
158
- print "#{$PROGRAM_NAME} Generating Report..."
158
+ print "#{File.basename($PROGRAM_NAME)} Generating Report..."
159
159
  PWN::Reports::SAST.generate(
160
160
  dir_path: dir_path,
161
161
  results_hash: results_hash
@@ -7,7 +7,7 @@ require 'optparse'
7
7
  opts = {}
8
8
  OptionParser.new do |options|
9
9
  options.banner = "USAGE:
10
- #{$PROGRAM_NAME} [opts]
10
+ #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
13
  options.on('-vNUM', '--voicemail_num=num', '<Required - Number to Voicemail Service>') do |num|
@@ -19,7 +19,7 @@ OptionParser.new do |options|
19
19
  end.parse!
20
20
 
21
21
  if opts.empty?
22
- puts `#{$PROGRAM_NAME} --help`
22
+ puts `#{File.basename($PROGRAM_NAME)} --help`
23
23
  exit 1
24
24
  end
25
25
 
@@ -8,7 +8,7 @@ require 'json'
8
8
  opts = {}
9
9
  OptionParser.new do |options|
10
10
  options.banner = "USAGE:
11
- #{$PROGRAM_NAME} [opts]
11
+ #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
 
14
14
  options.on('-dDEV', '--block-dev=DEV', '<Optional - MSR206 block device path (defaults to /dev/ttyUSB0)>') do |d|
@@ -37,7 +37,7 @@ OptionParser.new do |options|
37
37
  end.parse!
38
38
 
39
39
  if opts.empty?
40
- puts `#{$PROGRAM_NAME} --help`
40
+ puts `#{File.basename($PROGRAM_NAME)} --help`
41
41
  exit 1
42
42
  end
43
43
 
@@ -8,7 +8,7 @@ require 'json'
8
8
  opts = {}
9
9
  OptionParser.new do |options|
10
10
  options.banner = "USAGE:
11
- #{$PROGRAM_NAME} [opts]
11
+ #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
 
14
14
  options.on('-dDEV', '--block-dev=DEV', '<Optional - SonMicroRFID block device path (defaults to /dev/ttyUSB0)>') do |d|
@@ -37,7 +37,7 @@ OptionParser.new do |options|
37
37
  end.parse!
38
38
 
39
39
  if opts.empty?
40
- puts `#{$PROGRAM_NAME} --help`
40
+ puts `#{File.basename($PROGRAM_NAME)} --help`
41
41
  exit 1
42
42
  end
43
43
 
@@ -14,7 +14,7 @@ OptionParser.new do |options|
14
14
  end.parse!
15
15
 
16
16
  if opts.empty?
17
- puts `#{$PROGRAM_NAME} --help`
17
+ puts `#{File.basename($PROGRAM_NAME)} --help`
18
18
  exit 1
19
19
  end
20
20
 
@@ -9,7 +9,7 @@ require 'json'
9
9
  opts = {}
10
10
  OptionParser.new do |options|
11
11
  options.banner = "USAGE:
12
- #{$PROGRAM_NAME} [opts]
12
+ #{File.basename($PROGRAM_NAME)} [opts]
13
13
  "
14
14
 
15
15
  options.on('-cYAML', '--config-yaml=YAML', '<Required - YAML config containing api_key from Shodan.io>') do |y|
@@ -30,7 +30,7 @@ OptionParser.new do |options|
30
30
  end.parse!
31
31
 
32
32
  if opts.empty?
33
- puts `#{$PROGRAM_NAME} --help`
33
+ puts `#{File.basename($PROGRAM_NAME)} --help`
34
34
  exit 1
35
35
  end
36
36
 
@@ -6,7 +6,7 @@ require 'optparse'
6
6
  opts = {}
7
7
  OptionParser.new do |options|
8
8
  options.banner = "USAGE:
9
- #{$PROGRAM_NAME} [opts]
9
+ #{File.basename($PROGRAM_NAME)} [opts]
10
10
  "
11
11
  options.on('-iIP', '--bind-ip=IP', '<Optional - HTTP Bind IP to Listen> (Defaults to Localhost)') do |i|
12
12
  opts[:bind_ip] = i
@@ -7,7 +7,7 @@ require 'optparse'
7
7
  opts = {}
8
8
  OptionParser.new do |options|
9
9
  options.banner = "USAGE:
10
- #{$PROGRAM_NAME} [opts]
10
+ #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
13
  options.on('-tURL', '--target-url=URL', '<Required - URL to Target>') do |t|
@@ -44,7 +44,7 @@ OptionParser.new do |options|
44
44
  end.parse!
45
45
 
46
46
  if opts.empty?
47
- puts `#{$PROGRAM_NAME} --help`
47
+ puts `#{File.basename($PROGRAM_NAME)} --help`
48
48
  exit 1
49
49
  end
50
50
 
@@ -142,12 +142,12 @@ begin
142
142
  pwn_www_mod = PWN::WWW.const_get(pwn_www_mod_str)
143
143
  unless pwn_www_mod.respond_to?('open') && pwn_www_mod.respond_to?('login') && pwn_www_mod.respond_to?('logout')
144
144
  puts "#{@dark_red}Module PWN::WWW::#{pwn_www_mod_str} Missing #open #login and/or #logout Method(s)#{@end_of_color}"
145
- puts `#{$PROGRAM_NAME} -l`
145
+ puts `#{File.basename($PROGRAM_NAME)} -l`
146
146
  exit
147
147
  end
148
148
  rescue NAME_ERROR => e
149
149
  puts "#{@dark_red}Invalid module PWN::WWW::#{pwn_www_mod_str}#{@end_of_color}"
150
- puts `#{$PROGRAM_NAME} -l`
150
+ puts `#{File.basename($PROGRAM_NAME)} -l`
151
151
  exit
152
152
  end
153
153
 
data/bin/pwn_www_checkip CHANGED
@@ -8,7 +8,7 @@ require 'optparse'
8
8
  opts = {}
9
9
  OptionParser.new do |options|
10
10
  options.banner = "USAGE:
11
- #{$PROGRAM_NAME} [opts]
11
+ #{File.basename($PROGRAM_NAME)} [opts]
12
12
  "
13
13
  options.on('-tIP_HOST', '--target=IP_HOST', '<Optional - IP or Host to Check (Default - Your Public IP)>') do |t|
14
14
  opts[:target] = t
@@ -64,6 +64,8 @@ begin
64
64
  )
65
65
  puts JSON.pretty_generate(ip_info_obj)
66
66
  end
67
+ rescue Interrupt
68
+ puts "\n#{File.basename($PROGRAM_NAME)} => Goodbye."
67
69
  rescue StandardError => e
68
70
  raise e
69
71
  ensure
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: false
3
3
 
4
+ require 'time'
4
5
  require 'pwn'
5
6
  require 'optparse'
6
7
  require 'uri'
@@ -12,7 +13,7 @@ require 'json'
12
13
  opts = {}
13
14
  OptionParser.new do |options|
14
15
  options.banner = "USAGE:
15
- #{$PROGRAM_NAME} [opts]
16
+ #{File.basename($PROGRAM_NAME)} [opts]
16
17
  "
17
18
 
18
19
  options.on('-uURL', '--target-url=URL', '<Required - Target URL)>') do |t|
@@ -27,6 +28,14 @@ OptionParser.new do |options|
27
28
  opts[:append_pattern] = a
28
29
  end
29
30
 
31
+ options.on('-TFLOAT', '--max-timeout=FLOAT', '<Optional - HTTP Request Timeout (Default 6.0 Seconds)>') do |t|
32
+ opts[:max_timeout] = t
33
+ end
34
+
35
+ options.on('-TFLOAT', '--max-retries=FLOAT', '<Optional - HTTP Request Retries (Default 3)>') do |r|
36
+ opts[:max_retries] = r
37
+ end
38
+
30
39
  options.on('-pPROXY', '--proxy=PROXY', '<Optional - Proxy SCHEME://ADDRESS:PORT || tor>') do |p|
31
40
  opts[:proxy] = p
32
41
  end
@@ -61,7 +70,7 @@ OptionParser.new do |options|
61
70
  end.parse!
62
71
 
63
72
  if opts.empty?
64
- puts `#{$PROGRAM_NAME} --help`
73
+ puts `#{File.basename($PROGRAM_NAME)} --help`
65
74
  exit 1
66
75
  end
67
76
 
@@ -71,43 +80,60 @@ def request_path(opts = {})
71
80
  http_request_headers = opts[:http_request_headers]
72
81
  wordlist_line = opts[:wordlist_line]
73
82
  http_method = opts[:http_method]
83
+ max_timeout = opts[:max_timeout]
84
+ timeout = opts[:max_timeout] ||= max_timeout / 21.0
85
+ max_retries = opts[:max_retries]
74
86
 
75
87
  rest_client_resp_hash = {}
76
- begin
77
- print '.'
78
- http_uri = "#{target_url}/#{wordlist_line}"
79
- if proxy
80
- browser_obj = PWN::Plugins::TransparentBrowser.open(
81
- browser_type: :rest,
82
- proxy: proxy
83
- )
84
- else
85
- browser_obj = PWN::Plugins::TransparentBrowser.open(
86
- browser_type: :rest
87
- )
88
- end
89
- rest_client = browser_obj[:browser]::Request
90
88
 
91
- headers = {
92
- user_agent: Faker::Internet.user_agent
93
- }
89
+ # request_count && timeout values to begin with
90
+ request_count = 1
94
91
 
95
- if http_request_headers
96
- headers = JSON.parse(
97
- http_request_headers,
98
- symbolize_names: true
99
- )
100
- end
92
+ if proxy
93
+ browser_obj = PWN::Plugins::TransparentBrowser.open(
94
+ browser_type: :rest,
95
+ proxy: proxy
96
+ )
97
+ else
98
+ browser_obj = PWN::Plugins::TransparentBrowser.open(
99
+ browser_type: :rest
100
+ )
101
+ end
102
+ rest_client = browser_obj[:browser]::Request
103
+
104
+ headers = {
105
+ user_agent: Faker::Internet.user_agent
106
+ }
107
+
108
+ http_uri = "#{target_url}/#{wordlist_line}"
109
+
110
+ if http_request_headers
111
+ headers = JSON.parse(
112
+ http_request_headers,
113
+ symbolize_names: true
114
+ )
115
+ end
116
+
117
+ timestamp_fmt = '%Y-%m-%d %H:%M:%S.%L'
101
118
 
119
+ begin
120
+ request_timestamp = Time.now.strftime(timestamp_fmt)
102
121
  response = rest_client.execute(
103
122
  method: http_method,
104
123
  url: http_uri,
105
124
  headers: headers,
106
- verify_ssl: false
125
+ verify_ssl: false,
126
+ timeout: timeout
107
127
  )
128
+ response_timestamp = Time.now.strftime(timestamp_fmt)
129
+
130
+ # duration in milliseconds
131
+ duration = Time.parse(response_timestamp) - Time.parse(request_timestamp)
108
132
 
109
133
  rest_client_resp_hash = {
110
- request_timestamp: Time.now.strftime('%Y-%m-%d_%H-%M-%S'),
134
+ request_timestamp: request_timestamp,
135
+ response_timestamp: response_timestamp,
136
+ duration: duration,
111
137
  http_uri: http_uri,
112
138
  http_method: http_method,
113
139
  http_resp_code: response.code,
@@ -115,9 +141,8 @@ def request_path(opts = {})
115
141
  http_resp_headers: JSON.pretty_generate(response.headers),
116
142
  http_resp: "#{response.body[0..300]}..."
117
143
  }
118
- rescue Errno::ECONNREFUSED
119
- raise 'ERROR: Connection(s) Refused. Try lowering the --max-threads value.'
120
- rescue Errno::ECONNRESET,
144
+ rescue Errno::ECONNREFUSED,
145
+ Errno::ECONNRESET,
121
146
  NoMethodError,
122
147
  OpenSSL::SSL::SSLError,
123
148
  RestClient::Exceptions::ReadTimeout,
@@ -125,9 +150,16 @@ def request_path(opts = {})
125
150
  RestClient::ServerBrokeConnection,
126
151
  SOCKSError => e
127
152
 
153
+ timeout += 0.1
154
+ retry if timeout < max_timeout
155
+
128
156
  # May be best to switch Tor channel if SOCKSError is rescued
157
+ response_timestamp = Time.now.strftime(timestamp_fmt)
158
+ duration = Time.parse(response_timestamp) - Time.parse(request_timestamp)
129
159
  rest_client_resp_hash = {
130
- request_timestamp: Time.now.strftime('%Y-%m-%d_%H-%M-%S'),
160
+ request_timestamp: request_timestamp,
161
+ response_timestamp: response_timestamp,
162
+ duration: duration,
131
163
  http_uri: http_uri,
132
164
  http_method: http_method,
133
165
  http_resp_code: e.class,
@@ -136,9 +168,14 @@ def request_path(opts = {})
136
168
  http_resp: "ERROR: #{e.message}"
137
169
  }
138
170
  rescue RestClient::ExceptionWithResponse => e
171
+ response_timestamp = Time.now.strftime(timestamp_fmt)
172
+ duration = Time.parse(response_timestamp) - Time.parse(request_timestamp)
173
+
139
174
  if e.respond_to?(:response)
140
175
  rest_client_resp_hash = {
141
- request_timestamp: Time.now.strftime('%Y-%m-%d_%H-%M-%S'),
176
+ request_timestamp: request_timestamp,
177
+ response_timestamp: response_timestamp,
178
+ duration: duration,
142
179
  http_uri: http_uri,
143
180
  http_method: http_method,
144
181
  http_resp_code: e.response.code,
@@ -148,7 +185,9 @@ def request_path(opts = {})
148
185
  }
149
186
  else
150
187
  resp_client_resp_hash = {
151
- request_timestamp: Time.now.strftime('%Y-%m-%d_%H-%M-%S'),
188
+ request_timestamp: request_timestamp,
189
+ response_timestamp: response_timestamp,
190
+ duration: duration,
152
191
  http_uri: http_uri,
153
192
  http_method: http_method,
154
193
  http_resp_code: 'N/A',
@@ -163,17 +202,20 @@ def request_path(opts = {})
163
202
  url_encoded_wordlist_arr.push(CGI.escape(path))
164
203
  end
165
204
  wordlist_line = url_encoded_wordlist_arr.join('/')
166
-
167
- retry
168
205
  rescue RestClient::TooManyRequests
206
+ request_count += 1
169
207
  sleep 60
170
- ensure
171
- browser_obj = PWN::Plugins::TransparentBrowser.close(
172
- browser_obj: browser_obj
173
- )
208
+
209
+ retry if request_count < max_retries
210
+ rescue SystemExit, Interrupt
211
+ puts "\n#{File.basename($PROGRAM_NAME)}.#{__method__} Goodbye."
174
212
  end
175
213
 
176
214
  rest_client_resp_hash
215
+ ensure
216
+ browser_obj = PWN::Plugins::TransparentBrowser.close(
217
+ browser_obj: browser_obj
218
+ )
177
219
  end
178
220
 
179
221
  begin
@@ -189,18 +231,31 @@ begin
189
231
  raise "ERROR: #{wordlist} Does Not Exist." unless File.exist?(wordlist)
190
232
 
191
233
  append_pattern = opts[:append_pattern]
192
- proxy = opts[:proxy]
234
+ max_timeout = opts[:max_timeout] ||= 6
235
+ if max_timeout
236
+ max_timeout = max_timeout.to_f
237
+ raise 'ERROR: --max-timeout must be a positive float.' unless max_timeout.positive?
238
+ end
193
239
 
194
- max_threads = opts[:max_threads]
195
- max_threads ||= 25
240
+ max_retries = opts[:max_retries] ||= 3
241
+ if max_retries
242
+ max_retries = max_retries.to_i
243
+ raise 'ERROR: --max-retries must be a positive integer.' unless max_retries.positive?
244
+ end
196
245
 
197
- http_request_headers = opts[:http_request_headers]
246
+ proxy = opts[:proxy]
247
+ max_threads = opts[:max_threads] ||= 25
248
+ if max_threads
249
+ max_threads = max_threads.to_i
250
+ raise 'ERROR: --max-threads must be a positive integer.' unless max_threads.positive?
251
+ end
198
252
 
253
+ http_request_headers = opts[:http_request_headers]
199
254
  include_http_response_codes = opts[:include_http_response_codes]
200
- include_http_response_codes = include_http_response_codes.delete("\s").split(',') if include_http_response_codes
255
+ include_http_response_codes = include_http_response_codes.to_s.delete("\s").split(',') if include_http_response_codes
201
256
 
202
257
  exclude_http_response_codes = opts[:exclude_http_response_codes]
203
- exclude_http_response_codes = exclude_http_response_codes.delete("\s").split(',') if exclude_http_response_codes
258
+ exclude_http_response_codes = exclude_http_response_codes.to_s.delete("\s").split(',') if exclude_http_response_codes
204
259
 
205
260
  raise 'ERROR: Flags --include-response-codes and --exclude-response-codes cannot be used together.' if include_http_response_codes && exclude_http_response_codes
206
261
 
@@ -230,33 +285,60 @@ begin
230
285
 
231
286
  next if wordlist_line.match?(/^#/)
232
287
 
233
- http_methods = %i[DELETE GET HEAD OPTIONS PATCH POST PUT TRACE].shuffle
288
+ http_methods = %i[
289
+ DELETE
290
+ GET
291
+ HEAD
292
+ OPTIONS
293
+ PATCH
294
+ POST
295
+ PUT
296
+ TRACE
297
+ ].shuffle
298
+
299
+ timeout = nil
234
300
  http_methods.each do |http_method|
235
301
  # TODO: Implement HTTP response timeout
302
+ prev_rest_client_resp_hash = results_hash[:data].last
236
303
  rest_client_resp_hash = request_path(
237
304
  target_url: target_url,
238
305
  proxy: proxy,
239
306
  http_request_headers: http_request_headers,
240
307
  wordlist_line: "#{wordlist_line}#{append_pattern}",
241
- http_method: http_method
308
+ http_method: http_method,
309
+ max_timeout: max_timeout,
310
+ timeout: timeout,
311
+ max_retries: max_retries
242
312
  )
243
313
 
244
314
  mutex.synchronize do
245
- if include_http_response_codes
246
- ret_http_resp_code = rest_client_resp_hash[:http_resp_code].to_s
247
- results_hash[:data].push(rest_client_resp_hash) if include_http_response_codes.include?(ret_http_resp_code)
248
- elsif exclude_http_response_codes
249
- ret_http_resp_code = rest_client_resp_hash[:http_resp_code].to_s
250
- results_hash[:data].push(rest_client_resp_hash) unless exclude_http_response_codes.include?(ret_http_resp_code)
315
+ ret_http_resp_code = rest_client_resp_hash[:http_resp_code].to_s
316
+ # Better waY to implement this?
317
+ if include_http_response_codes.is_a?(Array)
318
+ # If include_http_response_codes is an array, only include
319
+ # the response into the results_hash if the response code
320
+ # is in the include_http_response_codes array
321
+ results_hash[:data].push(rest_client_resp_hash) if include_http_response_codes.include?(ret_http_resp_code) &&
322
+ prev_rest_client_resp_hash != rest_client_resp_hash &&
323
+ rest_client_resp_hash.any?
251
324
  else
252
- results_hash[:data].push(rest_client_resp_hash)
325
+ # If exclude_http_response_codes is an array, only include
326
+ # the response into the results_hash if the response code
327
+ # is not in the exclude_http_response_codes array
328
+ results_hash[:data].push(rest_client_resp_hash) unless exclude_http_response_codes.is_a?(Array) &&
329
+ exclude_http_response_codes.include?(ret_http_resp_code) &&
330
+ prev_rest_client_resp_hash != rest_client_resp_hash &&
331
+ rest_client_resp_hash.any?
253
332
  end
333
+ # Finally puts the last line of the results_hash to STDOUT
334
+ puts results_hash[:data].last if results_hash[:data].last.any?
335
+ timeout = results_hash[:data].last[:duration].to_f if results_hash[:data].last.any?
254
336
  end
255
337
  end
256
338
  end
257
339
 
258
340
  # Generate HTML Report
259
- print "#{$PROGRAM_NAME} Generating Report..."
341
+ print "#{File.basename($PROGRAM_NAME)} Generating Report..."
260
342
  PWN::Reports::URIBuster.generate(
261
343
  dir_path: dir_path,
262
344
  results_hash: results_hash
@@ -283,8 +365,10 @@ begin
283
365
  listen_port
284
366
  )
285
367
  end
286
- rescue SystemExit, Interrupt
287
- puts "\nGoodbye."
368
+ # rescue SystemExit, Interrupt
369
+ # puts "\nGoodbye."
370
+ rescue Interrupt
371
+ puts "\n#{File.basename($PROGRAM_NAME)} Goodbye."
288
372
  rescue StandardError => e
289
373
  puts e.backtrace
290
374
  raise e
@@ -7,7 +7,7 @@ require 'optparse'
7
7
  opts = {}
8
8
  OptionParser.new do |options|
9
9
  options.banner = "USAGE:
10
- #{$PROGRAM_NAME} [opts]
10
+ #{File.basename($PROGRAM_NAME)} [opts]
11
11
  "
12
12
 
13
13
  options.on('-tFQDN', '--target-fqdn=FQDN', '<Required - FQDN to Target>') do |t|
@@ -36,7 +36,7 @@ OptionParser.new do |options|
36
36
  end.parse!
37
37
 
38
38
  if opts.empty?
39
- puts `#{$PROGRAM_NAME} --help`
39
+ puts `#{File.basename($PROGRAM_NAME)} --help`
40
40
  exit 1
41
41
  end
42
42
 
@@ -30,7 +30,7 @@ module PWN
30
30
  pass = TTY::Prompt.new.mask("#{prompt}: ")
31
31
  pass.to_s.strip.chomp.scrub
32
32
  rescue Interrupt
33
- puts 'CTRL+C detected...goodbye.'
33
+ puts "#{self}.#{__method__} => Goodbye."
34
34
  rescue StandardError => e
35
35
  raise e
36
36
  end
@@ -46,7 +46,7 @@ module PWN
46
46
  mfa = TTY::Prompt.new.ask("#{prompt}: ")
47
47
  mfa.to_s.strip.chomp.scrub
48
48
  rescue Interrupt
49
- puts 'CTRL+C detected...goodbye.'
49
+ puts "#{self}.#{__method__} => Goodbye."
50
50
  rescue StandardError => e
51
51
  raise e
52
52
  end