pwn 0.5.493 → 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: f77880b0e335c1f554ab63cc6f639edd72839ae9c64fdad241d89f2f341aa41c
4
- data.tar.gz: e7fcb412903e2f718aafa08288b3e059f1f2fe36764e11b2747a8a5c56d6138e
3
+ metadata.gz: 60c64fa5b4751ee72882a87e666c22ca826bb94bab63f0dca925a92520241afd
4
+ data.tar.gz: 6ddf3436eca29081e4d53f81183555d80daaf847ebab4b9f6174114372bcde63
5
5
  SHA512:
6
- metadata.gz: e196bc3ae6c8701e9ee04424ad1578675c1d87586ceb09d05ee779b2d78cacc58e1a4c2d462288b09931699a5ed05c9978d0efe2bd1c8f6992a9404912fd75e6
7
- data.tar.gz: 10e500973b37a48f24b275d3aa2ceff00b9535210f4abdf245edc7b4bf72465386a98115060dab449640c2b46fbf6a1b83a4ae4a4080113a9842c669a7cfe4e6
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'
@@ -88,8 +88,8 @@ gem 'ruby-nmap', '1.0.3'
88
88
  gem 'ruby-saml', '1.18.1'
89
89
  gem 'rvm', '1.11.3.9'
90
90
  gem 'savon', '2.15.1'
91
- gem 'selenium-devtools', '0.141.0'
92
- gem 'selenium-webdriver', '4.37.0'
91
+ gem 'selenium-devtools', '0.142.0'
92
+ gem 'selenium-webdriver', '4.38.0'
93
93
  gem 'slack-ruby-client', '3.0.0'
94
94
  gem 'socksify', '1.8.1'
95
95
  gem 'spreadsheet', '1.3.4'
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.493]: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.493]: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.493]: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,52 +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
 
1156
+ method = nil
1153
1157
  case action
1154
1158
  when :enable
1155
- if debugger_state.is_a?(Hash)
1156
- debugger_state = devtools.instance_variable_get(:@debugger_state)
1157
- devtools.remove_instance_variable(:@debugger_state) if debugger_state.is_a?(Hash)
1158
- devtools.debugger.disable
1159
- end
1160
- debugger_state = {}
1161
- breakpoint_arr = []
1162
-
1163
- # breakpoint = devtools.debugger.set_instrumentation_breakpoint(instrumentation: 'beforeScriptExecution')
1164
- bcmd = 'EventBreakpoints.setInstrumentationBreakpoint'
1165
- event = 'load'
1166
- breakpoint = devtools.send_cmd(bcmd, eventName: event)
1167
- breakpoint['result']['breakpointId'] = "#{bcmd}.#{event}.#{SecureRandom.uuid}"
1168
- breakpoint_arr.push(breakpoint)
1169
- debugger_state[:breakpoints] = breakpoint_arr
1170
-
1171
- devtools.runtime.disable
1159
+ devtools.dom.enable
1172
1160
  devtools.log.disable
1173
1161
  devtools.network.disable
1174
1162
  devtools.page.disable
1175
- devtools.debugger.enable
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
1195
+ end
1196
+ devtools.debugger.enable
1197
+ end
1198
+ devtools.callbacks.delete(method)
1199
+ method = 'Debugger.enabled'
1176
1200
  when :pause
1177
- devtools.debugger.pause
1178
- Timeout.timeout(5) { 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
1219
+ devtools.debugger.pause
1220
+ # browser_obj[:browser].refresh
1221
+ debugger_state = devtools.instance_variable_get(:@debugger_state)
1222
+ breakpoint_arr = debugger_state[:breakpoints]
1223
+ end
1224
+ devtools.callbacks.delete(method)
1179
1225
  when :resume
1180
- devtools.debugger.resume
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)
1181
1230
  when :disable
1182
- debugger_state = devtools.instance_variable_get(:@debugger_state)
1183
- 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) }
1184
1233
  devtools.debugger.disable
1234
+ method = 'Debugger.disabled'
1185
1235
  end
1186
1236
 
1187
- devtools_websocket_messages = devtools_websocket_messages(browser_obj: browser_obj)
1188
- debugger_state[:method] = devtools_websocket_messages['method']
1189
- devtools.instance_variable_set(:@debugger_state, debugger_state)
1190
- devtools
1191
- rescue Timeout::Error
1192
1237
  devtools
1193
1238
  rescue Selenium::WebDriver::Error::WebDriverError => e
1194
1239
  puts e.message
1195
1240
  rescue StandardError => e
1196
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
1197
1297
  end
1198
1298
 
1199
1299
  # Supported Method Parameters::
@@ -1219,107 +1319,116 @@ module PWN
1219
1319
  steps = 1 if steps.zero? || steps.negative?
1220
1320
 
1221
1321
  devtools = browser_obj[:devtools]
1322
+ ws_msg = devtools_websocket_messages(browser_obj: browser_obj)
1323
+ method = ws_msg['method']
1324
+
1222
1325
  debugger_state = devtools.instance_variable_get(:@debugger_state)
1223
- method = debugger_state[:method]
1224
- if method != 'Debugger.paused'
1225
- puts 'The debugger must be paused before stepping. Pausing now...'
1226
- return devtools
1227
- end
1326
+ debugger_state[:method] = method
1327
+ devtools.instance_variable_set(:@debugger_state, debugger_state)
1228
1328
 
1229
- page_state_arr = []
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)
1334
+
1335
+ steps_arr = []
1336
+ cursor_termination_chars = %w[; , . ( ) { } = |]
1230
1337
  steps.times do |s|
1231
1338
  step_num = s + 1
1232
1339
  puts "Stepping #{action} (step #{step_num}/#{steps})..."
1233
1340
 
1234
- # before = get_page_state(browser_obj: browser_obj)
1235
- # puts before.inspect
1236
- before = devtools_websocket_messages(browser_obj: browser_obj)
1237
- method = before['method']
1238
- # puts before
1239
- puts "\n"
1240
-
1241
- if method == 'Debugger.paused'
1242
- before_location = before['params']['callFrames'].first['location']
1243
- start_location = before['params']['callFrames'].first['scopeChain'].first['startLocation']
1244
- before_script_id = start_location['scriptId']
1245
- from_line_num = start_location['lineNumber']
1246
- from_column_num = start_location['columnNumber']
1247
-
1248
- end_location = before['params']['callFrames'].first['scopeChain'].first['endLocation']
1249
- to_line_num = end_location['lineNumber']
1250
- to_column_num = end_location['columnNumber']
1251
-
1252
- source_obj = devtools.debugger.get_script_source(script_id: before_script_id)
1253
- source_code = source_obj['result']['scriptSource']
1254
- # puts source_code
1255
- # gets
1256
-
1257
- source_lines = source_code.split("\n")
1258
- source_lines_str = source_lines[from_line_num..to_line_num].join("\n")
1259
- source_to_review = source_lines_str[from_column_num..to_column_num]
1260
-
1261
- puts source_to_review
1262
- request = source_lines_str[from_column_num..to_column_num]
1263
- ai_analysis = PWN::AI::Introspection.reflect_on(request: request)
1264
- puts "^^^ #{ai_analysis}" unless ai_analysis.nil?
1265
- # gets
1266
- end
1267
-
1341
+ method = 'Debugger.resumed'
1268
1342
  case action
1269
1343
  when :into
1270
- devtools.debugger.step_into
1344
+ devtools.debugger.step_into until devtools.callbacks.keys.include?(method)
1271
1345
  when :out
1272
- devtools.debugger.step_out
1346
+ devtools.debugger.step_out until devtools.callbacks.keys.include?(method)
1273
1347
  when :over
1274
- devtools.debugger.step_over
1348
+ devtools.debugger.step_over until devtools.callbacks.keys.include?(method)
1349
+ end
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
1408
+ end
1275
1409
  end
1276
1410
 
1277
- puts "\n" * 3
1278
- after = devtools_websocket_messages(browser_obj: browser_obj)
1279
- method = after['method']
1280
- # puts after
1281
- puts "\n"
1282
-
1283
- if method == 'Debugger.paused'
1284
- after_location = after['params']['callFrames'].first['scopeChain'].first['object']
1285
- start_location = after['params']['callFrames'].first['scopeChain'].first['startLocation']
1286
- after_script_id = start_location['scriptId']
1287
- from_line_num = start_location['lineNumber']
1288
- from_column_num = start_location['columnNumber']
1289
-
1290
- end_location = after['params']['callFrames'].first['scopeChain'].first['endLocation']
1291
- to_line_num = end_location['lineNumber']
1292
- to_column_num = end_location['columnNumber']
1293
-
1294
- source_obj = devtools.debugger.get_script_source(script_id: after_script_id)
1295
- source_code = source_obj['result']['scriptSource']
1296
- # puts source_code
1297
- # gets
1298
-
1299
- source_lines = source_code.split("\n")
1300
- source_lines_str = source_lines[from_line_num..to_line_num].join("\n")
1301
- source_to_review = source_lines_str[from_column_num..to_column_num]
1302
-
1303
- puts source_to_review
1304
- request = source_lines_str[from_column_num..to_column_num]
1305
- ai_analysis = PWN::AI::Introspection.reflect_on(request: request)
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
+ )
1306
1418
  puts "^^^ #{ai_analysis}" unless ai_analysis.nil?
1307
- # gets
1308
1419
  end
1309
- puts "\n" * 6
1420
+ puts "\n" * 3
1310
1421
 
1311
- # step_hash = {
1312
- # step: step_num,
1313
- # action: action,
1314
- # before: before,
1315
- # after: after,
1316
- # diff: diff.to_s(:text)
1317
- # }
1422
+ step_hash = {
1423
+ step: step_num,
1424
+ action: action,
1425
+ source: current_step
1426
+ }
1318
1427
 
1319
- # page_state_arr.push(step_hash)
1428
+ steps_arr.push(step_hash)
1320
1429
  end
1321
1430
 
1322
- devtools
1431
+ steps_arr
1323
1432
  rescue Selenium::WebDriver::Error::WebDriverError
1324
1433
  devtools
1325
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.493'
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.493
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
@@ -1079,28 +1079,28 @@ dependencies:
1079
1079
  requirements:
1080
1080
  - - '='
1081
1081
  - !ruby/object:Gem::Version
1082
- version: 0.141.0
1082
+ version: 0.142.0
1083
1083
  type: :runtime
1084
1084
  prerelease: false
1085
1085
  version_requirements: !ruby/object:Gem::Requirement
1086
1086
  requirements:
1087
1087
  - - '='
1088
1088
  - !ruby/object:Gem::Version
1089
- version: 0.141.0
1089
+ version: 0.142.0
1090
1090
  - !ruby/object:Gem::Dependency
1091
1091
  name: selenium-webdriver
1092
1092
  requirement: !ruby/object:Gem::Requirement
1093
1093
  requirements:
1094
1094
  - - '='
1095
1095
  - !ruby/object:Gem::Version
1096
- version: 4.37.0
1096
+ version: 4.38.0
1097
1097
  type: :runtime
1098
1098
  prerelease: false
1099
1099
  version_requirements: !ruby/object:Gem::Requirement
1100
1100
  requirements:
1101
1101
  - - '='
1102
1102
  - !ruby/object:Gem::Version
1103
- version: 4.37.0
1103
+ version: 4.38.0
1104
1104
  - !ruby/object:Gem::Dependency
1105
1105
  name: slack-ruby-client
1106
1106
  requirement: !ruby/object:Gem::Requirement