pwn 0.5.351 → 0.5.353

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/README.md +3 -3
  3. data/bin/pwn_fuzz_net_app_proto +4 -3
  4. data/bin/pwn_phone +1 -2
  5. data/bin/pwn_sast +1 -2
  6. data/bin/pwn_www_uri_buster +1 -2
  7. data/lib/pwn/plugins/burp_suite.rb +15 -18
  8. data/lib/pwn/plugins/git.rb +3 -3
  9. data/lib/pwn/plugins/sock.rb +2 -2
  10. data/lib/pwn/reports/fuzz.rb +69 -24
  11. data/lib/pwn/reports/phone.rb +82 -23
  12. data/lib/pwn/reports/sast.rb +95 -45
  13. data/lib/pwn/reports/uri_buster.rb +79 -23
  14. data/lib/pwn/sast/amqp_connect_as_guest.rb +2 -2
  15. data/lib/pwn/sast/apache_file_system_util_api.rb +2 -2
  16. data/lib/pwn/sast/aws.rb +2 -2
  17. data/lib/pwn/sast/banned_function_calls_c.rb +2 -2
  18. data/lib/pwn/sast/base64.rb +2 -2
  19. data/lib/pwn/sast/beef_hook.rb +2 -2
  20. data/lib/pwn/sast/cmd_execution_java.rb +2 -2
  21. data/lib/pwn/sast/cmd_execution_python.rb +2 -2
  22. data/lib/pwn/sast/cmd_execution_ruby.rb +2 -2
  23. data/lib/pwn/sast/cmd_execution_scala.rb +2 -2
  24. data/lib/pwn/sast/csrf.rb +2 -2
  25. data/lib/pwn/sast/deserial_java.rb +2 -2
  26. data/lib/pwn/sast/emoticon.rb +2 -2
  27. data/lib/pwn/sast/eval.rb +2 -2
  28. data/lib/pwn/sast/factory.rb +2 -2
  29. data/lib/pwn/sast/http_authorization_header.rb +2 -2
  30. data/lib/pwn/sast/inner_html.rb +2 -2
  31. data/lib/pwn/sast/keystore.rb +2 -2
  32. data/lib/pwn/sast/local_storage.rb +2 -2
  33. data/lib/pwn/sast/location_hash.rb +2 -2
  34. data/lib/pwn/sast/log4j.rb +2 -2
  35. data/lib/pwn/sast/logger.rb +2 -2
  36. data/lib/pwn/sast/md5.rb +2 -2
  37. data/lib/pwn/sast/outer_html.rb +2 -2
  38. data/lib/pwn/sast/padding_oracle.rb +2 -2
  39. data/lib/pwn/sast/password.rb +2 -2
  40. data/lib/pwn/sast/php_input_mechanisms.rb +2 -2
  41. data/lib/pwn/sast/php_type_juggling.rb +2 -2
  42. data/lib/pwn/sast/pom_version.rb +2 -2
  43. data/lib/pwn/sast/port.rb +2 -2
  44. data/lib/pwn/sast/post_message.rb +2 -2
  45. data/lib/pwn/sast/private_key.rb +2 -2
  46. data/lib/pwn/sast/redirect.rb +2 -2
  47. data/lib/pwn/sast/redos.rb +2 -2
  48. data/lib/pwn/sast/shell.rb +2 -2
  49. data/lib/pwn/sast/signature.rb +2 -2
  50. data/lib/pwn/sast/sql.rb +2 -2
  51. data/lib/pwn/sast/ssl.rb +2 -2
  52. data/lib/pwn/sast/sudo.rb +2 -2
  53. data/lib/pwn/sast/task_tag.rb +2 -2
  54. data/lib/pwn/sast/throw_errors.rb +3 -2
  55. data/lib/pwn/sast/token.rb +2 -2
  56. data/lib/pwn/sast/type_script_type_juggling.rb +2 -2
  57. data/lib/pwn/sast/version.rb +2 -2
  58. data/lib/pwn/sast/window_location_hash.rb +2 -2
  59. data/lib/pwn/version.rb +1 -1
  60. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb30be67441b28427458dfab64121bf4ed929841daf537e9be1d32c821f4a56
4
- data.tar.gz: b86d8a385472b4133452eaa74332c2320802f6f2da0035fe843382f6fb332ffc
3
+ metadata.gz: 520c14131726908d9abb9e798087982d6d85e97fee79a4072f57379d71b4ec81
4
+ data.tar.gz: 7352109003e96f827c4ba7b4a49be75217216d6450c12e8c30067832712c52a5
5
5
  SHA512:
6
- metadata.gz: ac5bff9fe638a5a82db943f209b78b8cf8ee8268a74b88f88aaaa0a9a7a528652fc9627615c908917b3d26bf5da64ff5d862ce946c508f5c0ab335c98365e13d
7
- data.tar.gz: 8f598af14329fa31bf86bd383baa00eebcff9690dde51746a9ba2bcf2ed2334e17ac395b536fafdeb3b65217f1f4bcf3685f54bfa2740d998b5189ef4ae9dc9f
6
+ metadata.gz: 4d69ceb6ad6da3d4c6534a3a009c8f9a9d3bdd6501f113b7bd5c3b30fb0bb6c2b5b2ad4578976bb3b51ae243a5e9ae006f0d1bfa5ff168a951cf791dd13eb0cb
7
+ data.tar.gz: 6d505d50654d71114a4c6691e3114229531cd59ff05d6bc047b7ad3df3fb2f650c91a224dd60a90333e1d7ad608c074e73918feebc647fc4c36fe8594e9f7d8e
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.351]:001 >>> PWN.help
40
+ pwn[v0.5.353]: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.351]:001 >>> PWN.help
55
+ pwn[v0.5.353]: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.351]:001 >>> PWN.help
65
+ pwn[v0.5.353]: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:
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'pwn'
5
- require 'optparse'
4
+ require 'htmlentities'
6
5
  require 'json'
6
+ require 'optparse'
7
+ require 'pwn'
7
8
 
8
9
  opts = {}
9
10
  OptionParser.new do |options|
@@ -93,7 +94,7 @@ else
93
94
  raise "PWN_PROVIDER env variable is not set to 'ruby-gem' or 'docker'"
94
95
  end
95
96
 
96
- dir_path = opts[:dir_path].to_s.scrub
97
+ dir_path = opts[:dir_path] ||= '.'
97
98
  target = opts[:target]
98
99
  port = opts[:port]
99
100
  protocol = opts[:protocol]
data/bin/pwn_phone CHANGED
@@ -80,8 +80,7 @@ begin
80
80
  seconds_to_record = opts[:seconds_to_record]
81
81
  baresip_bin = opts[:baresip_bin]
82
82
  sox_bin = opts[:sox_bin]
83
- session_root = opts[:session_root]
84
- session_root ||= Dir.pwd
83
+ session_root = opts[:session_root] ||= '.'
85
84
 
86
85
  # Optional Flag Variables
87
86
  randomize = opts[:randomize]
data/bin/pwn_sast CHANGED
@@ -53,8 +53,7 @@ begin
53
53
  green = "\e[32m"
54
54
  end_of_color = "\e[0m"
55
55
 
56
- dir_path = opts[:dir_path]
57
- dir_path ||= '.'
56
+ dir_path = opts[:dir_path] ||= '.'
58
57
 
59
58
  uri_source_root = opts[:uri_source_root].to_s.scrub
60
59
 
@@ -259,8 +259,7 @@ begin
259
259
 
260
260
  raise 'ERROR: Flags --include-response-codes and --exclude-response-codes cannot be used together.' if include_http_response_codes && exclude_http_response_codes
261
261
 
262
- dir_path = opts[:dir_path]
263
- dir_path ||= '.'
262
+ dir_path = opts[:dir_path] ||= '.'
264
263
 
265
264
  report_name = opts[:report_name]
266
265
  report_name ||= "#{parsed_target_url.host}-#{File.basename(wordlist)}-#{Time.now.strftime('%Y-%m-%d_%H-%M-%S')}"
@@ -486,6 +486,12 @@ module PWN
486
486
  end
487
487
 
488
488
  begin
489
+ # Construct HTTP request headers
490
+ request_headers = {
491
+ host: host
492
+ }
493
+ request_headers.merge!(additional_http_headers)
494
+
489
495
  # Combine path-level and operation-level parameters
490
496
  operation_parameters = operation[:parameters].is_a?(Array) ? operation[:parameters] : []
491
497
  all_parameters = path_parameters + operation_parameters
@@ -495,11 +501,18 @@ module PWN
495
501
  request_path = full_path.dup
496
502
  query_params = []
497
503
 
498
- all_parameters.each do |param|
504
+ operation.each do |param|
499
505
  next unless param.is_a?(Hash) && param[:name] && param[:in]
500
506
 
501
507
  param_name = param[:name].to_s
502
508
  case param[:in]
509
+ when 'header'
510
+ # Aggregate remaining HTTP header names from spec,
511
+ # reference as keys, and assign their respective
512
+ # values to the request_headers hash
513
+ param_key = param_name.downcase
514
+ param_value = param[:schema]&.dig(:example) || 'PLACEHOLDER'
515
+ request_headers[param_key] = param_value.to_s
503
516
  when 'path'
504
517
  # Substitute path parameter with a default value (e.g., 'PLACEHOLDER')
505
518
  param_value = param[:schema]&.dig(:example) || 'PLACEHOLDER'
@@ -507,29 +520,13 @@ module PWN
507
520
  when 'query'
508
521
  # Collect query parameters
509
522
  param_value = param[:schema]&.dig(:example) || 'PLACEHOLDER'
510
- query_params << "#{URI.encode_www_form_component(param_name)}=#{URI.encode_www_form_component(param_value.to_s)}"
523
+ query_params.push("#{URI.encode_www_form_component(param_name)}=#{URI.encode_www_form_component(param_value.to_s)}")
511
524
  end
512
525
  end
513
526
 
514
527
  # Append query parameters to path if any
515
528
  request_path += "?#{query_params.join('&')}" if query_params.any?
516
529
 
517
- # Construct HTTP request headers
518
- request_headers = {
519
- host: host
520
- }
521
- request_headers.merge!(additional_http_headers)
522
- # Aggregate remaining HTTP header names from spec,
523
- # reference as keys, and assign their respective
524
- # values to the request_headers hash
525
- operation[:parameters]&.each do |param|
526
- next unless param.is_a?(Hash) && param[:in] == 'header' && param[:name]
527
-
528
- header_name = param[:name].to_s.downcase
529
- header_value = param[:schema]&.dig(:example) || 'PLACEHOLDER'
530
- request_headers[header_name] = header_value.to_s
531
- end
532
-
533
530
  # Construct request lines, including all headers
534
531
  request_lines = [
535
532
  "#{method_str.upcase} #{request_path} HTTP/1.1"
@@ -27,7 +27,7 @@ module PWN
27
27
  git_entity = `git log --since #{since_date} --stat-width=65535 --graph`.to_s.scrub
28
28
  else
29
29
  git_pull_output << "<h3>#{git_repo_name}->#{git_repo_branch} Diff Summary Since Last Pull</h3>"
30
- git_entity = `git log ORIG_HEAD.. --stat-width=65535 --graph`.to_s.scrub
30
+ git_entity = `git log ORIG_HEAD.. --stat-width=65535 --graph 2> /dev/null`.to_s.scrub
31
31
  end
32
32
  # For debugging purposes
33
33
  @@logger.info(git_entity)
@@ -60,7 +60,7 @@ module PWN
60
60
  target_file.gsub!(%r{^#{repo_root}/}, '')
61
61
 
62
62
  if File.directory?(repo_root) && File.file?("#{repo_root}/#{target_file}")
63
- `git --git-dir="#{Shellwords.escape(repo_root)}/.git" log -L #{from_line},#{to_line}:"#{Shellwords.escape(target_file)}" | grep Author | head -n 1`.to_s.scrub
63
+ `git --git-dir="#{Shellwords.escape(repo_root)}/.git" log -L #{from_line},#{to_line}:"#{Shellwords.escape(target_file)}" 2> /dev/null | grep Author | head -n 1`.to_s.scrub
64
64
  else
65
65
  -1
66
66
  end
@@ -75,7 +75,7 @@ module PWN
75
75
 
76
76
  public_class_method def self.dump_all_repo_branches(opts = {})
77
77
  git_url = opts[:git_url].to_s.scrub
78
- `git ls-remote #{git_url}`.to_s.scrub
78
+ `git ls-remote #{git_url} 2> /dev/null`.to_s.scrub
79
79
  rescue StandardError => e
80
80
  raise e
81
81
  end
@@ -13,7 +13,7 @@ module PWN
13
13
  # sock_obj = PWN::Plugins::Sock.connect(
14
14
  # target: 'required - target host or ip',
15
15
  # port: 'required - target port',
16
- # protocol: 'optional - :tcp || :udp (defaults to tcp)',
16
+ # protocol: 'optional - :tcp || :udp (defaults to :tcp)',
17
17
  # tls: 'optional - boolean connect to target socket using TLS (defaults to false)'
18
18
  # )
19
19
 
@@ -31,7 +31,7 @@ module PWN
31
31
 
32
32
  tls_min_version = OpenSSL::SSL::TLS1_VERSION if tls_min_version.nil?
33
33
 
34
- case protocol
34
+ case protocol.to_s.to_sym
35
35
  when :tcp
36
36
  if tls
37
37
  sock = TCPSocket.open(target, port)
@@ -79,8 +79,8 @@ module PWN
79
79
  word-wrap: break-word !important;
80
80
  }
81
81
 
82
- .highlighted {
83
- background-color: #F2F5A9 !important;
82
+ tr.highlighted td {
83
+ background-color: #FFF396 !important;
84
84
  }
85
85
  </style>
86
86
 
@@ -98,7 +98,11 @@ module PWN
98
98
  &nbsp;~&nbsp;<a href="https://github.com/0dayinc/pwn/tree/master">pwn network fuzzer</a>
99
99
  </h1><br /><br />
100
100
 
101
- <div><button type="button" id="button">Rows Selected</button></div><br />
101
+ <div>
102
+ <!--<button type="button" id="button">Rows Selected</button>-->
103
+ <button type="button" id="export_selected">Export Selected to JSON</button>
104
+ </div><br />
105
+
102
106
  <div>
103
107
  <b>Toggle Column(s):</b>&nbsp;
104
108
  <a class="toggle-vis" data-column="1" href="#">Timestamp</a>&nbsp;|&nbsp;
@@ -161,23 +165,11 @@ module PWN
161
165
  $('html,body').animate({scrollTop: targetOffset}, 500);
162
166
  oldStart = oSettings._iDisplayStart;
163
167
  }
164
- // Select individual lines in a row
165
- $('#multi_line_select tbody').on('click', 'tr', function () {
166
- $(this).toggleClass('highlighted');
167
- if ($('#multi_line_select tr.highlighted').length > 0) {
168
- $('#multi_line_select tr td button').attr('disabled', 'disabled');
169
- // Remove multi-line bug button
170
- } else {
171
- $('#multi_line_select tr td button').removeAttr('disabled');
172
- // Add multi-line bug button
173
- }
174
- });
175
168
  },
176
169
  "ajax": "#{report_name}.json",
177
170
  //"deferRender": true,
178
171
  "dom": "fplitfpliS",
179
172
  "autoWidth": false,
180
- "fixedColumns": true,
181
173
  "columnDefs": [
182
174
  {
183
175
  targets: 3,
@@ -277,19 +269,72 @@ module PWN
277
269
  column.visible( ! column.visible() );
278
270
  });
279
271
 
280
- // TODO: Open bug for highlighted rows ;)
281
272
  $('#button').click( function () {
282
- alert($('#multi_line_select tr.highlighted').length +' row(s) highlighted');
273
+ alert($('.multi_line_select tr.highlighted').length +' row(s) highlighted');
283
274
  });
284
- });
285
275
 
286
- function multi_line_select() {
287
- // Select all lines in a row
288
- //$('#pwn_fuzz_net_app_proto tbody').on('click', 'tr', function () {
289
- // $(this).children('td').children('#multi_line_select').children('tbody').children('tr').toggleClass('highlighted');
290
- //});
276
+ $('#export_selected').click( function () {
277
+ if ($('.multi_line_select tr.highlighted').length === 0) {
278
+ alert('No rows selected');
279
+ return;
280
+ }
291
281
 
292
- }
282
+ $.getJSON(table.ajax.url(), function(original_json) {
283
+ var selected_results = {};
284
+
285
+ $('.multi_line_select tr.highlighted').each(function() {
286
+ var inner_tr = $(this);
287
+ var main_tr = inner_tr.closest('td').parent();
288
+ var row = table.row(main_tr);
289
+ var row_index = row.index();
290
+ var line_index = inner_tr.index();
291
+
292
+ if (selected_results[row_index] === undefined) {
293
+ selected_results[row_index] = {
294
+ row: row,
295
+ lines: []
296
+ };
297
+ }
298
+
299
+ selected_results[row_index].lines.push(line_index);
300
+ });
301
+
302
+ var new_data = [];
303
+
304
+ Object.keys(selected_results).forEach(function(ri) {
305
+ var sel = selected_results[ri];
306
+ var orig_row_data = sel.row.data();
307
+ var new_row_data = JSON.parse(JSON.stringify(orig_row_data));
308
+
309
+ sel.lines.sort((a, b) => a - b);
310
+ new_row_data.line_no_and_contents = sel.lines.map(function(li) {
311
+ return orig_row_data.line_no_and_contents[li];
312
+ });
313
+
314
+ new_row_data.raw_content = new_row_data.line_no_and_contents.map(l => l.contents).join('\\n');
315
+
316
+ new_data.push(new_row_data);
317
+ });
318
+
319
+ original_json.data = new_data;
320
+
321
+ if (original_json.report_name) {
322
+ original_json.report_name += '_selected';
323
+ }
324
+
325
+ var json_str = JSON.stringify(original_json, null, 2);
326
+ var blob = new Blob([json_str], { type: 'application/json' });
327
+ var url = URL.createObjectURL(blob);
328
+ var a = document.createElement('a');
329
+ a.href = url;
330
+ a.download = (original_json.report_name || 'selected') + '.json';
331
+ document.body.appendChild(a);
332
+ a.click();
333
+ document.body.removeChild(a);
334
+ URL.revokeObjectURL(url);
335
+ });
336
+ });
337
+ });
293
338
  </script>
294
339
  </body>
295
340
  </html>
@@ -71,8 +71,8 @@ module PWN
71
71
  word-wrap: break-word !important;
72
72
  }
73
73
 
74
- .highlighted {
75
- background-color: #F2F5A9 !important;
74
+ tr.highlighted td {
75
+ background-color: #FFF396 !important;
76
76
  }
77
77
  </style>
78
78
 
@@ -95,7 +95,11 @@ module PWN
95
95
  </h1><br /><br />
96
96
  <h2 id="report_name"></h2><br />
97
97
 
98
- <div><button type="button" id="button">Rows Selected</button></div><br />
98
+ <div>
99
+ <!--<button type="button" id="button">Rows Selected</button>-->
100
+ <button type="button" id="export_selected">Export Selected to JSON</button>
101
+ </div><br />
102
+
99
103
  <div>
100
104
  <b>Toggle Column(s):</b>&nbsp;
101
105
  <a class="toggle-vis" data-column="1" href="#">Call Started</a>&nbsp;|&nbsp;
@@ -136,6 +140,19 @@ module PWN
136
140
  <th>Waveform</th>
137
141
  </tr>
138
142
  </thead>
143
+ <col width="30px" />
144
+ <col width="60px" />
145
+ <col width="60px" />
146
+ <col width="90px" />
147
+ <col width="60px" />
148
+ <col width="30px" />
149
+ <col width="30px" />
150
+ <col width="60px" />
151
+ <col width="300px" />
152
+ <col width="90px" />
153
+ <col width="300px" />
154
+ <col width="300px" />
155
+ <col width="300px" />
139
156
  <!-- DataTables <tbody> -->
140
157
  </table>
141
158
  </div>
@@ -162,17 +179,6 @@ module PWN
162
179
  $('html,body').animate({scrollTop: targetOffset}, 500);
163
180
  oldStart = oSettings._iDisplayStart;
164
181
  }
165
- // Select individual lines in a row
166
- $('#multi_line_select tbody').on('click', 'tr', function () {
167
- $(this).toggleClass('highlighted');
168
- if ($('#multi_line_select tr.highlighted').length > 0) {
169
- $('#multi_line_select tr td button').attr('disabled', 'disabled');
170
- // Remove multi-line bug button
171
- } else {
172
- $('#multi_line_select tr td button').removeAttr('disabled');
173
- // Add multi-line bug button
174
- }
175
- });
176
182
  },
177
183
  "ajax": "#{report_name}.json",
178
184
  //"deferRender": true,
@@ -318,19 +324,72 @@ module PWN
318
324
  column.visible( ! column.visible() );
319
325
  });
320
326
 
321
- // TODO: Open bug for highlighted rows ;)
322
327
  $('#button').click( function () {
323
- alert($('#multi_line_select tr.highlighted').length +' row(s) highlighted');
328
+ alert($('.multi_line_select tr.highlighted').length +' row(s) highlighted');
324
329
  });
325
- });
326
330
 
327
- function multi_line_select() {
328
- // Select all lines in a row
329
- //$('#pwn_phone_results tbody').on('click', 'tr', function () {
330
- // $(this).children('td').children('#multi_line_select').children('tbody').children('tr').toggleClass('highlighted');
331
- //});
331
+ $('#export_selected').click( function () {
332
+ if ($('.multi_line_select tr.highlighted').length === 0) {
333
+ alert('No rows selected');
334
+ return;
335
+ }
332
336
 
333
- }
337
+ $.getJSON(table.ajax.url(), function(original_json) {
338
+ var selected_results = {};
339
+
340
+ $('.multi_line_select tr.highlighted').each(function() {
341
+ var inner_tr = $(this);
342
+ var main_tr = inner_tr.closest('td').parent();
343
+ var row = table.row(main_tr);
344
+ var row_index = row.index();
345
+ var line_index = inner_tr.index();
346
+
347
+ if (selected_results[row_index] === undefined) {
348
+ selected_results[row_index] = {
349
+ row: row,
350
+ lines: []
351
+ };
352
+ }
353
+
354
+ selected_results[row_index].lines.push(line_index);
355
+ });
356
+
357
+ var new_data = [];
358
+
359
+ Object.keys(selected_results).forEach(function(ri) {
360
+ var sel = selected_results[ri];
361
+ var orig_row_data = sel.row.data();
362
+ var new_row_data = JSON.parse(JSON.stringify(orig_row_data));
363
+
364
+ sel.lines.sort((a, b) => a - b);
365
+ new_row_data.line_no_and_contents = sel.lines.map(function(li) {
366
+ return orig_row_data.line_no_and_contents[li];
367
+ });
368
+
369
+ new_row_data.raw_content = new_row_data.line_no_and_contents.map(l => l.contents).join('\\n');
370
+
371
+ new_data.push(new_row_data);
372
+ });
373
+
374
+ original_json.data = new_data;
375
+
376
+ if (original_json.report_name) {
377
+ original_json.report_name += '_selected';
378
+ }
379
+
380
+ var json_str = JSON.stringify(original_json, null, 2);
381
+ var blob = new Blob([json_str], { type: 'application/json' });
382
+ var url = URL.createObjectURL(blob);
383
+ var a = document.createElement('a');
384
+ a.href = url;
385
+ a.download = (original_json.report_name || 'selected') + '.json';
386
+ document.body.appendChild(a);
387
+ a.click();
388
+ document.body.removeChild(a);
389
+ URL.revokeObjectURL(url);
390
+ });
391
+ });
392
+ });
334
393
  </script>
335
394
  </body>
336
395
  </html>