pwn 0.5.442 → 0.5.444
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/Gemfile +1 -1
- data/README.md +3 -3
- data/bin/pwn_sast +1 -0
- data/lib/pwn/plugins/file_fu.rb +20 -22
- data/lib/pwn/sast/amqp_connect_as_guest.rb +12 -91
- data/lib/pwn/sast/apache_file_system_util_api.rb +11 -93
- data/lib/pwn/sast/aws.rb +13 -95
- data/lib/pwn/sast/banned_function_calls_c.rb +140 -219
- data/lib/pwn/sast/base64.rb +12 -92
- data/lib/pwn/sast/beef_hook.rb +10 -92
- data/lib/pwn/sast/cmd_execution_go_lang.rb +83 -0
- data/lib/pwn/sast/cmd_execution_java.rb +14 -93
- data/lib/pwn/sast/cmd_execution_python.rb +16 -95
- data/lib/pwn/sast/cmd_execution_ruby.rb +24 -103
- data/lib/pwn/sast/cmd_execution_scala.rb +14 -93
- data/lib/pwn/sast/csrf.rb +10 -92
- data/lib/pwn/sast/deserial_java.rb +19 -98
- data/lib/pwn/sast/emoticon.rb +17 -100
- data/lib/pwn/sast/eval.rb +10 -92
- data/lib/pwn/sast/factory.rb +15 -95
- data/lib/pwn/sast/http_authorization_header.rb +20 -102
- data/lib/pwn/sast/inner_html.rb +10 -92
- data/lib/pwn/sast/keystore.rb +10 -92
- data/lib/pwn/sast/local_storage.rb +11 -93
- data/lib/pwn/sast/location_hash.rb +10 -92
- data/lib/pwn/sast/log4j.rb +12 -91
- data/lib/pwn/sast/logger.rb +24 -106
- data/lib/pwn/sast/md5.rb +10 -92
- data/lib/pwn/sast/outer_html.rb +10 -92
- data/lib/pwn/sast/padding_oracle.rb +11 -93
- data/lib/pwn/sast/password.rb +15 -97
- data/lib/pwn/sast/php_input_mechanisms.rb +18 -97
- data/lib/pwn/sast/php_type_juggling.rb +16 -95
- data/lib/pwn/sast/pom_version.rb +1 -3
- data/lib/pwn/sast/port.rb +16 -98
- data/lib/pwn/sast/post_message.rb +10 -92
- data/lib/pwn/sast/private_key.rb +10 -92
- data/lib/pwn/sast/redirect.rb +13 -95
- data/lib/pwn/sast/redos.rb +16 -98
- data/lib/pwn/sast/shell.rb +18 -100
- data/lib/pwn/sast/signature.rb +10 -92
- data/lib/pwn/sast/sql.rb +19 -95
- data/lib/pwn/sast/ssl.rb +14 -96
- data/lib/pwn/sast/sudo.rb +10 -92
- data/lib/pwn/sast/task_tag.rb +23 -105
- data/lib/pwn/sast/test_case_engine.rb +188 -0
- data/lib/pwn/sast/throw_errors.rb +14 -96
- data/lib/pwn/sast/token.rb +12 -94
- data/lib/pwn/sast/type_script_type_juggling.rb +16 -95
- data/lib/pwn/sast/version.rb +12 -94
- data/lib/pwn/sast/window_location_hash.rb +10 -92
- data/lib/pwn/sast.rb +5 -0
- data/lib/pwn/version.rb +1 -1
- data/lib/pwn.rb +0 -2
- data/spec/lib/pwn/sast/cmd_execution_go_lang_spec.rb +25 -0
- data/spec/lib/pwn/sast/test_case_engine_spec.rb +20 -0
- data/third_party/pwn_rdoc.jsonl +1 -1
- metadata +7 -3
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
module PWN
|
7
|
+
module SAST
|
8
|
+
# SAST Module used to identify command
|
9
|
+
# execution residing within GoLang source code.
|
10
|
+
module CmdExecutionGoLang
|
11
|
+
# Supported Method Parameters::
|
12
|
+
# PWN::SAST::CmdExecutionGoLang.scan(
|
13
|
+
# dir_path: 'optional path to dir defaults to .'
|
14
|
+
# git_repo_root_uri: 'optional http uri of git repo scanned'
|
15
|
+
# )
|
16
|
+
|
17
|
+
public_class_method def self.scan(opts = {})
|
18
|
+
dir_path = opts[:dir_path]
|
19
|
+
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
20
|
+
|
21
|
+
test_case_filter = "
|
22
|
+
grep -Fn \
|
23
|
+
-e 'exec.Command(' \
|
24
|
+
-e 'exec.CommandContext(' \
|
25
|
+
-e 'Cmd.CombinedOutput(' \
|
26
|
+
-e 'Cmd.Output(' \
|
27
|
+
-e 'Cmd.Run(' \
|
28
|
+
-e 'Cmd.Start(' {PWN_SAST_SRC_TARGET} 2> /dev/null
|
29
|
+
"
|
30
|
+
|
31
|
+
include_extensions = %w[.go .s .o .a .mod]
|
32
|
+
|
33
|
+
PWN::SAST::TestCaseEngine.execute(
|
34
|
+
test_case_filter: test_case_filter,
|
35
|
+
security_references: security_references,
|
36
|
+
dir_path: dir_path,
|
37
|
+
include_extensions: include_extensions,
|
38
|
+
git_repo_root_uri: git_repo_root_uri
|
39
|
+
)
|
40
|
+
rescue StandardError => e
|
41
|
+
raise e
|
42
|
+
end
|
43
|
+
|
44
|
+
# Used primarily to map NIST 800-53 Revision 4 Security Controls
|
45
|
+
# https://web.nvd.nist.gov/view/800-53/Rev4/impact?impactName=HIGH
|
46
|
+
# to PWN Exploit & Static Code Anti-Pattern Matching Modules to
|
47
|
+
# Determine the level of Testing Coverage w/ PWN.
|
48
|
+
|
49
|
+
public_class_method def self.security_references
|
50
|
+
{
|
51
|
+
sast_module: self,
|
52
|
+
section: 'INFORMATION INPUT VALIDATION',
|
53
|
+
nist_800_53_uri: 'https://csrc.nist.gov/projects/cprt/catalog#/cprt/framework/version/SP_800_53_5_1_1/home?element=SI-10',
|
54
|
+
cwe_id: '78',
|
55
|
+
cwe_uri: 'https://cwe.mitre.org/data/definitions/78.html'
|
56
|
+
}
|
57
|
+
rescue StandardError => e
|
58
|
+
raise e
|
59
|
+
end
|
60
|
+
|
61
|
+
# Author(s):: 0day Inc. <support@0dayinc.com>
|
62
|
+
|
63
|
+
public_class_method def self.authors
|
64
|
+
"AUTHOR(S):
|
65
|
+
0day Inc. <support@0dayinc.com>
|
66
|
+
"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Display Usage for this Module
|
70
|
+
|
71
|
+
public_class_method def self.help
|
72
|
+
puts "USAGE:
|
73
|
+
sast_arr = #{self}.scan(
|
74
|
+
:dir_path => 'optional path to dir defaults to .',
|
75
|
+
:git_repo_root_uri => 'optional http uri of git repo scanned'
|
76
|
+
)
|
77
|
+
|
78
|
+
#{self}.authors
|
79
|
+
"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -8,8 +8,6 @@ module PWN
|
|
8
8
|
# SAST Module used to identify command
|
9
9
|
# execution residing within Java source code.
|
10
10
|
module CmdExecutionJava
|
11
|
-
@@logger = PWN::Plugins::PWNLogger.create
|
12
|
-
|
13
11
|
# Supported Method Parameters::
|
14
12
|
# PWN::SAST::CmdExecutionJava.scan(
|
15
13
|
# dir_path: 'optional path to dir defaults to .'
|
@@ -19,100 +17,23 @@ module PWN
|
|
19
17
|
public_class_method def self.scan(opts = {})
|
20
18
|
dir_path = opts[:dir_path]
|
21
19
|
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
22
|
-
result_arr = []
|
23
|
-
ai_introspection = PWN::Env[:ai][:introspection]
|
24
|
-
logger_results = "AI Introspection => #{ai_introspection} => "
|
25
|
-
|
26
|
-
PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
|
27
|
-
if (File.file?(entry) && File.basename(entry) !~ /^pwn.+(html|json|db)$/ && File.basename(entry) !~ /\.JS-BEAUTIFIED$/) && File.extname(entry) == '.java' && entry !~ /test/i
|
28
|
-
line_no_and_contents_arr = []
|
29
|
-
entry_beautified = false
|
30
|
-
|
31
|
-
if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js'))
|
32
|
-
js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED 2> /dev/null`.to_s.scrub
|
33
|
-
entry = "#{entry}.JS-BEAUTIFIED"
|
34
|
-
entry_beautified = true
|
35
|
-
end
|
36
|
-
|
37
|
-
test_case_filter = "
|
38
|
-
grep -Fn \
|
39
|
-
-e 'getEngineByName(' \
|
40
|
-
-e 'ProcessBuilder(' \
|
41
|
-
-e '.exec(' #{entry} 2> /dev/null
|
42
|
-
"
|
43
|
-
|
44
|
-
str = `#{test_case_filter}`.to_s.scrub
|
45
20
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
hash_line = {
|
53
|
-
timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%9N %z').to_s,
|
54
|
-
security_references: security_references,
|
55
|
-
filename: { git_repo_root_uri: git_repo_root_uri, entry: entry },
|
56
|
-
line_no_and_contents: '',
|
57
|
-
raw_content: str,
|
58
|
-
test_case_filter: test_case_filter
|
59
|
-
}
|
60
|
-
|
61
|
-
# COMMMENT: Must be a better way to implement this (regex is kinda funky)
|
62
|
-
line_contents_split = str.split(/^(\d{1,}):|\n(\d{1,}):/)[1..-1]
|
63
|
-
line_no_count = line_contents_split.length # This should always be an even number
|
64
|
-
current_count = 0
|
65
|
-
while line_no_count > current_count
|
66
|
-
line_no = line_contents_split[current_count]
|
67
|
-
contents = line_contents_split[current_count + 1]
|
68
|
-
if Dir.exist?('.git')
|
69
|
-
repo_root = '.'
|
70
|
-
|
71
|
-
author = PWN::Plugins::Git.get_author(
|
72
|
-
repo_root: repo_root,
|
73
|
-
from_line: line_no,
|
74
|
-
to_line: line_no,
|
75
|
-
target_file: entry,
|
76
|
-
entry_beautified: entry_beautified
|
77
|
-
)
|
78
|
-
end
|
79
|
-
author ||= 'N/A'
|
80
|
-
|
81
|
-
ai_analysis = nil
|
82
|
-
if ai_introspection
|
83
|
-
request = {
|
84
|
-
scm_uri: "#{hash_line[:filename][:git_repo_root_uri]}/#{hash_line[:filename][:entry]}",
|
85
|
-
line_no: line_no,
|
86
|
-
source_code_snippet: contents
|
87
|
-
}.to_json
|
88
|
-
response = PWN::AI::Introspection.reflect(request: request)
|
89
|
-
if response.is_a?(Hash)
|
90
|
-
ai_analysis = response[:choices].last[:text] if response[:choices].last.keys.include?(:text)
|
91
|
-
ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
|
92
|
-
end
|
93
|
-
end
|
21
|
+
test_case_filter = "
|
22
|
+
grep -Fn \
|
23
|
+
-e 'getEngineByName(' \
|
24
|
+
-e 'ProcessBuilder(' \
|
25
|
+
-e '.exec(' {PWN_SAST_SRC_TARGET} 2> /dev/null
|
26
|
+
"
|
94
27
|
|
95
|
-
|
96
|
-
line_no: line_no,
|
97
|
-
contents: contents,
|
98
|
-
author: author,
|
99
|
-
ai_analysis: ai_analysis
|
100
|
-
)
|
28
|
+
include_extensions = %w[.java .class .jar .war .ear .nar .properties .aj .jsp .jspx .jstm .jsptml .jnlp .jad .ser .gsp]
|
101
29
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html"
|
110
|
-
if logger_results.empty?
|
111
|
-
@@logger.info("#{logger_banner}: No files applicable to this test case.\n")
|
112
|
-
else
|
113
|
-
@@logger.info("#{logger_banner} => #{logger_results}complete.\n")
|
114
|
-
end
|
115
|
-
result_arr
|
30
|
+
PWN::SAST::TestCaseEngine.execute(
|
31
|
+
test_case_filter: test_case_filter,
|
32
|
+
security_references: security_references,
|
33
|
+
dir_path: dir_path,
|
34
|
+
include_extensions: include_extensions,
|
35
|
+
git_repo_root_uri: git_repo_root_uri
|
36
|
+
)
|
116
37
|
rescue StandardError => e
|
117
38
|
raise e
|
118
39
|
end
|
@@ -8,8 +8,6 @@ module PWN
|
|
8
8
|
# SAST Module used to identify command execution
|
9
9
|
# residing within Python source code.
|
10
10
|
module CmdExecutionPython
|
11
|
-
@@logger = PWN::Plugins::PWNLogger.create
|
12
|
-
|
13
11
|
# Supported Method Parameters::
|
14
12
|
# PWN::SAST::CmdExecutionPython.scan(
|
15
13
|
# dir_path: 'optional path to dir defaults to .'
|
@@ -19,102 +17,25 @@ module PWN
|
|
19
17
|
public_class_method def self.scan(opts = {})
|
20
18
|
dir_path = opts[:dir_path]
|
21
19
|
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
22
|
-
result_arr = []
|
23
|
-
ai_introspection = PWN::Env[:ai][:introspection]
|
24
|
-
logger_results = "AI Introspection => #{ai_introspection} => "
|
25
|
-
|
26
|
-
PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
|
27
|
-
if (File.file?(entry) && File.basename(entry) !~ /^pwn.+(html|json|db)$/ && File.basename(entry) !~ /\.JS-BEAUTIFIED$/) && (File.extname(entry) == '.py' || File.extname(entry) == '.pyc' || File.extname(entry) == '.pyo' || File.extname(entry) == '.pyd') && entry !~ /test/i
|
28
|
-
line_no_and_contents_arr = []
|
29
|
-
entry_beautified = false
|
30
|
-
|
31
|
-
if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js'))
|
32
|
-
js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED 2> /dev/null`.to_s.scrub
|
33
|
-
entry = "#{entry}.JS-BEAUTIFIED"
|
34
|
-
entry_beautified = true
|
35
|
-
end
|
36
|
-
|
37
|
-
test_case_filter = "
|
38
|
-
grep -Fn \
|
39
|
-
-e 'eval(' \
|
40
|
-
-e 'os.popen' \
|
41
|
-
-e 'os.system' \
|
42
|
-
-e 'subprocess.call' \
|
43
|
-
-e 'subprocess.Popen' #{entry} 2> /dev/null
|
44
|
-
"
|
45
|
-
|
46
|
-
str = `#{test_case_filter}`.to_s.scrub
|
47
20
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
security_references: security_references,
|
57
|
-
filename: { git_repo_root_uri: git_repo_root_uri, entry: entry },
|
58
|
-
line_no_and_contents: '',
|
59
|
-
raw_content: str,
|
60
|
-
test_case_filter: test_case_filter
|
61
|
-
}
|
62
|
-
|
63
|
-
# COMMMENT: Must be a better way to implement this (regex is kinda funky)
|
64
|
-
line_contents_split = str.split(/^(\d{1,}):|\n(\d{1,}):/)[1..-1]
|
65
|
-
line_no_count = line_contents_split.length # This should always be an even number
|
66
|
-
current_count = 0
|
67
|
-
while line_no_count > current_count
|
68
|
-
line_no = line_contents_split[current_count]
|
69
|
-
contents = line_contents_split[current_count + 1]
|
70
|
-
if Dir.exist?('.git')
|
71
|
-
repo_root = '.'
|
72
|
-
|
73
|
-
author = PWN::Plugins::Git.get_author(
|
74
|
-
repo_root: repo_root,
|
75
|
-
from_line: line_no,
|
76
|
-
to_line: line_no,
|
77
|
-
target_file: entry,
|
78
|
-
entry_beautified: entry_beautified
|
79
|
-
)
|
80
|
-
end
|
81
|
-
author ||= 'N/A'
|
82
|
-
|
83
|
-
ai_analysis = nil
|
84
|
-
if ai_introspection
|
85
|
-
request = {
|
86
|
-
scm_uri: "#{hash_line[:filename][:git_repo_root_uri]}/#{hash_line[:filename][:entry]}",
|
87
|
-
line_no: line_no,
|
88
|
-
source_code_snippet: contents
|
89
|
-
}.to_json
|
90
|
-
response = PWN::AI::Introspection.reflect(request: request)
|
91
|
-
if response.is_a?(Hash)
|
92
|
-
ai_analysis = response[:choices].last[:text] if response[:choices].last.keys.include?(:text)
|
93
|
-
ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
|
94
|
-
end
|
95
|
-
end
|
21
|
+
test_case_filter = "
|
22
|
+
grep -Fn \
|
23
|
+
-e 'eval(' \
|
24
|
+
-e 'os.popen' \
|
25
|
+
-e 'os.system' \
|
26
|
+
-e 'subprocess.call' \
|
27
|
+
-e 'subprocess.Popen' {PWN_SAST_SRC_TARGET} 2> /dev/null
|
28
|
+
"
|
96
29
|
|
97
|
-
|
98
|
-
line_no: line_no,
|
99
|
-
contents: contents,
|
100
|
-
author: author,
|
101
|
-
ai_analysis: ai_analysis
|
102
|
-
)
|
30
|
+
include_extensions = %w[.py .pyc .pyd .pyo .pyw .pyi .pyx .pxd .ipynb .pyz .whl .egg .pth]
|
103
31
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html"
|
112
|
-
if logger_results.empty?
|
113
|
-
@@logger.info("#{logger_banner}: No files applicable to this test case.\n")
|
114
|
-
else
|
115
|
-
@@logger.info("#{logger_banner} => #{logger_results}complete.\n")
|
116
|
-
end
|
117
|
-
result_arr
|
32
|
+
PWN::SAST::TestCaseEngine.execute(
|
33
|
+
test_case_filter: test_case_filter,
|
34
|
+
security_references: security_references,
|
35
|
+
dir_path: dir_path,
|
36
|
+
include_extensions: include_extensions,
|
37
|
+
git_repo_root_uri: git_repo_root_uri
|
38
|
+
)
|
118
39
|
rescue StandardError => e
|
119
40
|
raise e
|
120
41
|
end
|
@@ -8,8 +8,6 @@ module PWN
|
|
8
8
|
# SAST Module used to identify command
|
9
9
|
# execution residing within Ruby source code.
|
10
10
|
module CmdExecutionRuby
|
11
|
-
@@logger = PWN::Plugins::PWNLogger.create
|
12
|
-
|
13
11
|
# Supported Method Parameters::
|
14
12
|
# PWN::SAST::CmdExecutionRuby(
|
15
13
|
# dir_path: 'optional path to dir defaults to .'
|
@@ -19,110 +17,33 @@ module PWN
|
|
19
17
|
public_class_method def self.scan(opts = {})
|
20
18
|
dir_path = opts[:dir_path]
|
21
19
|
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
22
|
-
result_arr = []
|
23
|
-
ai_introspection = PWN::Env[:ai][:introspection]
|
24
|
-
logger_results = "AI Introspection => #{ai_introspection} => "
|
25
|
-
|
26
|
-
PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
|
27
|
-
if (File.file?(entry) && File.basename(entry) !~ /^pwn.+(html|json|db)$/ && File.basename(entry) !~ /\.JS-BEAUTIFIED$/) && (File.extname(entry) == '.rb' || File.extname(entry) == '.rbw') && entry !~ /test/i
|
28
|
-
line_no_and_contents_arr = []
|
29
|
-
entry_beautified = false
|
30
|
-
|
31
|
-
if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js'))
|
32
|
-
js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED 2> /dev/null`.to_s.scrub
|
33
|
-
entry = "#{entry}.JS-BEAUTIFIED"
|
34
|
-
entry_beautified = true
|
35
|
-
end
|
36
|
-
|
37
|
-
test_case_filter = "
|
38
|
-
grep -n \
|
39
|
-
-e '`.*`' \
|
40
|
-
-e 'eval(' \
|
41
|
-
-e 'exec(' \
|
42
|
-
-e 'exec \"' \
|
43
|
-
-e 'system(' \
|
44
|
-
-e 'system \"' \
|
45
|
-
-e 'IO.popen' \
|
46
|
-
-e 'Open3.popen3' \
|
47
|
-
-e 'Open3.popen3' \
|
48
|
-
-e 'Facter::Util::Resolution::exec' \
|
49
|
-
-e 'PTY.spawn' \
|
50
|
-
-e 'Process.fork' \
|
51
|
-
-e '%x' #{entry} 2> /dev/null
|
52
|
-
"
|
53
|
-
|
54
|
-
str = `#{test_case_filter}`.to_s.scrub
|
55
20
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
line_contents_split = str.split(/^(\d{1,}):|\n(\d{1,}):/)[1..-1]
|
73
|
-
line_no_count = line_contents_split.length # This should always be an even number
|
74
|
-
current_count = 0
|
75
|
-
while line_no_count > current_count
|
76
|
-
line_no = line_contents_split[current_count]
|
77
|
-
contents = line_contents_split[current_count + 1]
|
78
|
-
if Dir.exist?('.git')
|
79
|
-
repo_root = '.'
|
80
|
-
|
81
|
-
author = PWN::Plugins::Git.get_author(
|
82
|
-
repo_root: repo_root,
|
83
|
-
from_line: line_no,
|
84
|
-
to_line: line_no,
|
85
|
-
target_file: entry,
|
86
|
-
entry_beautified: entry_beautified
|
87
|
-
)
|
88
|
-
end
|
89
|
-
author ||= 'N/A'
|
90
|
-
|
91
|
-
ai_analysis = nil
|
92
|
-
if ai_introspection
|
93
|
-
request = {
|
94
|
-
scm_uri: "#{hash_line[:filename][:git_repo_root_uri]}/#{hash_line[:filename][:entry]}",
|
95
|
-
line_no: line_no,
|
96
|
-
source_code_snippet: contents
|
97
|
-
}.to_json
|
98
|
-
response = PWN::AI::Introspection.reflect(request: request)
|
99
|
-
if response.is_a?(Hash)
|
100
|
-
ai_analysis = response[:choices].last[:text] if response[:choices].last.keys.include?(:text)
|
101
|
-
ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
|
102
|
-
end
|
103
|
-
end
|
21
|
+
test_case_filter = "
|
22
|
+
grep -n \
|
23
|
+
-e '`.*`' \
|
24
|
+
-e 'eval(' \
|
25
|
+
-e 'exec(' \
|
26
|
+
-e 'exec \"' \
|
27
|
+
-e 'system(' \
|
28
|
+
-e 'system \"' \
|
29
|
+
-e 'IO.popen' \
|
30
|
+
-e 'Open3.popen3' \
|
31
|
+
-e 'Open3.popen3' \
|
32
|
+
-e 'Facter::Util::Resolution::exec' \
|
33
|
+
-e 'PTY.spawn' \
|
34
|
+
-e 'Process.fork' \
|
35
|
+
-e '%x' {PWN_SAST_SRC_TARGET} 2> /dev/null
|
36
|
+
"
|
104
37
|
|
105
|
-
|
106
|
-
line_no: line_no,
|
107
|
-
contents: contents,
|
108
|
-
author: author,
|
109
|
-
ai_analysis: ai_analysis
|
110
|
-
)
|
38
|
+
include_extensions = %w[.rb .erb .rhtml .rake .gemspec .gem .ru .bundle]
|
111
39
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html"
|
120
|
-
if logger_results.empty?
|
121
|
-
@@logger.info("#{logger_banner}: No files applicable to this test case.\n")
|
122
|
-
else
|
123
|
-
@@logger.info("#{logger_banner} => #{logger_results}complete.\n")
|
124
|
-
end
|
125
|
-
result_arr
|
40
|
+
PWN::SAST::TestCaseEngine.execute(
|
41
|
+
test_case_filter: test_case_filter,
|
42
|
+
security_references: security_references,
|
43
|
+
dir_path: dir_path,
|
44
|
+
include_extensions: include_extensions,
|
45
|
+
git_repo_root_uri: git_repo_root_uri
|
46
|
+
)
|
126
47
|
rescue StandardError => e
|
127
48
|
raise e
|
128
49
|
end
|
@@ -8,8 +8,6 @@ module PWN
|
|
8
8
|
# SAST Module used to identify command
|
9
9
|
# execution residing within scala source code.
|
10
10
|
module CmdExecutionScala
|
11
|
-
@@logger = PWN::Plugins::PWNLogger.create
|
12
|
-
|
13
11
|
# Supported Method Parameters::
|
14
12
|
# PWN::SAST::CmdExecutionScala.scan(
|
15
13
|
# dir_path: 'optional path to dir defaults to .'
|
@@ -19,100 +17,23 @@ module PWN
|
|
19
17
|
public_class_method def self.scan(opts = {})
|
20
18
|
dir_path = opts[:dir_path]
|
21
19
|
git_repo_root_uri = opts[:git_repo_root_uri].to_s.scrub
|
22
|
-
result_arr = []
|
23
|
-
ai_introspection = PWN::Env[:ai][:introspection]
|
24
|
-
logger_results = "AI Introspection => #{ai_introspection} => "
|
25
|
-
|
26
|
-
PWN::Plugins::FileFu.recurse_in_dir(dir_path: dir_path) do |entry|
|
27
|
-
if (File.file?(entry) && File.basename(entry) !~ /^pwn.+(html|json|db)$/ && File.basename(entry) !~ /\.JS-BEAUTIFIED$/) && File.extname(entry) == '.scala' && entry !~ /test/i
|
28
|
-
line_no_and_contents_arr = []
|
29
|
-
entry_beautified = false
|
30
|
-
|
31
|
-
if File.extname(entry) == '.js' && (`wc -l #{entry}`.split.first.to_i < 20 || entry.include?('.min.js') || entry.include?('-all.js'))
|
32
|
-
js_beautify = `js-beautify #{entry} > #{entry}.JS-BEAUTIFIED 2> /dev/null`.to_s.scrub
|
33
|
-
entry = "#{entry}.JS-BEAUTIFIED"
|
34
|
-
entry_beautified = true
|
35
|
-
end
|
36
|
-
|
37
|
-
test_case_filter = "
|
38
|
-
grep -Fn \
|
39
|
-
-e 'Process(' \
|
40
|
-
-e '.!' \
|
41
|
-
-e '.!!' #{entry} 2> /dev/null
|
42
|
-
"
|
43
|
-
|
44
|
-
str = `#{test_case_filter}`.to_s.scrub
|
45
20
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
hash_line = {
|
53
|
-
timestamp: Time.now.strftime('%Y-%m-%d %H:%M:%S.%9N %z').to_s,
|
54
|
-
security_references: security_references,
|
55
|
-
filename: { git_repo_root_uri: git_repo_root_uri, entry: entry },
|
56
|
-
line_no_and_contents: '',
|
57
|
-
raw_content: str,
|
58
|
-
test_case_filter: test_case_filter
|
59
|
-
}
|
60
|
-
|
61
|
-
# COMMMENT: Must be a better way to implement this (regex is kinda funky)
|
62
|
-
line_contents_split = str.split(/^(\d{1,}):|\n(\d{1,}):/)[1..-1]
|
63
|
-
line_no_count = line_contents_split.length # This should always be an even number
|
64
|
-
current_count = 0
|
65
|
-
while line_no_count > current_count
|
66
|
-
line_no = line_contents_split[current_count]
|
67
|
-
contents = line_contents_split[current_count + 1]
|
68
|
-
if Dir.exist?('.git')
|
69
|
-
repo_root = '.'
|
70
|
-
|
71
|
-
author = PWN::Plugins::Git.get_author(
|
72
|
-
repo_root: repo_root,
|
73
|
-
from_line: line_no,
|
74
|
-
to_line: line_no,
|
75
|
-
target_file: entry,
|
76
|
-
entry_beautified: entry_beautified
|
77
|
-
)
|
78
|
-
end
|
79
|
-
author ||= 'N/A'
|
80
|
-
|
81
|
-
ai_analysis = nil
|
82
|
-
if ai_introspection
|
83
|
-
request = {
|
84
|
-
scm_uri: "#{hash_line[:filename][:git_repo_root_uri]}/#{hash_line[:filename][:entry]}",
|
85
|
-
line_no: line_no,
|
86
|
-
source_code_snippet: contents
|
87
|
-
}.to_json
|
88
|
-
response = PWN::AI::Introspection.reflect(request: request)
|
89
|
-
if response.is_a?(Hash)
|
90
|
-
ai_analysis = response[:choices].last[:text] if response[:choices].last.keys.include?(:text)
|
91
|
-
ai_analysis = response[:choices].last[:content] if response[:choices].last.keys.include?(:content)
|
92
|
-
end
|
93
|
-
end
|
21
|
+
test_case_filter = "
|
22
|
+
grep -Fn \
|
23
|
+
-e 'Process(' \
|
24
|
+
-e '.!' \
|
25
|
+
-e '.!!' {PWN_SAST_SRC_TARGET} 2> /dev/null
|
26
|
+
"
|
94
27
|
|
95
|
-
|
96
|
-
line_no: line_no,
|
97
|
-
contents: contents,
|
98
|
-
author: author,
|
99
|
-
ai_analysis: ai_analysis
|
100
|
-
)
|
28
|
+
include_extensions = %w[.scala .sc .sbt .class .jar .war .tasty .scala.html .scala.js .scala.txt .properties]
|
101
29
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
logger_banner = "http://#{Socket.gethostname}:8808/doc_root/pwn-#{PWN::VERSION.to_s.scrub}/#{to_s.scrub.gsub('::', '/')}.html"
|
110
|
-
if logger_results.empty?
|
111
|
-
@@logger.info("#{logger_banner}: No files applicable to this test case.\n")
|
112
|
-
else
|
113
|
-
@@logger.info("#{logger_banner} => #{logger_results}complete.\n")
|
114
|
-
end
|
115
|
-
result_arr
|
30
|
+
PWN::SAST::TestCaseEngine.execute(
|
31
|
+
test_case_filter: test_case_filter,
|
32
|
+
security_references: security_references,
|
33
|
+
dir_path: dir_path,
|
34
|
+
include_extensions: include_extensions,
|
35
|
+
git_repo_root_uri: git_repo_root_uri
|
36
|
+
)
|
116
37
|
rescue StandardError => e
|
117
38
|
raise e
|
118
39
|
end
|