pwn 0.5.429 → 0.5.430
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/bin/pwn +4 -4
- data/bin/pwn_sast +20 -48
- data/etc/pwn.yaml.EXAMPLE +3 -0
- data/lib/pwn/ai/grok.rb +7 -0
- data/lib/pwn/ai/ollama.rb +7 -0
- data/lib/pwn/ai/open_ai.rb +7 -0
- data/lib/pwn/config.rb +81 -0
- data/lib/pwn/plugins/repl.rb +11 -30
- data/lib/pwn/plugins/vault.rb +1 -84
- data/lib/pwn/reports/sast.rb +45 -55
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn.rb +5 -2
- data/spec/lib/pwn/config_spec.rb +10 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eecc95f7ab850f845600203b3fed19bb77d5d3737233a7f7f98df09b84154737
|
4
|
+
data.tar.gz: d838e72b716e9fc50e604a95600f1c9e43db9a42772e76073adc2f4cd673e96b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81dea57ae53b6401b7b5fa2784d5dc64e8b2a311ad1fc89c20c18e1024739ed66502db92be54ece95695df3b7a3fb25bbacd3aa311ac69d71bf5c2f4b23496c6
|
7
|
+
data.tar.gz: 071b3ac7ac65e4f282e3f59d223ec4c68c2f9b61b62442e613b4aa347ed6ab836cca10eafee0a148b79446c2adaf3e1a5bf60107b4871165d04d3017ef7b2fb3
|
data/README.md
CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
|
|
37
37
|
$ ./install.sh
|
38
38
|
$ ./install.sh ruby-gem
|
39
39
|
$ pwn
|
40
|
-
pwn[v0.5.
|
40
|
+
pwn[v0.5.430]:001 >>> PWN.help
|
41
41
|
```
|
42
42
|
|
43
43
|
[](https://youtu.be/G7iLUY4FzsI)
|
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
|
|
52
52
|
$ gem uninstall --all --executables pwn
|
53
53
|
$ gem install --verbose pwn
|
54
54
|
$ pwn
|
55
|
-
pwn[v0.5.
|
55
|
+
pwn[v0.5.430]:001 >>> PWN.help
|
56
56
|
```
|
57
57
|
|
58
58
|
If you're using a multi-user install of RVM do:
|
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
|
|
62
62
|
$ rvmsudo gem uninstall --all --executables pwn
|
63
63
|
$ rvmsudo gem install --verbose pwn
|
64
64
|
$ pwn
|
65
|
-
pwn[v0.5.
|
65
|
+
pwn[v0.5.430]: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:
|
data/bin/pwn
CHANGED
@@ -10,12 +10,12 @@ OptionParser.new do |options|
|
|
10
10
|
#{File.basename($PROGRAM_NAME)} [opts]
|
11
11
|
"
|
12
12
|
|
13
|
-
options.on('-
|
14
|
-
opts[:
|
13
|
+
options.on('-YPATH', '--pwn-config=PATH', '<Optional - PWN YAML File>') do |p|
|
14
|
+
opts[:pwn_config_path] = p
|
15
15
|
end
|
16
16
|
|
17
|
-
options.on('-
|
18
|
-
opts[:
|
17
|
+
options.on('-ZPATH', '--pwn-decryptor=PATH', '<Optional - Out-of-Band YAML File with :key && :iv>') do |d|
|
18
|
+
opts[:pwn_decryptor_path] = d
|
19
19
|
end
|
20
20
|
end.parse!
|
21
21
|
|
data/bin/pwn_sast
CHANGED
@@ -11,6 +11,15 @@ OptionParser.new do |options|
|
|
11
11
|
#{File.basename($PROGRAM_NAME)} [opts]
|
12
12
|
"
|
13
13
|
|
14
|
+
# TODO: HOW TO LOAD THE pwn.yaml CONFIGURATION FILE FOR EVERYTHING UNLESS OVERRIDDEN???
|
15
|
+
options.on('-YPATH', '--pwn-config=PATH', '<Optional - PWN YAML File>') do |p|
|
16
|
+
opts[:pwn_config_path] = p
|
17
|
+
end
|
18
|
+
|
19
|
+
options.on('-ZPATH', '--pwn-decryptor=PATH', '<Optional - Out-of-Band YAML File with :key && :iv>') do |d|
|
20
|
+
opts[:pwn_decryptor_path] = d
|
21
|
+
end
|
22
|
+
|
14
23
|
options.on('-uGITURI', '--uri-source-root=GITURI', '<Required - HTTP URI of Git Repo Scanned e.g. https://github.com/0dayInc/pwn/tree/master>') do |u|
|
15
24
|
opts[:uri_source_root] = u
|
16
25
|
end
|
@@ -35,30 +44,6 @@ OptionParser.new do |options|
|
|
35
44
|
opts[:report_name] = n
|
36
45
|
end
|
37
46
|
|
38
|
-
options.on('-aENGINE', '--ai-engine=ENGINE', '<Optional AI Engine to Analyze Results (grok, ollama, openai) [WARNING: CAN DRAMATICALLY INCREASE AI USAGE COSTS AND/OR SCAN DURATIONS]>') do |a|
|
39
|
-
opts[:ai_engine] = a
|
40
|
-
end
|
41
|
-
|
42
|
-
options.on('-bURI', '--ai-base-uri=URI', '<Optional AI Base API URI (Only Required for "ollama" AI Engine. Supported by other LLMs when hosted privately)>') do |b|
|
43
|
-
opts[:ai_base_uri] = b
|
44
|
-
end
|
45
|
-
|
46
|
-
options.on('-mMODEL', '--ai-model=MODEL', '<Optional AI Model to Use for Respective AI Engine (e.g., grok-4-0709, grok-3-mini-fast, gpt5-chat-latest, chargpt-4o-latest, llama-3.1, etc.)>') do |m|
|
47
|
-
opts[:ai_model] = m
|
48
|
-
end
|
49
|
-
|
50
|
-
options.on('-kTOKEN', '--ai-key=TOKEN', '<Optional AI Key/Token for Respective AI Engine>') do |k|
|
51
|
-
opts[:ai_key] = k
|
52
|
-
end
|
53
|
-
|
54
|
-
options.on('-SCONTENT', '--ai-system-content=CONTENT', '<Optional AI System Role Content for Respective AI Engine (Defaults to, "Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.")>') do |s|
|
55
|
-
opts[:ai_system_role_content] = s
|
56
|
-
end
|
57
|
-
|
58
|
-
options.on('-FTEMP', '--ai-temp=TEMP', '<Optional AI Temperature for Respective AI Engine (Default 0.9)>') do |t|
|
59
|
-
opts[:ai_temp] = t
|
60
|
-
end
|
61
|
-
|
62
47
|
options.on('-s', '--[no-]start-reporting-server', '<Optional - Start Simple HTTP Server for Reporting>') do |s|
|
63
48
|
opts[:start_reporting_server] = s
|
64
49
|
end
|
@@ -71,6 +56,16 @@ end
|
|
71
56
|
|
72
57
|
begin
|
73
58
|
timestamp = Time.now.strftime('%Y-%m-%d_%H:%M:%S%z')
|
59
|
+
|
60
|
+
pwn_config_path = opts[:pwn_config_path]
|
61
|
+
pwn_decryptor_path = opts[:pwn_decryptor_path]
|
62
|
+
if pwn_config_path
|
63
|
+
PWN::Config.refresh(
|
64
|
+
pwn_config_path: pwn_config_path,
|
65
|
+
pwn_decryptor_path: pwn_decryptor_path
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
74
69
|
previous_dir = Dir.pwd
|
75
70
|
|
76
71
|
pwn_provider = 'ruby-gem'
|
@@ -95,23 +90,6 @@ begin
|
|
95
90
|
report_name ||= "#{File.basename(Dir.pwd)}-#{timestamp}" if dir_path == '.'
|
96
91
|
report_name ||= "#{File.basename(dir_path)}-#{timestamp}" unless dir_path == '.'
|
97
92
|
|
98
|
-
ai_engine = opts[:ai_engine]
|
99
|
-
if ai_engine
|
100
|
-
ai_engine = ai_engine.to_s.to_sym
|
101
|
-
valid_ai_engines = %i[grok ollama openai]
|
102
|
-
raise "ERROR: Invalid AI Engine. Valid options are: #{valid_ai_engines.join(', ')}" unless valid_ai_engines.include?(ai_engine)
|
103
|
-
|
104
|
-
ai_base_uri = opts[:ai_base_uri]
|
105
|
-
raise 'ERROR: --base-uri Parameter for Ollama AI engine is required.' if ai_engine == :ollama && ai_base_uri.nil?
|
106
|
-
|
107
|
-
ai_model = opts[:ai_model]
|
108
|
-
raise 'ERROR: AI Model is required for AI engine ollama.' if ai_engine == :ollama && ai_model.nil?
|
109
|
-
|
110
|
-
ai_key = opts[:ai_key] ||= PWN::Plugins::AuthenticationHelper.mask_password(prompt: "#{ai_engine} Token")
|
111
|
-
ai_system_role_content = opts[:ai_system_role_content]
|
112
|
-
ai_temp = opts[:ai_temp]
|
113
|
-
end
|
114
|
-
|
115
93
|
start_reporting_server = opts[:start_reporting_server]
|
116
94
|
|
117
95
|
# Define Test Cases to Run & Start Thread Pool
|
@@ -202,13 +180,7 @@ begin
|
|
202
180
|
PWN::Reports::SAST.generate(
|
203
181
|
dir_path: dir_path,
|
204
182
|
results_hash: results_hash,
|
205
|
-
report_name: report_name
|
206
|
-
ai_engine: ai_engine,
|
207
|
-
ai_model: ai_model,
|
208
|
-
ai_key: ai_key,
|
209
|
-
ai_base_uri: ai_base_uri,
|
210
|
-
ai_system_role_content: ai_system_role_content,
|
211
|
-
ai_temp: ai_temp
|
183
|
+
report_name: report_name
|
212
184
|
)
|
213
185
|
puts 'complete.'
|
214
186
|
|
data/etc/pwn.yaml.EXAMPLE
CHANGED
@@ -2,6 +2,9 @@
|
|
2
2
|
# Use PWN::Plugins::Vault.create(file: 'pwn.yaml') to encrypt this file
|
3
3
|
|
4
4
|
# ai_engine: 'openai' || 'ollama'
|
5
|
+
# WARNING::
|
6
|
+
# If PWN::CONFIG[:ai][:instrospection] = true, the active ai agent will be used
|
7
|
+
# wherever possible throughout pwn. Proceeds with caution, as this may incur additional costs
|
5
8
|
ai:
|
6
9
|
active: 'grok'
|
7
10
|
introspection: false
|
data/lib/pwn/ai/grok.rb
CHANGED
@@ -25,6 +25,13 @@ module PWN
|
|
25
25
|
|
26
26
|
private_class_method def self.grok_rest_call(opts = {})
|
27
27
|
token = opts[:token]
|
28
|
+
if token.nil?
|
29
|
+
PWN::Plugins::AuthenticationHelper.mask_password(
|
30
|
+
prompt: 'Grok Key'
|
31
|
+
)
|
32
|
+
PWN::CONFIG[:ai][:grok][:key] = token
|
33
|
+
end
|
34
|
+
|
28
35
|
http_method = if opts[:http_method].nil?
|
29
36
|
:get
|
30
37
|
else
|
data/lib/pwn/ai/ollama.rb
CHANGED
@@ -27,6 +27,13 @@ module PWN
|
|
27
27
|
private_class_method def self.ollama_rest_call(opts = {})
|
28
28
|
base_uri = opts[:base_uri]
|
29
29
|
token = opts[:token]
|
30
|
+
if token.nil?
|
31
|
+
PWN::Plugins::AuthenticationHelper.mask_password(
|
32
|
+
prompt: 'Ollama Key'
|
33
|
+
)
|
34
|
+
PWN::CONFIG[:ai][:ollama][:key] = token
|
35
|
+
end
|
36
|
+
|
30
37
|
http_method = if opts[:http_method].nil?
|
31
38
|
:get
|
32
39
|
else
|
data/lib/pwn/ai/open_ai.rb
CHANGED
@@ -26,6 +26,13 @@ module PWN
|
|
26
26
|
|
27
27
|
private_class_method def self.open_ai_rest_call(opts = {})
|
28
28
|
token = opts[:token]
|
29
|
+
if token.nil?
|
30
|
+
PWN::Plugins::AuthenticationHelper.mask_password(
|
31
|
+
prompt: 'OpenAI Key'
|
32
|
+
)
|
33
|
+
PWN::CONFIG[:ai][:openai][:key] = token
|
34
|
+
end
|
35
|
+
|
29
36
|
http_method = if opts[:http_method].nil?
|
30
37
|
:get
|
31
38
|
else
|
data/lib/pwn/config.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
module PWN
|
7
|
+
# Used to manage PWN configuration settings within PWN drivers.
|
8
|
+
module Config
|
9
|
+
# Supported Method Parameters::
|
10
|
+
# PWN::Config.refresh(
|
11
|
+
# pwn_config_path: 'optional - Path to pwn.yaml file. Defaults to ~/.pwn/pwn.yaml',
|
12
|
+
# pwn_decryptor_path: 'optional - Path to pwn.decryptor.yaml file. Defaults to ~/.pwn/pwn.decryptor.yaml'
|
13
|
+
# )
|
14
|
+
|
15
|
+
public_class_method def self.refresh(opts = {})
|
16
|
+
pwn_config_root = "#{Dir.home}/.pwn"
|
17
|
+
FileUtils.mkdir_p(pwn_config_root)
|
18
|
+
|
19
|
+
pwn_config_path = opts[:pwn_config_path] ||= "#{pwn_config_root}/pwn.yaml"
|
20
|
+
raise "PWN Config (#{pwn_config_path}) does not exist!" unless File.exist?(pwn_config_path)
|
21
|
+
|
22
|
+
is_encrypted = PWN::Plugins::Vault.file_encrypted?(file: pwn_config_path)
|
23
|
+
|
24
|
+
if is_encrypted
|
25
|
+
pwn_decryptor_path = opts[:pwn_decryptor_path] ||= "#{pwn_config_root}/pwn.decryptor.yaml"
|
26
|
+
raise "PWN Decryptor (#{pwn_decryptor_path}) does not exist!" unless File.exist?(pwn_decryptor_path)
|
27
|
+
|
28
|
+
pwn_decryptor = YAML.load_file(pwn_decryptor_path, symbolize_names: true)
|
29
|
+
|
30
|
+
key = opts[:key] ||= pwn_decryptor[:key] ||= ENV.fetch('PWN_DECRYPTOR_KEY')
|
31
|
+
key = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption Key') if key.nil?
|
32
|
+
|
33
|
+
iv = opts[:iv] ||= pwn_decryptor[:iv] ||= ENV.fetch('PWN_DECRYPTOR_IV')
|
34
|
+
iv = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption IV') if iv.nil?
|
35
|
+
|
36
|
+
config = PWN::Plugins::Vault.dump(
|
37
|
+
file: pwn_config_path,
|
38
|
+
key: key,
|
39
|
+
iv: iv
|
40
|
+
)
|
41
|
+
else
|
42
|
+
config = YAML.load_file(pwn_config_path, symbolize_names: true)
|
43
|
+
end
|
44
|
+
|
45
|
+
valid_ai_engines = %i[
|
46
|
+
grok
|
47
|
+
openai
|
48
|
+
ollama
|
49
|
+
]
|
50
|
+
|
51
|
+
engine = config[:ai][:active].to_s.downcase.to_sym
|
52
|
+
raise "ERROR: Unsupported AI Engine: #{engine} in #{pwn_config_path}. Supported AI Engines:\n#{valid_ai_engines.inspect}" unless valid_ai_engines.include?(engine)
|
53
|
+
|
54
|
+
model = config[:ai][engine][:model]
|
55
|
+
system_role_content = config[:ai][engine][:system_role_content]
|
56
|
+
|
57
|
+
# Reset the ai response history on config refresh
|
58
|
+
config[:ai][engine][:response_history] = {
|
59
|
+
id: '',
|
60
|
+
object: '',
|
61
|
+
model: model,
|
62
|
+
usage: {},
|
63
|
+
choices: [
|
64
|
+
{
|
65
|
+
role: 'system',
|
66
|
+
content: system_role_content
|
67
|
+
}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
|
71
|
+
# These two lines should be immutable for the session
|
72
|
+
config[:pwn_config_path] = pwn_config_path
|
73
|
+
config[:pwn_decryptor_path] = pwn_decryptor_path if is_encrypted
|
74
|
+
|
75
|
+
Pry.config.refresh = false if defined?(Pry)
|
76
|
+
|
77
|
+
PWN.send(:remove_const, :CONFIG) if PWN.const_defined?(:CONFIG)
|
78
|
+
PWN.const_set(:CONFIG, config)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/pwn/plugins/repl.rb
CHANGED
@@ -18,11 +18,7 @@ module PWN
|
|
18
18
|
mode = opts[:mode]
|
19
19
|
|
20
20
|
proc do |_target_self, _nest_level, pi|
|
21
|
-
if Pry.config.refresh
|
22
|
-
# puts "Refreshing PWN env via #{opts[:yaml_config_path]}"
|
23
|
-
opts[:pi] = pi
|
24
|
-
PWN::Plugins::Vault.refresh_config(opts)
|
25
|
-
end
|
21
|
+
pi.config.pwn = PWN::Config.refresh(opts) if Pry.config.refresh
|
26
22
|
|
27
23
|
pi.config.pwn_repl_line += 1
|
28
24
|
line_pad = format(
|
@@ -464,24 +460,24 @@ module PWN
|
|
464
460
|
|
465
461
|
def process
|
466
462
|
pi = pry_instance
|
467
|
-
|
468
|
-
unless File.exist?(
|
469
|
-
puts "ERROR: pwn.yaml not found: #{
|
463
|
+
pwn_config_path = pi.config.pwn_config_path ||= "#{Dir.home}/.pwn/pwn.yaml"
|
464
|
+
unless File.exist?(pwn_config_path)
|
465
|
+
puts "ERROR: pwn.yaml not found: #{pwn_config_path}"
|
470
466
|
return
|
471
467
|
end
|
472
468
|
|
473
|
-
|
474
|
-
unless File.exist?(
|
475
|
-
puts "ERROR: pwn.decryptor.yaml not found: #{
|
469
|
+
pwn_decryptor_path = pi.config.pwn_decryptor_path ||= "#{Dir.home}/.pwn/pwn.decryptor.yaml"
|
470
|
+
unless File.exist?(pwn_decryptor_path)
|
471
|
+
puts "ERROR: pwn.decryptor.yaml not found: #{pwn_decryptor_path}"
|
476
472
|
return
|
477
473
|
end
|
478
474
|
|
479
|
-
decryptor = YAML.load_file(
|
475
|
+
decryptor = YAML.load_file(pwn_decryptor_path, symbolize_names: true)
|
480
476
|
key = decryptor[:key]
|
481
477
|
iv = decryptor[:iv]
|
482
478
|
|
483
479
|
PWN::Plugins::Vault.edit(
|
484
|
-
file:
|
480
|
+
file: pwn_config_path,
|
485
481
|
key: key,
|
486
482
|
iv: iv
|
487
483
|
)
|
@@ -539,9 +535,7 @@ module PWN
|
|
539
535
|
|
540
536
|
# Initialize pwn.yaml Configuration using :before_session Hook
|
541
537
|
Pry.config.hooks.add_hook(:before_session, :init_opts) do |_output, _binding, pi|
|
542
|
-
|
543
|
-
opts[:pi] = pi
|
544
|
-
PWN::Plugins::Vault.refresh_config(opts)
|
538
|
+
pi.config.pwn = PWN::Config.refresh(opts)
|
545
539
|
end
|
546
540
|
|
547
541
|
Pry.config.hooks.add_hook(:after_read, :pwn_asm_hook) do |request, pi|
|
@@ -588,13 +582,6 @@ module PWN
|
|
588
582
|
engine = pi.config.pwn[:ai][:active].to_s.downcase.to_sym
|
589
583
|
base_uri = pi.config.pwn[:ai][engine][:base_uri]
|
590
584
|
key = pi.config.pwn[:ai][engine][:key] ||= ''
|
591
|
-
if key.empty?
|
592
|
-
key = PWN::Plugins::AuthenticationHelper.mask_password(
|
593
|
-
prompt: 'pwn-ai Key'
|
594
|
-
)
|
595
|
-
pi.config.pwn[:ai][engine][:key] = key
|
596
|
-
end
|
597
|
-
|
598
585
|
response_history = pi.config.pwn[:ai][engine][:response_history]
|
599
586
|
speak_answer = pi.config.pwn_ai_speak
|
600
587
|
model = pi.config.pwn[:ai][engine][:model]
|
@@ -683,16 +670,10 @@ module PWN
|
|
683
670
|
public_class_method def self.start(opts = {})
|
684
671
|
# Monkey Patch Pry, add commands, && hooks
|
685
672
|
PWN::Plugins::MonkeyPatch.pry
|
686
|
-
add_commands
|
687
|
-
|
688
673
|
pwn_config_root = "#{Dir.home}/.pwn"
|
689
|
-
FileUtils.mkdir_p(pwn_config_root)
|
690
|
-
|
691
674
|
Pry.config.history_file = "#{pwn_config_root}/pwn_history"
|
692
675
|
|
693
|
-
|
694
|
-
opts[:yaml_decryptor_path] ||= "#{pwn_config_root}/pwn.decryptor.yaml"
|
695
|
-
|
676
|
+
add_commands
|
696
677
|
add_hooks(opts)
|
697
678
|
|
698
679
|
# Define PS1 Prompt
|
data/lib/pwn/plugins/vault.rb
CHANGED
@@ -15,7 +15,7 @@ module PWN
|
|
15
15
|
# iv: 'required - iv to decrypt'
|
16
16
|
# )
|
17
17
|
|
18
|
-
def self.refresh_encryption_secrets(opts = {})
|
18
|
+
public_class_method def self.refresh_encryption_secrets(opts = {})
|
19
19
|
file = opts[:file].to_s.scrub if File.exist?(opts[:file].to_s.scrub)
|
20
20
|
key = opts[:key]
|
21
21
|
iv = opts[:iv]
|
@@ -234,83 +234,6 @@ module PWN
|
|
234
234
|
raise e
|
235
235
|
end
|
236
236
|
|
237
|
-
# Supported Method Parameters::
|
238
|
-
# PWN::Plugins::Vault.refresh_config(
|
239
|
-
# yaml_config_path: 'required - full path to pwn.yaml file',
|
240
|
-
# pi: 'optional - Pry instance (default: Pry)',
|
241
|
-
# yaml_decryptor_path: 'optional - full path to decryption YAML file'
|
242
|
-
# )
|
243
|
-
public_class_method def self.refresh_config(opts = {})
|
244
|
-
yaml_config_path = opts[:yaml_config_path]
|
245
|
-
|
246
|
-
return false unless File.exist?(yaml_config_path)
|
247
|
-
|
248
|
-
pi = opts[:pi]
|
249
|
-
raise 'ERROR: Pry instance is required.' if pi.nil?
|
250
|
-
|
251
|
-
is_encrypted = PWN::Plugins::Vault.file_encrypted?(file: yaml_config_path)
|
252
|
-
|
253
|
-
if is_encrypted
|
254
|
-
# TODO: Implement "something you know, something you have, && something you are?"
|
255
|
-
yaml_decryptor_path = opts[:yaml_decryptor_path] ||= "#{Dir.home}/.pwn/pwn.decryptor.yaml"
|
256
|
-
raise "ERROR: #{yaml_decryptor_path} does not exist." unless File.exist?(yaml_decryptor_path)
|
257
|
-
|
258
|
-
yaml_decryptor = YAML.load_file(yaml_decryptor_path, symbolize_names: true)
|
259
|
-
|
260
|
-
key = opts[:key] ||= yaml_decryptor[:key] ||= ENV.fetch('PWN_DECRYPTOR_KEY')
|
261
|
-
key = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption Key') if key.nil?
|
262
|
-
|
263
|
-
iv = opts[:iv] ||= yaml_decryptor[:iv] ||= ENV.fetch('PWN_DECRYPTOR_IV')
|
264
|
-
iv = PWN::Plugins::AuthenticationHelper.mask_password(prompt: 'Decryption IV') if iv.nil?
|
265
|
-
|
266
|
-
yaml_config = PWN::Plugins::Vault.dump(
|
267
|
-
file: yaml_config_path,
|
268
|
-
key: key,
|
269
|
-
iv: iv
|
270
|
-
)
|
271
|
-
else
|
272
|
-
yaml_config = YAML.load_file(yaml_config_path, symbolize_names: true)
|
273
|
-
end
|
274
|
-
|
275
|
-
valid_ai_engines = %i[
|
276
|
-
grok
|
277
|
-
openai
|
278
|
-
ollama
|
279
|
-
]
|
280
|
-
|
281
|
-
# Convert ai_engine to symbol and downcase to ensure stability
|
282
|
-
pi.config.pwn = yaml_config
|
283
|
-
engine = pi.config.pwn[:ai][:active].to_s.downcase.to_sym
|
284
|
-
raise "ERROR: Unsupported AI Engine: #{engine} in #{yaml_config_path}. Supported AI Engines:\n#{valid_ai_engines.inspect}" unless valid_ai_engines.include?(engine)
|
285
|
-
|
286
|
-
model = pi.config.pwn[:ai][engine][:model]
|
287
|
-
system_role_content = pi.config.pwn[:ai][engine][:system_role_content]
|
288
|
-
|
289
|
-
# Reset the ai response history on config refresh
|
290
|
-
pi.config.pwn[:ai][engine][:response_history] = {
|
291
|
-
id: '',
|
292
|
-
object: '',
|
293
|
-
model: model,
|
294
|
-
usage: {},
|
295
|
-
choices: [
|
296
|
-
{
|
297
|
-
role: 'system',
|
298
|
-
content: system_role_content
|
299
|
-
}
|
300
|
-
]
|
301
|
-
}
|
302
|
-
|
303
|
-
# These two lines should be immutable for the session
|
304
|
-
pi.config.pwn[:yaml_config_path] = yaml_config_path
|
305
|
-
pi.config.pwn[:yaml_decryptor_path] = yaml_decryptor_path if is_encrypted
|
306
|
-
|
307
|
-
Pry.config.refresh = false
|
308
|
-
|
309
|
-
true
|
310
|
-
rescue StandardError => e
|
311
|
-
raise e
|
312
|
-
end
|
313
|
-
|
314
237
|
# Author(s):: 0day Inc. <support@0dayinc.com>
|
315
238
|
|
316
239
|
public_class_method def self.authors
|
@@ -363,12 +286,6 @@ module PWN
|
|
363
286
|
file: 'required - file to check if encrypted'
|
364
287
|
)
|
365
288
|
|
366
|
-
#{self}.refresh_config(
|
367
|
-
yaml_config_path: 'required - full path to pwn.yaml file',
|
368
|
-
pi: 'optional - Pry instance (default: Pry)',
|
369
|
-
yaml_decryptor_path: 'optional - full path to decryption YAML file'
|
370
|
-
)
|
371
|
-
|
372
289
|
#{self}.authors
|
373
290
|
"
|
374
291
|
end
|
data/lib/pwn/reports/sast.rb
CHANGED
@@ -14,13 +14,7 @@ module PWN
|
|
14
14
|
# PWN::Reports::SAST.generate(
|
15
15
|
# dir_path: 'optional - Directory path to save the report (defaults to .)',
|
16
16
|
# results_hash: 'optional - Hash containing the results of the SAST analysis (defaults to empty hash structure)',
|
17
|
-
# report_name: 'optional - Name of the report file (defaults to current directory name)'
|
18
|
-
# ai_engine: 'optional - AI engine to use for analysis (:grok, :ollama, or :openai)',
|
19
|
-
# ai_model: 'optionnal - AI Model to Use for Respective AI Engine (e.g., grok-4i-0709, chargpt-4o-latest, llama-3.1, etc.)',
|
20
|
-
# ai_key: 'optional - AI Key/Token for Respective AI Engine',
|
21
|
-
# ai_base_uri: 'optional - AI FQDN (Only Required for "ollama" AI Engine)',
|
22
|
-
# ai_system_role_content: 'optional - AI System Role Content (Defaults to "Confidence score of 0-10 this is vulnerable (0 being not vulnerable, moving upwards in confidence of exploitation). Provide additional context to assist penetration tester assessment.")',
|
23
|
-
# ai_temp: 'optional - AI Temperature (Defaults to 0.1)'
|
17
|
+
# report_name: 'optional - Name of the report file (defaults to current directory name)'
|
24
18
|
# )
|
25
19
|
|
26
20
|
public_class_method def self.generate(opts = {})
|
@@ -31,28 +25,21 @@ module PWN
|
|
31
25
|
}
|
32
26
|
report_name = opts[:report_name] ||= File.basename(Dir.pwd)
|
33
27
|
|
34
|
-
|
35
|
-
if
|
36
|
-
|
37
|
-
|
38
|
-
|
28
|
+
ai_instrospection = PWN::CONFIG[:ai][:introspection]
|
29
|
+
if ai_instrospection
|
30
|
+
engine = PWN::CONFIG[:ai][:active].to_s.downcase.to_sym
|
31
|
+
base_uri = PWN::CONFIG[:ai][engine][:base_uri]
|
32
|
+
model = PWN::CONFIG[:ai][engine][:model]
|
33
|
+
key = PWN::CONFIG[:ai][engine][:key]
|
34
|
+
system_role_content = PWN::CONFIG[:ai][engine][:system_role_content]
|
35
|
+
temp = PWN::CONFIG[:ai][engine][:temp]
|
39
36
|
|
40
|
-
|
41
|
-
raise 'ERROR: FQDN for Ollama AI engine is required.' if ai_engine == :ollama && ai_base_uri.nil?
|
42
|
-
|
43
|
-
ai_model = opts[:ai_model]
|
44
|
-
raise 'ERROR: AI Model is required for AI engine ollama.' if ai_engine == :ollama && ai_model.nil?
|
45
|
-
|
46
|
-
ai_key = opts[:ai_key] ||= PWN::Plugins::AuthenticationHelper.mask_password(prompt: "#{ai_engine} Token")
|
47
|
-
ai_system_role_content = opts[:ai_system_role_content] ||= 'Your sole purpose is to analyze source code snippets and generate an Exploit Prediction Scoring System (EPSS) score between 0% - 100%. Just generate a score unless score is higher than 75% in which a code fic should also be included.'
|
48
|
-
ai_temp = opts[:ai_temp] ||= 0.1
|
49
|
-
|
50
|
-
puts "Analyzing source code using AI engine: #{ai_engine}\nModel: #{ai_model}\nSystem Role Content: #{ai_system_role_content}\nTemperature: #{ai_temp}"
|
37
|
+
puts "Analyzing source code using AI engine: #{engine}\nModel: #{model}\nSystem Role Content: #{system_role_content}\nTemperature: #{temp}"
|
51
38
|
end
|
52
39
|
|
53
40
|
# Calculate percentage of AI analysis based on the number of entries
|
54
41
|
total_entries = results_hash[:data].sum { |entry| entry[:line_no_and_contents].size }
|
55
|
-
puts "Total entries to analyze: #{total_entries}" if
|
42
|
+
puts "Total entries to analyze: #{total_entries}" if engine
|
56
43
|
|
57
44
|
percent_complete = 0.0
|
58
45
|
entry_count = 0
|
@@ -79,37 +66,39 @@ module PWN
|
|
79
66
|
response = nil
|
80
67
|
author = src_detail[:author].to_s.scrub.chomp.strip
|
81
68
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
69
|
+
if ai_instrospection
|
70
|
+
case engine
|
71
|
+
when :grok
|
72
|
+
response = PWN::AI::Grok.chat(
|
73
|
+
base_uri: base_uri,
|
74
|
+
token: key,
|
75
|
+
model: model,
|
76
|
+
system_role_content: system_role_content,
|
77
|
+
temp: temp,
|
78
|
+
request: request.chomp,
|
79
|
+
spinner: false
|
80
|
+
)
|
81
|
+
when :ollama
|
82
|
+
response = PWN::AI::Ollama.chat(
|
83
|
+
base_uri: base_uri,
|
84
|
+
token: key,
|
85
|
+
model: model,
|
86
|
+
system_role_content: system_role_content,
|
87
|
+
temp: temp,
|
88
|
+
request: request.chomp,
|
89
|
+
spinner: false
|
90
|
+
)
|
91
|
+
when :openai
|
92
|
+
response = PWN::AI::OpenAI.chat(
|
93
|
+
base_uri: base_uri,
|
94
|
+
token: key,
|
95
|
+
model: model,
|
96
|
+
system_role_content: system_role_content,
|
97
|
+
temp: temp,
|
98
|
+
request: request.chomp,
|
99
|
+
spinner: false
|
100
|
+
)
|
101
|
+
end
|
113
102
|
end
|
114
103
|
|
115
104
|
ai_analysis = nil
|
@@ -118,6 +107,7 @@ module PWN
|
|
118
107
|
ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
|
119
108
|
# puts "AI Analysis Progress: #{percent_complete}% Line: #{line_no} | Author: #{author} | AI Analysis: #{ai_analysis}\n\n\n" if ai_analysis
|
120
109
|
end
|
110
|
+
# TODO: Make results prettier in the HTML report
|
121
111
|
src_detail[:ai_analysis] = ai_analysis.to_s.scrub.chomp.strip
|
122
112
|
|
123
113
|
spin.update(
|
data/lib/pwn/version.rb
CHANGED
data/lib/pwn.rb
CHANGED
@@ -12,19 +12,22 @@ module PWN
|
|
12
12
|
autoload :AWS, 'pwn/aws'
|
13
13
|
autoload :Banner, 'pwn/banner'
|
14
14
|
autoload :Blockchain, 'pwn/blockchain'
|
15
|
+
autoload :Config, 'pwn/config'
|
15
16
|
autoload :FFI, 'pwn/ffi'
|
16
17
|
autoload :Plugins, 'pwn/plugins'
|
17
18
|
autoload :Reports, 'pwn/reports'
|
18
19
|
autoload :SAST, 'pwn/sast'
|
19
20
|
autoload :WWW, 'pwn/www'
|
20
|
-
# TODO: If pwn.yaml is present, attempt to decrypt it, and initialize the YAML config
|
21
|
-
# Do this within PWN::Plugins::AuthenticationHelper?
|
22
21
|
|
23
22
|
# Display a List of Every PWN Module
|
24
23
|
|
25
24
|
public_class_method def self.help
|
26
25
|
constants.sort
|
27
26
|
end
|
27
|
+
|
28
|
+
# Initialize PWN configuration file
|
29
|
+
# PWN::CONFIG is the constant that stores the configuration data
|
30
|
+
PWN::Config.refresh
|
28
31
|
rescue StandardError => e
|
29
32
|
puts e.backtrace
|
30
33
|
raise e
|
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.
|
4
|
+
version: 0.5.430
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 0day Inc.
|
@@ -1832,6 +1832,7 @@ files:
|
|
1832
1832
|
- lib/pwn/blockchain.rb
|
1833
1833
|
- lib/pwn/blockchain/btc.rb
|
1834
1834
|
- lib/pwn/blockchain/eth.rb
|
1835
|
+
- lib/pwn/config.rb
|
1835
1836
|
- lib/pwn/ffi.rb
|
1836
1837
|
- lib/pwn/ffi/stdio.rb
|
1837
1838
|
- lib/pwn/plugins.rb
|
@@ -2180,6 +2181,7 @@ files:
|
|
2180
2181
|
- spec/lib/pwn/blockchain/btc_spec.rb
|
2181
2182
|
- spec/lib/pwn/blockchain/eth_spec.rb
|
2182
2183
|
- spec/lib/pwn/blockchain_spec.rb
|
2184
|
+
- spec/lib/pwn/config_spec.rb
|
2183
2185
|
- spec/lib/pwn/ffi/stdio_spec.rb
|
2184
2186
|
- spec/lib/pwn/ffi_spec.rb
|
2185
2187
|
- spec/lib/pwn/plugins/android_spec.rb
|