pwn 0.5.494 → 0.5.495

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: 6a226c1c12aee43a0797ac73395608686c8d3af0c22d00ae91ae4a32963cdeab
4
- data.tar.gz: 1aaac65edd8e5b056fa62e6a78038de56bbca3bec7191431ed164d07db93bba4
3
+ metadata.gz: 60c64fa5b4751ee72882a87e666c22ca826bb94bab63f0dca925a92520241afd
4
+ data.tar.gz: 6ddf3436eca29081e4d53f81183555d80daaf847ebab4b9f6174114372bcde63
5
5
  SHA512:
6
- metadata.gz: e033ff70444b1d95a997435ab995aea33648e2ac76ad3b28c0c5ee287519e812cadb4bd59f7b9d38d0e7fbe7ac56190c617599b8cc68291d5dcb4b067b718b88
7
- data.tar.gz: 75e791c0ed1dfe955697c23505ccdb75f74c216b89adf5e021e66687e7329ebef33350cbc2f63cfe9411d735785d3f44a5a0b445017dc9136b8b4e83708b13b3
6
+ metadata.gz: 25a82f578e16c23fbf958e353381c39f0ec9126a6d254c51849620b42d68012024f987cacbbf79790889bd282936f13c8ae8fba060b39b3bd2738e44270f76f7
7
+ data.tar.gz: 378c9b00d02d832aa820c4cf53cb09c01949d3a1ff2e7f4fd2d6e17833d84266d9a6be6a1fb5f2e792335a90b21ae88e42c574f2fb152d8b0bf74b56ab672112
data/.rubocop.yml CHANGED
@@ -14,13 +14,13 @@ Metrics/BlockNesting:
14
14
  Metrics/ClassLength:
15
15
  Max: 134
16
16
  Metrics/CyclomaticComplexity:
17
- Max: 157
17
+ Max: 158
18
18
  Metrics/MethodLength:
19
19
  Max: 565
20
20
  Metrics/ModuleLength:
21
- Max: 1116
21
+ Max: 1133
22
22
  Metrics/PerceivedComplexity:
23
- Max: 156
23
+ Max: 157
24
24
  Style/HashEachMethods:
25
25
  Enabled: true
26
26
  Style/HashSyntax:
data/Gemfile CHANGED
@@ -11,14 +11,14 @@ gemspec
11
11
  # In some circumstances custom flags are passed to gems in order
12
12
  # to build appropriately. Defer to ./reinstall_pwn_gemset.sh
13
13
  # to review these custom flags (e.g. pg, serialport, etc).
14
- gem 'activesupport', '<8.1.0'
14
+ gem 'activesupport', '<8.1.1'
15
15
  gem 'anemone', '0.7.2'
16
16
  gem 'authy', '3.0.1'
17
17
  gem 'aws-sdk', '3.3.0'
18
18
  gem 'barby', '0.7.0'
19
19
  gem 'base32', '0.3.4'
20
20
  gem 'bitcoin-ruby', '0.0.20'
21
- gem 'brakeman', '7.1.0'
21
+ gem 'brakeman', '7.1.1'
22
22
  gem 'bson', '5.2.0'
23
23
  gem 'bundler', '>=2.7.2'
24
24
  gem 'bundler-audit', '0.9.2'
@@ -70,17 +70,17 @@ gem 'pdf-reader', '2.15.0'
70
70
  gem 'pg', '1.6.2'
71
71
  gem 'pry', '0.15.2'
72
72
  gem 'pry-doc', '1.6.0'
73
- gem 'rake', '13.3.0'
73
+ gem 'rake', '13.3.1'
74
74
  gem 'rb-readline', '0.5.5'
75
75
  gem 'rbvmomi2', '3.8.0'
76
- gem 'rdoc', '6.15.0'
76
+ gem 'rdoc', '6.15.1'
77
77
  gem 'rest-client', '2.1.0'
78
78
  gem 'rex', '2.0.13'
79
79
  gem 'rmagick', '6.1.4'
80
80
  gem 'rqrcode', '3.1.0'
81
81
  gem 'rspec', '3.13.2'
82
82
  gem 'rtesseract', '3.1.4'
83
- gem 'rubocop', '1.81.6'
83
+ gem 'rubocop', '1.81.7'
84
84
  gem 'rubocop-rake', '0.7.1'
85
85
  gem 'rubocop-rspec', '3.7.0'
86
86
  gem 'ruby-audio', '1.6.1'
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.494]:001 >>> PWN.help
40
+ pwn[v0.5.495]: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-3.4.4@pwn
52
52
  $ gem uninstall --all --executables pwn
53
53
  $ gem install --verbose pwn
54
54
  $ pwn
55
- pwn[v0.5.494]:001 >>> PWN.help
55
+ pwn[v0.5.495]: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.494]:001 >>> PWN.help
65
+ pwn[v0.5.495]: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:
@@ -11,7 +11,8 @@ module PWN
11
11
  # Supported Method Parameters::
12
12
  # response = PWN::AI::Introspection.reflect_on(
13
13
  # request: 'required - String - What you want the AI to reflect on',
14
- # system_role_content: 'optional - context to set up the model behavior for reflection'
14
+ # system_role_content: 'optional - context to set up the model behavior for reflection',
15
+ # spinner: 'optional - Boolean - Display spinner during operation (default: false)'
15
16
  # )
16
17
 
17
18
  public_class_method def self.reflect_on(opts = {})
@@ -20,6 +21,8 @@ module PWN
20
21
 
21
22
  system_role_content = opts[:system_role_content]
22
23
 
24
+ spinner = opts[:spinner] || false
25
+
23
26
  response = nil
24
27
 
25
28
  ai_introspection = PWN::Env[:ai][:introspection]
@@ -34,7 +37,7 @@ module PWN
34
37
  response = PWN::AI::Grok.chat(
35
38
  request: request.chomp,
36
39
  system_role_content: system_role_content,
37
- spinner: false
40
+ spinner: spinner
38
41
  )
39
42
  response = response[:choices].last[:content] if response.is_a?(Hash) &&
40
43
  response.key?(:choices) &&
@@ -43,7 +46,7 @@ module PWN
43
46
  response = PWN::AI::Ollama.chat(
44
47
  request: request.chomp,
45
48
  system_role_content: system_role_content,
46
- spinner: false
49
+ spinner: spinner
47
50
  )
48
51
  response = response[:choices].last[:content] if response.is_a?(Hash) &&
49
52
  response.key?(:choices) &&
@@ -52,7 +55,7 @@ module PWN
52
55
  response = PWN::AI::OpenAI.chat(
53
56
  request: request.chomp,
54
57
  system_role_content: system_role_content,
55
- spinner: false
58
+ spinner: spinner
56
59
  )
57
60
  if response.is_a?(Hash) && response.key?(:choices)
58
61
  response = response[:choices].last[:text] if response[:choices].last.keys.include?(:text)
@@ -158,16 +158,12 @@ module PWN
158
158
  case model
159
159
  when 'gpt-3.5-turbo', 'gpt-3.5-turbo-0125', 'gpt-3.5-turbo-1106', 'gpt-3.5-turbo-instruct',
160
160
  'gpt-4-turbo', 'gpt-4-turbo-2024-04-09', 'gpt-4-turbo-preview', 'gpt-4-0125-preview', 'gpt-4-1106-preview'
161
- max_completion_tokens = 4_096 - (request.to_s.length / 4)
161
+ max_completion_tokens = 4_096
162
162
  when 'gpt-4', 'gpt-4-0613', 'gpt-4-0314',
163
163
  'gpt-4o', 'gpt-4o-2024-05-13'
164
- max_completion_tokens = 8_192 - (request.to_s.length / 4)
165
- when 'gpt-4o-mini', 'gpt-4o-mini-2024-07-18', 'gpt-4o-2024-08-06', 'chatgpt-4o-latest', 'gpt-5-chat-latest'
166
- max_completion_tokens = 16_384 - (request.to_s.length / 4)
167
- when 'o1-preview', 'o1-preview-2024-09-12'
168
- max_completion_tokens = 32_768 - (request.to_s.length / 4)
169
- when 'o1-mini', 'o1-mini-2024-09-12'
170
- max_completion_tokens = 65_536 - (request.to_s.length / 4)
164
+ max_completion_tokens = 8_192
165
+ else
166
+ max_completion_tokens = 16_384
171
167
  end
172
168
 
173
169
  response_history = opts[:response_history]
data/lib/pwn/config.rb CHANGED
@@ -90,6 +90,7 @@ module PWN
90
90
  token: 'Jira Server API Token'
91
91
  },
92
92
  meshtastic: {
93
+ admin_key: 'Public key authorized to send admin messages to nodes',
93
94
  serial: {
94
95
  port: '/dev/ttyUSB0',
95
96
  baud: 115_200,
@@ -108,12 +108,20 @@ module PWN
108
108
  next
109
109
  end
110
110
 
111
- # Update proxy listener to use the burp_ip and burp_port
112
- update_proxy_listener(
111
+ # Delete existing proxy listener and add new one
112
+ # in favor of weird update behavior in event the port is alread in use
113
+ # by another application which refuses to enable the listener even when
114
+ # the port is changed via the update method.
115
+ delete_proxy_listener(
113
116
  burp_obj: burp_obj,
114
- id: '0',
115
- address: burp_ip,
116
- port: burp_port
117
+ id: 0
118
+ )
119
+
120
+ add_proxy_listener(
121
+ burp_obj: burp_obj,
122
+ bindAddress: burp_ip,
123
+ port: burp_port,
124
+ enabled: true
117
125
  )
118
126
 
119
127
  burp_obj
@@ -270,7 +278,7 @@ module PWN
270
278
  # Supported Method Parameters::
271
279
  # json_proxy_listener = PWN::Plugins::BurpSuite.add_proxy_listener(
272
280
  # burp_obj: 'required - burp_obj returned by #start method',
273
- # bind_address: 'required - bind address for the proxy listener (e.g., "127.0.0.1")',
281
+ # bindAddress: 'required - bind address for the proxy listener (e.g., "127.0.0.1")',
274
282
  # port: 'required - port for the proxy listener (e.g., 8081)',
275
283
  # enabled: 'optional - enable the listener (defaults to true)'
276
284
  # )
@@ -279,8 +287,8 @@ module PWN
279
287
  burp_obj = opts[:burp_obj]
280
288
  rest_browser = burp_obj[:rest_browser]
281
289
  mitm_rest_api = burp_obj[:mitm_rest_api]
282
- bind_address = opts[:bind_address]
283
- raise 'ERROR: bind_address parameter is required' if bind_address.nil?
290
+ bind_address = opts[:bindAddress]
291
+ raise 'ERROR: bindAddress parameter is required' if bind_address.nil?
284
292
 
285
293
  port = opts[:port]
286
294
  raise 'ERROR: port parameter is required' if port.nil?
@@ -288,12 +296,14 @@ module PWN
288
296
  enabled = opts[:enabled] != false # Default to true if not specified
289
297
 
290
298
  proxy_listeners = get_proxy_listeners(burp_obj: burp_obj)
291
- last_known_proxy_id = proxy_listeners.last[:id].to_i ||= 0
299
+ puts "Proxy Listeners: #{proxy_listeners.inspect}"
300
+ last_known_proxy_id = 0
301
+ last_known_proxy_id = proxy_listeners.last[:id].to_i if proxy_listeners.any?
292
302
  next_id = last_known_proxy_id + 1
293
303
 
294
304
  post_body = {
295
305
  id: next_id.to_s,
296
- bind_address: bind_address,
306
+ bindAddress: bind_address,
297
307
  port: port,
298
308
  enabled: enabled
299
309
  }.to_json
@@ -308,24 +318,29 @@ module PWN
308
318
  # Supported Method Parameters::
309
319
  # json_proxy_listener = PWN::Plugins::BurpSuite.update_proxy_listener(
310
320
  # burp_obj: 'required - burp_obj returned by #start method',
311
- # id: 'optional - ID of the proxy listener (defaults to "0")',
312
- # bind_address: 'optional - bind address for the proxy listener (defaults to "127.0.0.1")',
313
- # port: 'optional - port for the proxy listener (defaults to 8080)',
314
- # enabled: 'optional - enable or disable the listener (defaults to true)'
321
+ # id: 'optional - ID of the proxy listener (defaults to 0)',
322
+ # bindAddress: 'optional - bind address for the proxy listener (defaults to value of existing listener)',
323
+ # port: 'optional - port for the proxy listener (defaults to value of existing listener)',
324
+ # enabled: 'optional - enable or disable the listener (defaults to value of existing listener)'
315
325
  # )
316
326
 
317
327
  public_class_method def self.update_proxy_listener(opts = {})
318
328
  burp_obj = opts[:burp_obj]
319
329
  rest_browser = burp_obj[:rest_browser]
320
330
  mitm_rest_api = burp_obj[:mitm_rest_api]
321
- id = opts[:id] ||= '0'
322
- bind_address = opts[:bind_address] ||= '127.0.0.1'
323
- port = opts[:port] ||= 8080
324
- enabled = opts[:enabled] != false # Default to true if not specified
331
+ id = opts[:id] ||= 0
332
+
333
+ proxy_listeners = get_proxy_listeners(burp_obj: burp_obj)
334
+ listener_by_id = proxy_listeners.find { |listener| listener[:id].to_i == id.to_i }
335
+ raise "ERROR: No proxy listener found with ID #{id}" if listener_by_id.nil?
336
+
337
+ bind_address = opts[:bindAddress] ||= listener_by_id[:bindAddress]
338
+ port = opts[:port] ||= listener_by_id[:port]
339
+ enabled = opts[:enabled] ||= listener_by_id[:enabled]
325
340
 
326
341
  post_body = {
327
- id: id,
328
- bind_address: bind_address,
342
+ id: id.to_s,
343
+ bindAddress: bind_address,
329
344
  port: port,
330
345
  enabled: enabled
331
346
  }.to_json
@@ -340,15 +355,17 @@ module PWN
340
355
  # Supported Method Parameters::
341
356
  # PWN::Plugins::BurpSuite.delete_proxy_listener(
342
357
  # burp_obj: 'required - burp_obj returned by #start method',
343
- # id: 'required - ID of the proxy listener (defaults to "0")'
358
+ # id: 'optional - ID of the proxy listener (defaults to 0)'
344
359
  # )
345
360
 
346
361
  public_class_method def self.delete_proxy_listener(opts = {})
347
362
  burp_obj = opts[:burp_obj]
348
363
  rest_browser = burp_obj[:rest_browser]
349
364
  mitm_rest_api = burp_obj[:mitm_rest_api]
350
- id = opts[:id] ||= '0'
351
- raise 'ERROR: id parameter is required' if id.nil?
365
+ id = opts[:id] ||= 0
366
+ proxy_listeners = get_proxy_listeners(burp_obj: burp_obj)
367
+ listener_by_id = proxy_listeners.find { |listener| listener[:id].to_i == id.to_i }
368
+ raise "ERROR: No proxy listener found with ID #{id}" if listener_by_id.nil?
352
369
 
353
370
  rest_browser.delete("http://#{mitm_rest_api}/proxy/listeners/#{id}")
354
371
  true # Return true to indicate successful deletion (or error if API fails)
@@ -567,6 +584,22 @@ module PWN
567
584
  sitemap = opts[:sitemap] ||= {}
568
585
  debug = opts[:debug] || false
569
586
 
587
+ decoded_sitemap = {
588
+ request: Base64.strict_decode64(sitemap[:request]),
589
+ http_service: {
590
+ host: sitemap[:http_service][:host],
591
+ port: sitemap[:http_service][:port],
592
+ protocol: sitemap[:http_service][:protocol]
593
+ }
594
+ }
595
+ system_role_content = 'Your sole purpose is to analyze each `protocol`, `host`, `port`, and `request` and generate an Exploit Prediction Scoring System (EPSS) score from 0%-100%. Just generate a score unless the score is >= 75% in which a PoC should also be generated to communicate the threat. You can always generate an score - provide the score at _the beggining of your analysis_. Be concise and to the point.'
596
+ ai_analysis = PWN::AI::Introspection.reflect_on(
597
+ system_role_content: system_role_content,
598
+ request: decoded_sitemap.to_json,
599
+ spinner: true
600
+ )
601
+ sitemap[:comment] = ai_analysis unless ai_analysis.nil?
602
+
570
603
  rest_client = rest_browser::Request
571
604
  response = rest_client.execute(
572
605
  method: :post,
@@ -1484,22 +1517,22 @@ module PWN
1484
1517
 
1485
1518
  json_proxy_listener = #{self}.add_proxy_listener(
1486
1519
  burp_obj: 'required - burp_obj returned by #start method',
1487
- bind_address: 'required - bind address for the proxy listener (e.g., \"127.0.0.1\")',
1520
+ bindAddress: 'required - bind address for the proxy listener (e.g., \"127.0.0.1\")',
1488
1521
  port: 'required - port for the proxy listener (e.g., 8081)',
1489
1522
  enabled: 'optional - enable the listener (defaults to true)'
1490
1523
  )
1491
1524
 
1492
1525
  json_proxy_listener = #{self}.update_proxy_listener(
1493
1526
  burp_obj: 'required - burp_obj returned by #start method',
1494
- id: 'optional - ID of the proxy listener (defaults to \"0\")',
1495
- bind_address: 'required - bind address for the proxy listener (e.g., \"127.0.0.1\")',
1496
- port: 'required - port for the proxy listener (e.g., 8081)',
1497
- enabled: 'optional - enable the listener (defaults to true)'
1527
+ id: 'optional - ID of the proxy listener (defaults to 0)',
1528
+ bindAddress: 'optional - bind address for the proxy listener (defaults to value of existing listener)',
1529
+ port: 'optional - port for the proxy listener (defaults to value of existing listener)',
1530
+ enabled: 'optional - enable the listener (defaults to value of existing listener)'
1498
1531
  )
1499
1532
 
1500
1533
  #{self}.delete_proxy_listener(
1501
1534
  burp_obj: 'required - burp_obj returned by #start method',
1502
- id: 'required - ID of the proxy listener (defaults to \"0\")'
1535
+ id: 'optional - ID of the proxy listener (defaults to 0)'
1503
1536
  )
1504
1537
 
1505
1538
  json_sitemap = #{self}.get_sitemap(
@@ -190,11 +190,6 @@ module PWN
190
190
  args.push("--proxy-server=#{proxy}")
191
191
  end
192
192
 
193
- if devtools
194
- args.push('--auto-open-devtools-for-tabs')
195
- args.push('--disable-hang-monitor')
196
- end
197
-
198
193
  # Incognito browsing mode
199
194
  args.push('--incognito')
200
195
  options = Selenium::WebDriver::Chrome::Options.new(
@@ -202,6 +197,13 @@ module PWN
202
197
  accept_insecure_certs: true
203
198
  )
204
199
 
200
+ if devtools
201
+ args.push('--auto-open-devtools-for-tabs')
202
+ args.push('--disable-hang-monitor')
203
+ options.add_preference('devtools.preferences.enable-ignore-listing', false)
204
+ options.add_preference('devtools.preferences.default-indentation', '2 spaces')
205
+ end
206
+
205
207
  # This is required for BiDi support
206
208
  options.web_socket_url = true
207
209
  options.add_preference('remote.active-protocols', 3)
@@ -1148,69 +1150,150 @@ module PWN
1148
1150
  raise 'ERROR: action parameter must be :enable|:pause|:resume|:disable' unless valid_actions.include?(action)
1149
1151
 
1150
1152
  devtools = browser_obj[:devtools]
1151
- debugger_state = devtools.instance_variable_get(:@debugger_state)
1153
+ debugger_state = devtools.instance_variable_get(:@debugger_state) || {}
1154
+ breakpoint_arr = debugger_state[:breakpoints] || []
1152
1155
 
1153
1156
  method = nil
1154
1157
  case action
1155
1158
  when :enable
1156
- while method != 'Debugger.scriptParsed'
1157
- if debugger_state.is_a?(Hash)
1158
- debugger_state = devtools.instance_variable_get(:@debugger_state)
1159
- devtools.remove_instance_variable(:@debugger_state) unless debugger_state.nil?
1160
- devtools.debugger.disable
1159
+ devtools.dom.enable
1160
+ devtools.log.disable
1161
+ devtools.network.disable
1162
+ devtools.page.disable
1163
+ devtools.runtime.disable
1164
+
1165
+ method = 'Debugger.scriptParsed'
1166
+ callbacks_to_delete = devtools.callbacks.keys.reject { |k| k == 'Target.atta`chedToTarget' }
1167
+ # until devtools.callbacks.keys.include?(method) && breakpoint_arr.any?
1168
+ until breakpoint_arr.any?
1169
+ callbacks_to_delete.each { |method| devtools.callbacks.delete(method) }
1170
+ breakpoint_set = false
1171
+ # devtools.dom.disable
1172
+ devtools.debugger.disable
1173
+ devtools.debugger.on(:script_parsed) do |params|
1174
+ url = params['url']
1175
+ next if breakpoint_set || url.include?('devtools://') || url.empty?
1176
+
1177
+ breakpoint_set = true
1178
+ puts url
1179
+ bcmd = 'Debugger.setBreakpoint'
1180
+ script_id = params['scriptId']
1181
+ line = params['startLine']
1182
+ column = params['startColumn']
1183
+ location = { scriptId: script_id, lineNumber: line, columnNumber: column }
1184
+ breakpoint = devtools.send_cmd(bcmd, location: location)
1185
+ breakpoint['result']['breakpointId'] = "#{bcmd}.#{script_id}.#{line}.#{column}.#{SecureRandom.uuid}"
1186
+ breakpoint['id'] = breakpoint['id'].to_s
1187
+ breakpoint['url'] = url
1188
+ breakpoint['caught'] = false
1189
+ breakpoint_arr.push(breakpoint)
1190
+ debugger_state[:breakpoints] = breakpoint_arr
1191
+ devtools.instance_variable_set(:@debugger_state, debugger_state)
1192
+
1193
+ puts "Breakpoint set in #{url} at line #{line}, column #{column}: #{breakpoint}"
1194
+ puts params.inspect
1161
1195
  end
1162
- debugger_state = {}
1163
- breakpoint_arr = []
1164
-
1165
1196
  devtools.debugger.enable
1166
- ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1167
- method = ws_msg['method']
1168
-
1169
- bcmd = 'EventBreakpoints.setInstrumentationBreakpoint'
1170
- event = 'load'
1171
- breakpoint = devtools.send_cmd(bcmd, eventName: event)
1172
- breakpoint['result']['breakpointId'] = "#{bcmd}.#{event}.#{SecureRandom.uuid}"
1173
- # bcmd = 'Debugger.setInstrumentationBreakpoint'
1174
- # instrumentation = 'beforeScriptExecution'
1175
- # breakpoint = devtools.send_cmd(bcmd, instrumentation: instrumentation)
1176
- # breakpoint['result']['breakpointId'] = "#{bcmd}.#{instrumentation}.#{SecureRandom.uuid}"
1177
- breakpoint_arr.push(breakpoint)
1178
- debugger_state[:breakpoints] = breakpoint_arr
1179
-
1180
- devtools.runtime.disable
1181
- devtools.log.disable
1182
- devtools.network.disable
1183
- devtools.page.disable
1184
- puts debugger_state.inspect
1185
1197
  end
1198
+ devtools.callbacks.delete(method)
1199
+ method = 'Debugger.enabled'
1186
1200
  when :pause
1187
- while method != 'Debugger.paused'
1188
- Timeout.timeout(9) { browser_obj[:browser].refresh }
1201
+ method = 'Debugger.paused'
1202
+ callbacks_to_delete = devtools.callbacks.keys.reject { |k| k == 'Target.attachedToTarget' }
1203
+ Timeout.timeout(30) { browser_obj[:browser].refresh }
1204
+ until devtools.callbacks.keys.include?(method) && breakpoint_arr.any? { |bp| bp['caught'] == true }
1205
+ callbacks_to_delete.each { |method| devtools.callbacks.delete(method) }
1206
+ devtools.debugger.on(:paused) do |params|
1207
+ breakpoint_id_caught = params['callFrames'].first['location']['scriptId']
1208
+ breakpoint_arr.each_with_index do |bp, idx|
1209
+ next unless bp['id'] == breakpoint_id_caught
1210
+
1211
+ bp['caught'] = true
1212
+ breakpoint_arr[idx] = bp
1213
+ debugger_state[:breakpoints] = breakpoint_arr
1214
+ devtools.instance_variable_set(:@debugger_state, debugger_state)
1215
+ end
1216
+ puts "TARGET BREAKPOINTS: #{breakpoint_arr.inspect}"
1217
+ puts "PARAMS Observerd: #{params.inspect}"
1218
+ end
1189
1219
  devtools.debugger.pause
1190
- ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1191
- method = ws_msg['method']
1220
+ # browser_obj[:browser].refresh
1221
+ debugger_state = devtools.instance_variable_get(:@debugger_state)
1222
+ breakpoint_arr = debugger_state[:breakpoints]
1192
1223
  end
1224
+ devtools.callbacks.delete(method)
1193
1225
  when :resume
1194
- while method != 'Debugger.resumed'
1195
- devtools.debugger.resume
1196
- ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1197
- method = ws_msg['method']
1198
- end
1226
+ method = 'Debugger.resumed'
1227
+ callbacks_to_delete = devtools.callbacks.keys.reject { |k| k == 'Target.attachedToTarget' }
1228
+ callbacks_to_delete.each { |method| devtools.callbacks.delete(method) }
1229
+ devtools.debugger.resume until devtools.callbacks.keys.include?(method)
1199
1230
  when :disable
1200
- debugger_state = devtools.instance_variable_get(:@debugger_state)
1201
- devtools.remove_instance_variable(:@debugger_state) if debugger_state.is_a?(Hash)
1231
+ callbacks_to_delete = devtools.callbacks.keys.reject { |k| k == 'Target.attachedToTarget' }
1232
+ callbacks_to_delete.each { |method| devtools.callbacks.delete(method) }
1202
1233
  devtools.debugger.disable
1234
+ method = 'Debugger.disabled'
1203
1235
  end
1204
1236
 
1205
- debugger_state[:method] = method
1206
- devtools.instance_variable_set(:@debugger_state, debugger_state) if debugger_state.is_a?(Hash)
1207
- devtools
1208
- rescue Timeout::Error
1209
1237
  devtools
1210
1238
  rescue Selenium::WebDriver::Error::WebDriverError => e
1211
1239
  puts e.message
1212
1240
  rescue StandardError => e
1213
1241
  raise e
1242
+ ensure
1243
+ debugger_state[:method] = method
1244
+ devtools.instance_variable_set(:@debugger_state, debugger_state) if debugger_state.is_a?(Hash)
1245
+ end
1246
+
1247
+ # Supported Method Parameters::
1248
+ # page_state_arr = PWN::Plugins::TransparentBrowser.get_targets(
1249
+ # browser_obj: 'required - browser_obj returned from #open method)'
1250
+ # )
1251
+
1252
+ public_class_method def self.get_targets(opts = {})
1253
+ browser_obj = opts[:browser_obj]
1254
+ supported = %i[chrome headless_chrome]
1255
+ verified = verify_devtools_browser(browser_obj: browser_obj, supported: supported)
1256
+ puts 'This browser is not supported for DevTools operations.' unless verified
1257
+ return unless verified
1258
+
1259
+ devtools = browser_obj[:devtools]
1260
+ bcmd = 'Target.getTargets'
1261
+ devtools.send_cmd(bcmd)
1262
+ rescue StandardError => e
1263
+ raise e
1264
+ end
1265
+
1266
+ # Supported Method Parameters::
1267
+ # page_state_arr = PWN::Plugins::TransparentBrowser.breakpoint_locations(
1268
+ # browser_obj: 'required - browser_obj returned from #open method)'
1269
+ # )
1270
+
1271
+ public_class_method def self.breakpoint_locations(opts = {})
1272
+ browser_obj = opts[:browser_obj]
1273
+ supported = %i[chrome headless_chrome]
1274
+ verified = verify_devtools_browser(browser_obj: browser_obj, supported: supported)
1275
+ puts 'This browser is not supported for DevTools operations.' unless verified
1276
+ return unless verified
1277
+
1278
+ valid_methods = %w[Debugger.scriptParsed Debugger.paused Debugger.resumed]
1279
+ devtools = browser_obj[:devtools]
1280
+ ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1281
+ method = ws_msg['method']
1282
+ raise "ERROR: Unsupported method: #{method}" unless valid_methods.include?(method)
1283
+
1284
+ case method
1285
+ when 'Debugger.resumed', 'Debugger.paused'
1286
+ script_id = ws_msg['params']['callFrames'].first['location']['scriptId'].to_s
1287
+ when 'Debugger.scriptParsed'
1288
+ script_id = ws_msg['params']['scriptId'].to_s
1289
+ end
1290
+
1291
+ puts "Method: #{method}"
1292
+ puts "Fetching possible breakpoints for script ID: #{script_id}..."
1293
+ bcmd = 'Debugger.getPossibleBreakpoints'
1294
+ devtools.send_cmd(bcmd, start: { scriptId: script_id, lineNumber: 0, columnNumber: 0 })
1295
+ rescue StandardError => e
1296
+ raise e
1214
1297
  end
1215
1298
 
1216
1299
  # Supported Method Parameters::
@@ -1236,119 +1319,116 @@ module PWN
1236
1319
  steps = 1 if steps.zero? || steps.negative?
1237
1320
 
1238
1321
  devtools = browser_obj[:devtools]
1322
+ ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1323
+ method = ws_msg['method']
1324
+
1239
1325
  debugger_state = devtools.instance_variable_get(:@debugger_state)
1240
- method = debugger_state[:method]
1241
- if method != 'Debugger.paused'
1242
- puts 'The debugger must be paused before stepping. Pausing now...'
1243
- return devtools
1244
- end
1326
+ debugger_state[:method] = method
1327
+ devtools.instance_variable_set(:@debugger_state, debugger_state)
1245
1328
 
1246
- system_role_content = 'Being an expert penetration tester skilled in code analysis, debugging, and exploitation while stepping through JavaScript in a Chrome DevTools debugging session: 1. Your sole purpose is to analyze each JavaScript step and generate an Exploit Prediction Scoring System (EPSS) score between 0% - 100%. 2. If the score is >= 75%, generate a JavaScript proof-of-concept that would allow a threat actor to directly exploit or target a user for exploitation (i.e. no self-exploit). 3. If the EPSS score is >= 75% also provide a code fix. *** If the EPSS score is < 75%, no explanations or summaries - just the EPSS score.'
1329
+ valid_methods = %w[Debugger.scriptParsed Debugger.paused Debugger.resumed]
1330
+ devtools = browser_obj[:devtools]
1331
+ ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1332
+ method = ws_msg['method']
1333
+ raise "ERROR: Unsupported method: #{method}" unless valid_methods.include?(method)
1247
1334
 
1248
- page_state_arr = []
1335
+ steps_arr = []
1336
+ cursor_termination_chars = %w[; , . ( ) { } = |]
1249
1337
  steps.times do |s|
1250
1338
  step_num = s + 1
1251
1339
  puts "Stepping #{action} (step #{step_num}/#{steps})..."
1252
1340
 
1253
- # before = get_page_state(browser_obj: browser_obj)
1254
- # puts before.inspect
1255
- before = devtools_websocket_messages(browser_obj: browser_obj)
1256
- method = before['method']
1257
- # puts before
1258
- puts "\n"
1259
-
1260
- if method == 'Debugger.paused'
1261
- before_location = before['params']['callFrames'].first['location']
1262
- start_location = before['params']['callFrames'].first['scopeChain'].first['startLocation']
1263
- before_script_id = start_location['scriptId']
1264
- from_line_num = start_location['lineNumber']
1265
- from_column_num = start_location['columnNumber']
1266
-
1267
- end_location = before['params']['callFrames'].first['scopeChain'].first['endLocation']
1268
- to_line_num = end_location['lineNumber']
1269
- to_column_num = end_location['columnNumber']
1270
-
1271
- source_obj = devtools.debugger.get_script_source(script_id: before_script_id)
1272
- source_code = source_obj['result']['scriptSource']
1273
- # puts source_code
1274
- # gets
1275
-
1276
- source_lines = source_code.split("\n")
1277
- source_lines_str = source_lines[from_line_num..to_line_num].join("\n")
1278
- source_to_review = source_lines_str[from_column_num..to_column_num]
1279
- source_before = source_to_review.dup
1280
-
1281
- if source_to_review.length.positive?
1282
- puts source_to_review
1283
- ai_analysis = PWN::AI::Introspection.reflect_on(
1284
- system_role_content: system_role_content,
1285
- request: source_to_review
1286
- )
1287
- puts "^^^ #{ai_analysis}" unless ai_analysis.nil?
1288
- # gets
1289
- end
1290
- end
1291
-
1341
+ method = 'Debugger.resumed'
1292
1342
  case action
1293
1343
  when :into
1294
- devtools.debugger.step_into
1344
+ devtools.debugger.step_into until devtools.callbacks.keys.include?(method)
1295
1345
  when :out
1296
- devtools.debugger.step_out
1346
+ devtools.debugger.step_out until devtools.callbacks.keys.include?(method)
1297
1347
  when :over
1298
- devtools.debugger.step_over
1348
+ devtools.debugger.step_over until devtools.callbacks.keys.include?(method)
1299
1349
  end
1300
-
1301
- puts "\n" * 3
1302
- after = devtools_websocket_messages(browser_obj: browser_obj)
1303
- method = after['method']
1304
- # puts after
1305
- puts "\n"
1306
-
1307
- if method == 'Debugger.paused'
1308
- after_location = after['params']['callFrames'].first['scopeChain'].first['object']
1309
- start_location = after['params']['callFrames'].first['scopeChain'].first['startLocation']
1310
- after_script_id = start_location['scriptId']
1311
- from_line_num = start_location['lineNumber']
1312
- from_column_num = start_location['columnNumber']
1313
-
1314
- end_location = after['params']['callFrames'].first['scopeChain'].first['endLocation']
1315
- to_line_num = end_location['lineNumber']
1316
- to_column_num = end_location['columnNumber']
1317
-
1318
- source_obj = devtools.debugger.get_script_source(script_id: after_script_id)
1319
- source_code = source_obj['result']['scriptSource']
1320
- # puts source_code
1321
- # gets
1322
-
1323
- source_lines = source_code.split("\n")
1324
- source_lines_str = source_lines[from_line_num..to_line_num].join("\n")
1325
- source_to_review = source_lines_str[from_column_num..to_column_num]
1326
- source_after = source_to_review.dup
1327
-
1328
- if source_to_review.length.positive? && source_to_review != source_before
1329
- puts source_to_review
1330
- ai_analysis = PWN::AI::Introspection.reflect_on(
1331
- system_role_content: system_role_content,
1332
- request: source_to_review
1333
- )
1334
- puts "^^^ #{ai_analysis}" unless ai_analysis.nil?
1335
- # gets
1350
+ devtools.callbacks.delete(method)
1351
+
1352
+ method = 'Debugger.paused'
1353
+ devtools.debugger.pause until devtools.callbacks.keys.include?(method)
1354
+ devtools.callbacks.delete(method)
1355
+
1356
+ ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1357
+ ws_msg_params = ws_msg['params']
1358
+ ws_msg_call_frames = ws_msg_params['callFrames'].first
1359
+ ws_msg_scope_chain_local = ws_msg_call_frames['scopeChain'].find { |scope| scope['type'] == 'local' }
1360
+ next unless ws_msg_scope_chain_local.is_a?(Hash)
1361
+
1362
+ ws_msg_scope_chain_block = ws_msg_call_frames['scopeChain'].find { |scope| scope['type'] == 'block' }
1363
+
1364
+ cursor_location = ws_msg_call_frames['location']
1365
+ cursor_line_num = cursor_location['lineNumber']
1366
+ cursor_column_num = cursor_location['columnNumber']
1367
+
1368
+ script_id = cursor_location['scriptId']
1369
+
1370
+ start_location = ws_msg_scope_chain_local['startLocation']
1371
+ start_line_num = start_location['lineNumber']
1372
+ start_column_num = start_location['columnNumber']
1373
+
1374
+ end_location = ws_msg_scope_chain_local['endLocation']
1375
+ # end_location_block = ws_msg_scope_chain_block['endLocation']
1376
+ # puts "TEST: #{end_location - end_location_block}"
1377
+ end_line_num = end_location['lineNumber']
1378
+ end_column_num = end_location['columnNumber']
1379
+
1380
+ source_obj = devtools.debugger.get_script_source(script_id: script_id)
1381
+ full_source_code = source_obj['result']['scriptSource']
1382
+
1383
+ source_lines = full_source_code.split("\n")
1384
+ # puts source_lines.inspect
1385
+ source_lines_range = source_lines[start_line_num..end_line_num]
1386
+ next if source_lines_range.nil?
1387
+
1388
+ source_lines_str = source_lines_range.join("\n")
1389
+ source_to_review = source_lines_str[start_column_num..end_column_num]
1390
+ current_step = source_lines_str[cursor_column_num..end_column_num]
1391
+
1392
+ # TODO: leverage ANSI escape codes to highlight current_step to red
1393
+ # puts ws_msg.inspect
1394
+ # puts "\n"
1395
+ # puts ws_msg_call_frames['scopeChain'].inspect
1396
+ # puts "\n"
1397
+ cursor_terminated = false
1398
+ source_to_review.each_char.with_index do |char, idx|
1399
+ cursor_start_offset = cursor_column_num - start_column_num
1400
+ cursor_end_offset = end_column_num - start_column_num
1401
+
1402
+ if idx >= cursor_start_offset && !cursor_terminated
1403
+ cursor_terminated = true if cursor_termination_chars.include?(char)
1404
+ print char if cursor_terminated
1405
+ print "\001\e[31m\002#{char}\001\e[0m\002" unless cursor_terminated
1406
+ else
1407
+ print char
1336
1408
  end
1337
1409
  end
1338
- puts "\n" * 6
1339
1410
 
1340
- # step_hash = {
1341
- # step: step_num,
1342
- # action: action,
1343
- # before: before,
1344
- # after: after,
1345
- # diff: diff.to_s(:text)
1346
- # }
1411
+ if current_step.length.positive?
1412
+ system_role_content = "Being an expert penetration tester skilled in code analysis, debugging, and exploitation while stepping through JavaScript in a Chrome DevTools debugging session: 1. Your sole purpose is to analyze each JavaScript step and generate an Exploit Prediction Scoring System (EPSS) score between 0% - 100%. The step currently resides in this block of JavaScript:\n```\n#{source_to_review}\n```\n2. If the score is >= 75%, generate a JavaScript proof-of-concept that would allow a threat actor to directly exploit or target a user for exploitation (i.e. no self-exploit). 3. If the EPSS score is >= 75% also provide a code fix. *** If the EPSS score is < 75%, no explanations or summaries - just the EPSS score."
1413
+
1414
+ ai_analysis = PWN::AI::Introspection.reflect_on(
1415
+ system_role_content: system_role_content,
1416
+ request: current_step
1417
+ )
1418
+ puts "^^^ #{ai_analysis}" unless ai_analysis.nil?
1419
+ end
1420
+ puts "\n" * 3
1421
+
1422
+ step_hash = {
1423
+ step: step_num,
1424
+ action: action,
1425
+ source: current_step
1426
+ }
1347
1427
 
1348
- # page_state_arr.push(step_hash)
1428
+ steps_arr.push(step_hash)
1349
1429
  end
1350
1430
 
1351
- devtools
1431
+ steps_arr
1352
1432
  rescue Selenium::WebDriver::Error::WebDriverError
1353
1433
  devtools
1354
1434
  rescue StandardError => e
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.494'
4
+ VERSION = '0.5.495'
5
5
  end
@@ -131,9 +131,18 @@ module PWN
131
131
  cursor = page_info[:endCursor]
132
132
  break unless page_info[:hasNextPage]
133
133
  end
134
+ puts "\n"
134
135
 
135
136
  programs_arr.sort_by! { |p| -p[:min_payout].gsub('$', '').gsub(',', '').to_f }
136
137
 
138
+ system_role_content = 'Suggest an optimal bug bounty program to target on HackerOne to maximize potential earnings based on values within `min_payout` and publicly known vulnerabilities that have surfaced for the `name` of the program.'
139
+ ai_analysis = PWN::AI::Introspection.reflect_on(
140
+ request: programs_arr.to_json,
141
+ system_role_content: system_role_content,
142
+ spinner: true
143
+ )
144
+ puts "\n\n#{ai_analysis}" unless ai_analysis.nil?
145
+
137
146
  programs_arr
138
147
  rescue RestClient::ExceptionWithResponse => e
139
148
  if e.response
@@ -270,6 +279,16 @@ module PWN
270
279
  name: program_name,
271
280
  scope_details: json_resp_hash
272
281
  }
282
+
283
+ system_role_content = 'Analyze the scope details for the given bug bounty program on HackerOne. Identify key areas of interest, potential vulnerabilities, and any patterns that could inform a targeted security assessment based on the provided scope information.'
284
+ ai_analysis = PWN::AI::Introspection.reflect_on(
285
+ request: json_resp.to_json,
286
+ system_role_content: system_role_content,
287
+ spinner: true
288
+ )
289
+ puts "\n\n#{ai_analysis}" unless ai_analysis.nil?
290
+
291
+ json_resp
273
292
  rescue RestClient::ExceptionWithResponse => e
274
293
  if e.response
275
294
  puts "HTTP RESPONSE CODE: #{e.response.code}"
@@ -408,6 +427,16 @@ module PWN
408
427
  name: program_name,
409
428
  hacktivity: json_resp_hash
410
429
  }
430
+
431
+ system_role_content = 'Analyze the hacktivity details for the given bug bounty program on HackerOne. Identify significant disclosed reports, common vulnerability types, and any trends that could inform future security assessments based on the provided hacktivity information.'
432
+ ai_analysis = PWN::AI::Introspection.reflect_on(
433
+ request: json_resp.to_json,
434
+ system_role_content: system_role_content,
435
+ spinner: true
436
+ )
437
+ puts "\n\n#{ai_analysis}" unless ai_analysis.nil?
438
+
439
+ json_resp
411
440
  rescue RestClient::ExceptionWithResponse => e
412
441
  if e.response
413
442
  puts "HTTP RESPONSE CODE: #{e.response.code}"
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.494
4
+ version: 0.5.495
5
5
  platform: ruby
6
6
  authors:
7
7
  - 0day Inc.
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "<"
17
17
  - !ruby/object:Gem::Version
18
- version: 8.1.0
18
+ version: 8.1.1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "<"
24
24
  - !ruby/object:Gem::Version
25
- version: 8.1.0
25
+ version: 8.1.1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: anemone
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -113,14 +113,14 @@ dependencies:
113
113
  requirements:
114
114
  - - '='
115
115
  - !ruby/object:Gem::Version
116
- version: 7.1.0
116
+ version: 7.1.1
117
117
  type: :runtime
118
118
  prerelease: false
119
119
  version_requirements: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - '='
122
122
  - !ruby/object:Gem::Version
123
- version: 7.1.0
123
+ version: 7.1.1
124
124
  - !ruby/object:Gem::Dependency
125
125
  name: bson
126
126
  requirement: !ruby/object:Gem::Requirement
@@ -827,14 +827,14 @@ dependencies:
827
827
  requirements:
828
828
  - - '='
829
829
  - !ruby/object:Gem::Version
830
- version: 13.3.0
830
+ version: 13.3.1
831
831
  type: :development
832
832
  prerelease: false
833
833
  version_requirements: !ruby/object:Gem::Requirement
834
834
  requirements:
835
835
  - - '='
836
836
  - !ruby/object:Gem::Version
837
- version: 13.3.0
837
+ version: 13.3.1
838
838
  - !ruby/object:Gem::Dependency
839
839
  name: rb-readline
840
840
  requirement: !ruby/object:Gem::Requirement
@@ -869,14 +869,14 @@ dependencies:
869
869
  requirements:
870
870
  - - '='
871
871
  - !ruby/object:Gem::Version
872
- version: 6.15.0
872
+ version: 6.15.1
873
873
  type: :development
874
874
  prerelease: false
875
875
  version_requirements: !ruby/object:Gem::Requirement
876
876
  requirements:
877
877
  - - '='
878
878
  - !ruby/object:Gem::Version
879
- version: 6.15.0
879
+ version: 6.15.1
880
880
  - !ruby/object:Gem::Dependency
881
881
  name: rest-client
882
882
  requirement: !ruby/object:Gem::Requirement
@@ -967,14 +967,14 @@ dependencies:
967
967
  requirements:
968
968
  - - '='
969
969
  - !ruby/object:Gem::Version
970
- version: 1.81.6
970
+ version: 1.81.7
971
971
  type: :runtime
972
972
  prerelease: false
973
973
  version_requirements: !ruby/object:Gem::Requirement
974
974
  requirements:
975
975
  - - '='
976
976
  - !ruby/object:Gem::Version
977
- version: 1.81.6
977
+ version: 1.81.7
978
978
  - !ruby/object:Gem::Dependency
979
979
  name: rubocop-rake
980
980
  requirement: !ruby/object:Gem::Requirement