brakeman-lib 4.10.1 → 5.0.0.pre1
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/CHANGES.md +9 -7
- data/README.md +1 -1
- data/lib/brakeman.rb +6 -0
- data/lib/brakeman/app_tree.rb +36 -3
- data/lib/brakeman/checks/check_execute.rb +1 -1
- data/lib/brakeman/checks/check_regex_dos.rb +1 -1
- data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
- data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
- data/lib/brakeman/file_parser.rb +19 -23
- data/lib/brakeman/options.rb +5 -1
- data/lib/brakeman/parsers/template_parser.rb +2 -3
- data/lib/brakeman/processors/alias_processor.rb +2 -2
- data/lib/brakeman/processors/controller_processor.rb +1 -1
- data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
- data/lib/brakeman/processors/output_processor.rb +1 -1
- data/lib/brakeman/processors/template_alias_processor.rb +0 -5
- data/lib/brakeman/report.rb +8 -0
- data/lib/brakeman/report/report_sonar.rb +38 -0
- data/lib/brakeman/rescanner.rb +7 -5
- data/lib/brakeman/scanner.rb +42 -18
- data/lib/brakeman/tracker.rb +6 -0
- data/lib/brakeman/tracker/controller.rb +1 -1
- data/lib/brakeman/util.rb +9 -4
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning_codes.rb +2 -0
- data/lib/ruby_parser/bm_sexp.rb +9 -9
- metadata +10 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d69b909ed56306516d662fbc2bd25c22c3886925a8a98ca81ea761f0bb3851ca
|
4
|
+
data.tar.gz: bb2f1fe108cccdefb21da6cce220489d0890659bfaf2a773e3e9201892db0f9b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 004d9116ce3b94abe715744571e124b477b9e30acaa623e3e46dc28493e9829d9c7cee9a060024eb6aa8158336d0622e81fb68a43343e5a4dddf837c214e62c8
|
7
|
+
data.tar.gz: af6556b75109436f49a1087ace59aab6c235a82452d92159770fbd8248c56aa458d254307046e73b649563d7471c88a31b049257ad5305498963d815aa1f2439
|
data/CHANGES.md
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
*
|
4
|
-
*
|
5
|
-
*
|
6
|
-
*
|
7
|
-
*
|
1
|
+
# 5.0.0.pre1 - 2020-11-17
|
2
|
+
|
3
|
+
* Add check for (more) unsafe method reflection
|
4
|
+
* Suggest using `--force` if no Rails application is detected
|
5
|
+
* Add Sonarqube report format (Adam England)
|
6
|
+
* Add check for potential HTTP verb confusion
|
7
|
+
* Add `--[no-]skip-vendor` option
|
8
|
+
* Scan (almost) all Ruby files in project
|
9
|
+
* Add support for Haml 5.2.0
|
8
10
|
|
9
11
|
# 4.10.0 - 2020-09-28
|
10
12
|
|
data/README.md
CHANGED
@@ -76,7 +76,7 @@ To specify an output file for the results:
|
|
76
76
|
|
77
77
|
brakeman -o output_file
|
78
78
|
|
79
|
-
The output format is determined by the file extension or by using the `-f` option. Current options are: `text`, `html`, `tabs`, `json`, `junit`, `markdown`, `csv`, and `
|
79
|
+
The output format is determined by the file extension or by using the `-f` option. Current options are: `text`, `html`, `tabs`, `json`, `junit`, `markdown`, `csv`, `codeclimate`, and `sonar`.
|
80
80
|
|
81
81
|
Multiple output files can be specified:
|
82
82
|
|
data/lib/brakeman.rb
CHANGED
@@ -66,6 +66,7 @@ module Brakeman
|
|
66
66
|
# * :run_checks - array of checks to run (run all if not specified)
|
67
67
|
# * :safe_methods - array of methods to consider safe
|
68
68
|
# * :skip_libs - do not process lib/ directory (default: false)
|
69
|
+
# * :skip_vendor - do not process vendor/ directory (default: true)
|
69
70
|
# * :skip_checks - checks not to run (run all if not specified)
|
70
71
|
# * :absolute_paths - show absolute path of each file (default: false)
|
71
72
|
# * :summary_only - only output summary section of report for plain/table (:summary_only, :no_summary, true)
|
@@ -191,6 +192,7 @@ module Brakeman
|
|
191
192
|
:report_progress => true,
|
192
193
|
:safe_methods => Set.new,
|
193
194
|
:skip_checks => Set.new,
|
195
|
+
:skip_vendor => true,
|
194
196
|
}
|
195
197
|
end
|
196
198
|
|
@@ -239,6 +241,8 @@ module Brakeman
|
|
239
241
|
[:to_junit]
|
240
242
|
when :sarif, :to_sarif
|
241
243
|
[:to_sarif]
|
244
|
+
when :sonar, :to_sonar
|
245
|
+
[:to_sonar]
|
242
246
|
else
|
243
247
|
[:to_text]
|
244
248
|
end
|
@@ -270,6 +274,8 @@ module Brakeman
|
|
270
274
|
:to_junit
|
271
275
|
when /\.sarif$/i
|
272
276
|
:to_sarif
|
277
|
+
when /\.sonar$/i
|
278
|
+
:to_sonar
|
273
279
|
else
|
274
280
|
:to_text
|
275
281
|
end
|
data/lib/brakeman/app_tree.rb
CHANGED
@@ -21,6 +21,7 @@ module Brakeman
|
|
21
21
|
end
|
22
22
|
init_options[:additional_libs_path] = options[:additional_libs_path]
|
23
23
|
init_options[:engine_paths] = options[:engine_paths]
|
24
|
+
init_options[:skip_vendor] = options[:skip_vendor]
|
24
25
|
new(root, init_options)
|
25
26
|
end
|
26
27
|
|
@@ -62,6 +63,7 @@ module Brakeman
|
|
62
63
|
@engine_paths = init_options[:engine_paths] || []
|
63
64
|
@absolute_engine_paths = @engine_paths.select { |path| path.start_with?(File::SEPARATOR) }
|
64
65
|
@relative_engine_paths = @engine_paths - @absolute_engine_paths
|
66
|
+
@skip_vendor = init_options[:skip_vendor]
|
65
67
|
@gemspec = nil
|
66
68
|
@root_search_pattern = nil
|
67
69
|
end
|
@@ -96,6 +98,10 @@ module Brakeman
|
|
96
98
|
end
|
97
99
|
end
|
98
100
|
|
101
|
+
def ruby_file_paths
|
102
|
+
find_paths(".").uniq
|
103
|
+
end
|
104
|
+
|
99
105
|
def initializer_paths
|
100
106
|
@initializer_paths ||= prioritize_concerns(find_paths("config/initializers"))
|
101
107
|
end
|
@@ -109,8 +115,8 @@ module Brakeman
|
|
109
115
|
end
|
110
116
|
|
111
117
|
def template_paths
|
112
|
-
@template_paths ||= find_paths("
|
113
|
-
|
118
|
+
@template_paths ||= find_paths(".", "*.{#{VIEW_EXTENSIONS}}") +
|
119
|
+
find_paths("**", "*.{erb,haml,slim}").reject { |path| File.basename(path).count(".") > 1 }
|
114
120
|
end
|
115
121
|
|
116
122
|
def layout_exists?(name)
|
@@ -163,7 +169,8 @@ module Brakeman
|
|
163
169
|
def select_files(paths)
|
164
170
|
paths = select_only_files(paths)
|
165
171
|
paths = reject_skipped_files(paths)
|
166
|
-
convert_to_file_paths(paths)
|
172
|
+
paths = convert_to_file_paths(paths)
|
173
|
+
reject_global_excludes(paths)
|
167
174
|
end
|
168
175
|
|
169
176
|
def select_only_files(paths)
|
@@ -182,6 +189,32 @@ module Brakeman
|
|
182
189
|
end
|
183
190
|
end
|
184
191
|
|
192
|
+
EXCLUDED_PATHS = %w[
|
193
|
+
/generators/
|
194
|
+
lib/tasks/
|
195
|
+
lib/templates/
|
196
|
+
db/
|
197
|
+
spec/
|
198
|
+
test/
|
199
|
+
tmp/
|
200
|
+
public/
|
201
|
+
log/
|
202
|
+
]
|
203
|
+
|
204
|
+
def reject_global_excludes(paths)
|
205
|
+
paths.reject do |path|
|
206
|
+
relative_path = path.relative
|
207
|
+
|
208
|
+
if @skip_vendor and relative_path.include? 'vendor/'
|
209
|
+
true
|
210
|
+
else
|
211
|
+
EXCLUDED_PATHS.any? do |excluded|
|
212
|
+
relative_path.include? excluded
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
185
218
|
def match_path files, path
|
186
219
|
absolute_path = Pathname.new(path)
|
187
220
|
# relative root never has a leading separator. But, we use a leading
|
@@ -208,7 +208,7 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
208
208
|
if node_type? e, :if
|
209
209
|
# If we're in a conditional, evaluate the `then` and `else` clauses to
|
210
210
|
# see if they're dangerous.
|
211
|
-
if res = dangerous?(e.
|
211
|
+
if res = dangerous?(e.values[1..-1])
|
212
212
|
return res
|
213
213
|
end
|
214
214
|
elsif node_type? e, :or, :evstr, :dstr
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'brakeman/checks/base_check'
|
2
|
+
|
3
|
+
class Brakeman::CheckUnsafeReflectionMethods < Brakeman::BaseCheck
|
4
|
+
Brakeman::Checks.add self
|
5
|
+
|
6
|
+
@description = "Checks for unsafe reflection to access methods"
|
7
|
+
|
8
|
+
def run_check
|
9
|
+
check_method
|
10
|
+
check_tap
|
11
|
+
check_to_proc
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_method
|
15
|
+
tracker.find_call(method: :method, nested: true).each do |result|
|
16
|
+
argument = result[:call].first_arg
|
17
|
+
|
18
|
+
if user_input = include_user_input?(argument)
|
19
|
+
warn_unsafe_reflection(result, user_input)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_tap
|
25
|
+
tracker.find_call(method: :tap, nested: true).each do |result|
|
26
|
+
argument = result[:call].first_arg
|
27
|
+
|
28
|
+
# Argument is passed like a.tap(&argument)
|
29
|
+
if node_type? argument, :block_pass
|
30
|
+
argument = argument.value
|
31
|
+
end
|
32
|
+
|
33
|
+
if user_input = include_user_input?(argument)
|
34
|
+
warn_unsafe_reflection(result, user_input)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_to_proc
|
40
|
+
tracker.find_call(method: :to_proc, nested: true).each do |result|
|
41
|
+
target = result[:call].target
|
42
|
+
|
43
|
+
if user_input = include_user_input?(target)
|
44
|
+
warn_unsafe_reflection(result, user_input)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def warn_unsafe_reflection result, input
|
50
|
+
return unless original? result
|
51
|
+
method = result[:call].method
|
52
|
+
|
53
|
+
confidence = if input.type == :params
|
54
|
+
:high
|
55
|
+
else
|
56
|
+
:medium
|
57
|
+
end
|
58
|
+
|
59
|
+
message = msg("Unsafe reflection method ", msg_code(method), " called with ", msg_input(input))
|
60
|
+
|
61
|
+
warn :result => result,
|
62
|
+
:warning_type => "Remote Code Execution",
|
63
|
+
:warning_code => :unsafe_method_reflection,
|
64
|
+
:message => message,
|
65
|
+
:user_input => input,
|
66
|
+
:confidence => confidence
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'brakeman/checks/base_check'
|
2
|
+
|
3
|
+
class Brakeman::CheckVerbConfusion < Brakeman::BaseCheck
|
4
|
+
Brakeman::Checks.add self
|
5
|
+
|
6
|
+
@description = "Check for uses of `request.get?` that might have unintentional behavior"
|
7
|
+
|
8
|
+
#Process calls
|
9
|
+
def run_check
|
10
|
+
calls = tracker.find_call(target: :request, methods: [:get?])
|
11
|
+
|
12
|
+
calls.each do |call|
|
13
|
+
process_result call
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_result result
|
18
|
+
@current_result = result
|
19
|
+
@matched_call = result[:call]
|
20
|
+
klass = tracker.find_class(result[:location][:class])
|
21
|
+
|
22
|
+
# TODO: abstract into tracker.find_location ?
|
23
|
+
if klass.nil?
|
24
|
+
Brakeman.debug "No class found: #{result[:location][:class]}"
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
method = klass.get_method(result[:location][:method])
|
29
|
+
|
30
|
+
if method.nil?
|
31
|
+
Brakeman.debug "No method found: #{result[:location][:method]}"
|
32
|
+
return
|
33
|
+
end
|
34
|
+
|
35
|
+
process method[:src]
|
36
|
+
end
|
37
|
+
|
38
|
+
def process_if exp
|
39
|
+
if exp.condition == @matched_call
|
40
|
+
# Found `if request.get?`
|
41
|
+
|
42
|
+
# Do not warn if there is an `elsif` clause
|
43
|
+
if node_type? exp.else_clause, :if
|
44
|
+
return exp
|
45
|
+
end
|
46
|
+
|
47
|
+
warn_about_result @current_result, exp
|
48
|
+
end
|
49
|
+
|
50
|
+
exp
|
51
|
+
end
|
52
|
+
|
53
|
+
def warn_about_result result, code
|
54
|
+
return unless original? result
|
55
|
+
|
56
|
+
confidence = :weak
|
57
|
+
message = msg('Potential HTTP verb confusion. ',
|
58
|
+
msg_code('HEAD'),
|
59
|
+
' is routed like ',
|
60
|
+
msg_code('GET'),
|
61
|
+
' but ',
|
62
|
+
msg_code('request.get?'),
|
63
|
+
' will return ',
|
64
|
+
msg_code('false')
|
65
|
+
)
|
66
|
+
|
67
|
+
warn :result => result,
|
68
|
+
:warning_type => "HTTP Verb Confusion",
|
69
|
+
:warning_code => :http_verb_confusion,
|
70
|
+
:message => message,
|
71
|
+
:code => code,
|
72
|
+
:user_input => result[:call],
|
73
|
+
:confidence => confidence
|
74
|
+
end
|
75
|
+
end
|
data/lib/brakeman/file_parser.rb
CHANGED
@@ -3,55 +3,51 @@ module Brakeman
|
|
3
3
|
|
4
4
|
# This class handles reading and parsing files.
|
5
5
|
class FileParser
|
6
|
-
attr_reader :file_list
|
6
|
+
attr_reader :file_list, :errors
|
7
7
|
|
8
|
-
def initialize
|
9
|
-
@
|
10
|
-
@timeout =
|
11
|
-
@
|
12
|
-
@
|
8
|
+
def initialize app_tree, timeout
|
9
|
+
@app_tree = app_tree
|
10
|
+
@timeout = timeout
|
11
|
+
@file_list = []
|
12
|
+
@errors = []
|
13
13
|
end
|
14
14
|
|
15
|
-
def parse_files list
|
16
|
-
read_files list
|
15
|
+
def parse_files list
|
16
|
+
read_files list do |path, contents|
|
17
17
|
if ast = parse_ruby(contents, path.relative)
|
18
18
|
ASTFile.new(path, ast)
|
19
19
|
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def read_files list
|
24
|
-
@file_list[type] ||= []
|
25
|
-
|
23
|
+
def read_files list
|
26
24
|
list.each do |path|
|
27
25
|
file = @app_tree.file_path(path)
|
28
26
|
|
29
27
|
result = yield file, file.read
|
28
|
+
|
30
29
|
if result
|
31
|
-
@file_list
|
30
|
+
@file_list << result
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
35
34
|
|
36
|
-
# _path_ can be a string or a Brakeman::FilePath
|
37
35
|
def parse_ruby input, path
|
38
|
-
if path.is_a? Brakeman::FilePath
|
39
|
-
path = path.relative
|
40
|
-
end
|
41
|
-
|
42
36
|
begin
|
43
37
|
Brakeman.debug "Parsing #{path}"
|
44
38
|
RubyParser.new.parse input, path, @timeout
|
45
39
|
rescue Racc::ParseError => e
|
46
|
-
|
47
|
-
nil
|
40
|
+
error e.exception(e.message + "\nCould not parse #{path}")
|
48
41
|
rescue Timeout::Error => e
|
49
|
-
|
50
|
-
nil
|
42
|
+
error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
|
51
43
|
rescue => e
|
52
|
-
|
53
|
-
nil
|
44
|
+
error e.exception(e.message + "\nWhile processing #{path}")
|
54
45
|
end
|
55
46
|
end
|
47
|
+
|
48
|
+
def error exception
|
49
|
+
@errors << exception
|
50
|
+
nil
|
51
|
+
end
|
56
52
|
end
|
57
53
|
end
|
data/lib/brakeman/options.rb
CHANGED
@@ -166,6 +166,10 @@ module Brakeman::Options
|
|
166
166
|
options[:only_files].merge files
|
167
167
|
end
|
168
168
|
|
169
|
+
opts.on "--[no-]skip-vendor", "Skip processing vendor directory (Default)" do |skip|
|
170
|
+
options[:skip_vendor] = skip
|
171
|
+
end
|
172
|
+
|
169
173
|
opts.on "--skip-libs", "Skip processing lib directory" do
|
170
174
|
options[:skip_libs] = true
|
171
175
|
end
|
@@ -229,7 +233,7 @@ module Brakeman::Options
|
|
229
233
|
|
230
234
|
opts.on "-f",
|
231
235
|
"--format TYPE",
|
232
|
-
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif],
|
236
|
+
[:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar],
|
233
237
|
"Specify output formats. Default is text" do |type|
|
234
238
|
|
235
239
|
type = "s" if type == :text
|
@@ -9,7 +9,6 @@ module Brakeman
|
|
9
9
|
def initialize tracker, file_parser
|
10
10
|
@tracker = tracker
|
11
11
|
@file_parser = file_parser
|
12
|
-
@file_parser.file_list[:templates] ||= []
|
13
12
|
end
|
14
13
|
|
15
14
|
def parse_template path, text
|
@@ -33,7 +32,7 @@ module Brakeman
|
|
33
32
|
end
|
34
33
|
|
35
34
|
if src and ast = @file_parser.parse_ruby(src, path)
|
36
|
-
@file_parser.file_list
|
35
|
+
@file_parser.file_list << TemplateFile.new(path, ast, name, type)
|
37
36
|
end
|
38
37
|
rescue Racc::ParseError => e
|
39
38
|
tracker.error e, "Could not parse #{path}"
|
@@ -97,7 +96,7 @@ module Brakeman
|
|
97
96
|
end
|
98
97
|
|
99
98
|
def self.parse_inline_erb tracker, text
|
100
|
-
fp = Brakeman::FileParser.new(tracker)
|
99
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
101
100
|
tp = self.new(tracker, fp)
|
102
101
|
src = tp.parse_erb '_inline_', text
|
103
102
|
type = tp.erubis? ? :erubis : :erb
|
@@ -236,7 +236,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
236
236
|
env[target_var] = target
|
237
237
|
return target
|
238
238
|
elsif string? target and string_interp? first_arg
|
239
|
-
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg
|
239
|
+
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg[2..-1])
|
240
240
|
env[target_var] = exp
|
241
241
|
elsif string? first_arg and string_interp? target
|
242
242
|
if string? target.last
|
@@ -941,7 +941,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
941
941
|
args = exp.args
|
942
942
|
exp.pop # remove last arg
|
943
943
|
if args.length > 1
|
944
|
-
exp.arglist = args
|
944
|
+
exp.arglist = args[1..-1]
|
945
945
|
end
|
946
946
|
end
|
947
947
|
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Brakeman
|
2
|
+
class FileTypeDetector < BaseProcessor
|
3
|
+
def initialize
|
4
|
+
super(nil)
|
5
|
+
reset
|
6
|
+
end
|
7
|
+
|
8
|
+
def detect_type(file)
|
9
|
+
reset
|
10
|
+
process(file.ast)
|
11
|
+
|
12
|
+
if @file_type.nil?
|
13
|
+
@file_type = guess_from_path(file.path.relative)
|
14
|
+
end
|
15
|
+
|
16
|
+
@file_type || :libs
|
17
|
+
end
|
18
|
+
|
19
|
+
MODEL_CLASSES = [
|
20
|
+
:'ActiveRecord::Base',
|
21
|
+
:ApplicationRecord
|
22
|
+
]
|
23
|
+
|
24
|
+
def process_class exp
|
25
|
+
name = class_name(exp.class_name)
|
26
|
+
parent = class_name(exp.parent_name)
|
27
|
+
|
28
|
+
if name.match(/Controller$/)
|
29
|
+
@file_type = :controllers
|
30
|
+
return exp
|
31
|
+
elsif MODEL_CLASSES.include? parent
|
32
|
+
@file_type = :models
|
33
|
+
return exp
|
34
|
+
end
|
35
|
+
|
36
|
+
super
|
37
|
+
end
|
38
|
+
|
39
|
+
def guess_from_path path
|
40
|
+
case
|
41
|
+
when path.include?('app/models')
|
42
|
+
:models
|
43
|
+
when path.include?('app/controllers')
|
44
|
+
:controllers
|
45
|
+
when path.include?('config/initializers')
|
46
|
+
:initializers
|
47
|
+
when path.include?('lib/')
|
48
|
+
:libs
|
49
|
+
when path.match?(%r{config/environments/(?!production\.rb)$})
|
50
|
+
:skip
|
51
|
+
when path.match?(%r{environments/production\.rb$})
|
52
|
+
:skip
|
53
|
+
when path.match?(%r{application\.rb$})
|
54
|
+
:skip
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def reset
|
61
|
+
@file_type = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -20,11 +20,6 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
|
|
20
20
|
|
21
21
|
#Process template
|
22
22
|
def process_template name, args, _, line = nil
|
23
|
-
# Strip forward slash from beginning of template path.
|
24
|
-
# This also happens in RenderHelper#process_template but
|
25
|
-
# we need it here too to accurately avoid circular renders below.
|
26
|
-
name = name.to_s.gsub(/^\//, "")
|
27
|
-
|
28
23
|
if @called_from
|
29
24
|
if @called_from.include_template? name
|
30
25
|
Brakeman.debug "Skipping circular render from #{@template.name} to #{name}"
|
data/lib/brakeman/report.rb
CHANGED
@@ -45,6 +45,9 @@ class Brakeman::Report
|
|
45
45
|
Brakeman::Report::JUnit
|
46
46
|
when :to_sarif
|
47
47
|
return self.to_sarif
|
48
|
+
when :to_sonar
|
49
|
+
require_report 'sonar'
|
50
|
+
Brakeman::Report::Sonar
|
48
51
|
else
|
49
52
|
raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
|
50
53
|
end
|
@@ -69,6 +72,11 @@ class Brakeman::Report
|
|
69
72
|
generate Brakeman::Report::JSON
|
70
73
|
end
|
71
74
|
|
75
|
+
def to_sonar
|
76
|
+
require_report 'sonar'
|
77
|
+
generate Brakeman::Report::Sonar
|
78
|
+
end
|
79
|
+
|
72
80
|
def to_table
|
73
81
|
require_report 'table'
|
74
82
|
generate Brakeman::Report::Table
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Brakeman::Report::Sonar < Brakeman::Report::Base
|
2
|
+
def generate_report
|
3
|
+
report_object = {
|
4
|
+
issues: all_warnings.map { |warning| issue_json(warning) }
|
5
|
+
}
|
6
|
+
return JSON.pretty_generate report_object
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def issue_json(warning)
|
12
|
+
{
|
13
|
+
engineId: "Brakeman",
|
14
|
+
ruleId: warning.warning_code,
|
15
|
+
type: "VULNERABILITY",
|
16
|
+
severity: severity_level_for(warning.confidence),
|
17
|
+
primaryLocation: {
|
18
|
+
message: warning.message,
|
19
|
+
filePath: warning.file.relative,
|
20
|
+
textRange: {
|
21
|
+
"startLine": warning.line || 1,
|
22
|
+
"endLine": warning.line || 1,
|
23
|
+
}
|
24
|
+
},
|
25
|
+
effortMinutes: (4 - warning.confidence) * 15
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def severity_level_for(confidence)
|
30
|
+
if confidence == 0
|
31
|
+
"CRITICAL"
|
32
|
+
elsif confidence == 1
|
33
|
+
"MAJOR"
|
34
|
+
else
|
35
|
+
"MINOR"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/brakeman/rescanner.rb
CHANGED
@@ -132,10 +132,11 @@ class Brakeman::Rescanner < Brakeman::Scanner
|
|
132
132
|
template_name = template_path_to_name(path)
|
133
133
|
|
134
134
|
tracker.reset_template template_name
|
135
|
-
fp = Brakeman::FileParser.new(tracker)
|
135
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
136
136
|
template_parser = Brakeman::TemplateParser.new(tracker, fp)
|
137
137
|
template_parser.parse_template path, path.read
|
138
|
-
|
138
|
+
tracker.add_errors(fp.errors)
|
139
|
+
process_template fp.file_list.first
|
139
140
|
|
140
141
|
@processor.process_template_alias tracker.templates[template_name]
|
141
142
|
|
@@ -390,9 +391,10 @@ class Brakeman::Rescanner < Brakeman::Scanner
|
|
390
391
|
|
391
392
|
def parse_ruby_files list
|
392
393
|
paths = list.select(&:exists?)
|
393
|
-
file_parser = Brakeman::FileParser.new(tracker)
|
394
|
-
file_parser.parse_files paths
|
395
|
-
file_parser.
|
394
|
+
file_parser = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
395
|
+
file_parser.parse_files paths
|
396
|
+
tracker.add_errors(file_parser.errors)
|
397
|
+
file_parser.file_list
|
396
398
|
end
|
397
399
|
end
|
398
400
|
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -7,6 +7,7 @@ begin
|
|
7
7
|
require 'brakeman/app_tree'
|
8
8
|
require 'brakeman/file_parser'
|
9
9
|
require 'brakeman/parsers/template_parser'
|
10
|
+
require 'brakeman/processors/lib/file_type_detector'
|
10
11
|
rescue LoadError => e
|
11
12
|
$stderr.puts e.message
|
12
13
|
$stderr.puts "Please install the appropriate dependency."
|
@@ -23,7 +24,10 @@ class Brakeman::Scanner
|
|
23
24
|
@app_tree = Brakeman::AppTree.from_options(options)
|
24
25
|
|
25
26
|
if (!@app_tree.root || !@app_tree.exists?("app")) && !options[:force_scan]
|
26
|
-
|
27
|
+
message = "Please supply the path to a Rails application (looking in #{@app_tree.root}).\n" <<
|
28
|
+
" Use `--force` to run a scan anyway - for example if there are many applications in one directory."
|
29
|
+
|
30
|
+
raise Brakeman::NoApplication, message
|
27
31
|
end
|
28
32
|
|
29
33
|
@processor = processor || Brakeman::Processor.new(@app_tree, options)
|
@@ -43,6 +47,8 @@ class Brakeman::Scanner
|
|
43
47
|
process_config
|
44
48
|
Brakeman.notify "Parsing files..."
|
45
49
|
parse_files
|
50
|
+
Brakeman.notify "Detecting file types..."
|
51
|
+
detect_file_types
|
46
52
|
Brakeman.notify "Processing initializers..."
|
47
53
|
process_initializers
|
48
54
|
Brakeman.notify "Processing libs..."
|
@@ -65,29 +71,47 @@ class Brakeman::Scanner
|
|
65
71
|
end
|
66
72
|
|
67
73
|
def parse_files
|
68
|
-
fp = Brakeman::FileParser.new tracker
|
69
|
-
|
70
|
-
files = {
|
71
|
-
:initializers => @app_tree.initializer_paths,
|
72
|
-
:controllers => @app_tree.controller_paths,
|
73
|
-
:models => @app_tree.model_paths
|
74
|
-
}
|
75
|
-
|
76
|
-
unless options[:skip_libs]
|
77
|
-
files[:libs] = @app_tree.lib_paths
|
78
|
-
end
|
74
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
79
75
|
|
80
|
-
|
81
|
-
fp.parse_files paths, name
|
82
|
-
end
|
76
|
+
fp.parse_files tracker.app_tree.ruby_file_paths
|
83
77
|
|
84
78
|
template_parser = Brakeman::TemplateParser.new(tracker, fp)
|
85
79
|
|
86
|
-
fp.read_files(@app_tree.template_paths
|
80
|
+
fp.read_files(@app_tree.template_paths) do |path, contents|
|
87
81
|
template_parser.parse_template path, contents
|
88
82
|
end
|
89
83
|
|
90
|
-
|
84
|
+
# Collect errors raised during parsing
|
85
|
+
tracker.add_errors(fp.errors)
|
86
|
+
|
87
|
+
@parsed_files = fp.file_list
|
88
|
+
end
|
89
|
+
|
90
|
+
def detect_file_types
|
91
|
+
@file_list = {
|
92
|
+
controllers: [],
|
93
|
+
initializers: [],
|
94
|
+
libs: [],
|
95
|
+
models: [],
|
96
|
+
templates: [],
|
97
|
+
}
|
98
|
+
|
99
|
+
detector = Brakeman::FileTypeDetector.new
|
100
|
+
|
101
|
+
@parsed_files.each do |file|
|
102
|
+
if file.is_a? Brakeman::TemplateParser::TemplateFile
|
103
|
+
@file_list[:templates] << file
|
104
|
+
else
|
105
|
+
type = detector.detect_type(file)
|
106
|
+
unless type == :skip
|
107
|
+
if @file_list[type].nil?
|
108
|
+
raise type.to_s
|
109
|
+
else
|
110
|
+
@file_list[type] << file
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
91
115
|
end
|
92
116
|
|
93
117
|
#Process config/environment.rb and config/gems.rb
|
@@ -325,7 +349,7 @@ class Brakeman::Scanner
|
|
325
349
|
end
|
326
350
|
|
327
351
|
def parse_ruby_file file
|
328
|
-
fp = Brakeman::FileParser.new(
|
352
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
329
353
|
fp.parse_ruby(file.read, file)
|
330
354
|
end
|
331
355
|
end
|
data/lib/brakeman/tracker.rb
CHANGED
data/lib/brakeman/util.rb
CHANGED
@@ -321,7 +321,7 @@ module Brakeman::Util
|
|
321
321
|
if node_type? current, :class
|
322
322
|
return true
|
323
323
|
elsif sexp? current
|
324
|
-
todo = current.
|
324
|
+
todo = current[1..-1].concat todo
|
325
325
|
end
|
326
326
|
end
|
327
327
|
|
@@ -334,7 +334,7 @@ module Brakeman::Util
|
|
334
334
|
if args.empty? or args.first.empty?
|
335
335
|
#nothing to do
|
336
336
|
elsif node_type? args.first, :arglist
|
337
|
-
call.concat args.first
|
337
|
+
call.concat args.first[1..-1]
|
338
338
|
elsif args.first.node_type.is_a? Sexp #just a list of args
|
339
339
|
call.concat args.first
|
340
340
|
else
|
@@ -368,8 +368,13 @@ module Brakeman::Util
|
|
368
368
|
#
|
369
369
|
# views/test/something.html.erb -> test/something
|
370
370
|
def template_path_to_name path
|
371
|
-
names = path.relative.split(
|
371
|
+
names = path.relative.split('/')
|
372
372
|
names.last.gsub!(/(\.(html|js)\..*|\.(rhtml|haml|erb|slim))$/, '')
|
373
|
-
|
373
|
+
|
374
|
+
if names.include? 'views'
|
375
|
+
names[(names.index('views') + 1)..-1]
|
376
|
+
else
|
377
|
+
names
|
378
|
+
end.join('/').to_sym
|
374
379
|
end
|
375
380
|
end
|
data/lib/brakeman/version.rb
CHANGED
data/lib/ruby_parser/bm_sexp.rb
CHANGED
@@ -175,7 +175,7 @@ class Sexp
|
|
175
175
|
start_index = 3
|
176
176
|
|
177
177
|
if exp.is_a? Sexp and exp.node_type == :arglist
|
178
|
-
exp = exp
|
178
|
+
exp = exp[1..-1]
|
179
179
|
end
|
180
180
|
|
181
181
|
exp.each_with_index do |e, i|
|
@@ -198,10 +198,10 @@ class Sexp
|
|
198
198
|
|
199
199
|
case self.node_type
|
200
200
|
when :call, :attrasgn, :safe_call, :safe_attrasgn
|
201
|
-
self
|
201
|
+
self[3..-1].unshift :arglist
|
202
202
|
when :super, :zsuper
|
203
203
|
if self[1]
|
204
|
-
self.
|
204
|
+
self[1..-1].unshift :arglist
|
205
205
|
else
|
206
206
|
Sexp.new(:arglist)
|
207
207
|
end
|
@@ -218,13 +218,13 @@ class Sexp
|
|
218
218
|
case self.node_type
|
219
219
|
when :call, :attrasgn, :safe_call, :safe_attrasgn
|
220
220
|
if self[3]
|
221
|
-
self
|
221
|
+
self[3..-1]
|
222
222
|
else
|
223
223
|
Sexp.new
|
224
224
|
end
|
225
225
|
when :super, :zsuper
|
226
226
|
if self[1]
|
227
|
-
self
|
227
|
+
self[1..-1]
|
228
228
|
else
|
229
229
|
Sexp.new
|
230
230
|
end
|
@@ -512,7 +512,7 @@ class Sexp
|
|
512
512
|
self.slice!(index..-1) #Remove old body
|
513
513
|
|
514
514
|
if exp.first == :rlist
|
515
|
-
exp = exp
|
515
|
+
exp = exp[1..-1]
|
516
516
|
end
|
517
517
|
|
518
518
|
#Insert new body
|
@@ -529,11 +529,11 @@ class Sexp
|
|
529
529
|
|
530
530
|
case self.node_type
|
531
531
|
when :defn, :class
|
532
|
-
self
|
532
|
+
self[3..-1]
|
533
533
|
when :defs
|
534
|
-
self
|
534
|
+
self[4..-1]
|
535
535
|
when :module
|
536
|
-
self
|
536
|
+
self[2..-1]
|
537
537
|
end
|
538
538
|
end
|
539
539
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brakeman-lib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.0.pre1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Collins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -212,20 +212,6 @@ dependencies:
|
|
212
212
|
- - "<="
|
213
213
|
- !ruby/object:Gem::Version
|
214
214
|
version: '4.1'
|
215
|
-
- !ruby/object:Gem::Dependency
|
216
|
-
name: rexml
|
217
|
-
requirement: !ruby/object:Gem::Requirement
|
218
|
-
requirements:
|
219
|
-
- - "~>"
|
220
|
-
- !ruby/object:Gem::Version
|
221
|
-
version: '3.0'
|
222
|
-
type: :runtime
|
223
|
-
prerelease: false
|
224
|
-
version_requirements: !ruby/object:Gem::Requirement
|
225
|
-
requirements:
|
226
|
-
- - "~>"
|
227
|
-
- !ruby/object:Gem::Version
|
228
|
-
version: '3.0'
|
229
215
|
description: Brakeman detects security vulnerabilities in Ruby on Rails applications
|
230
216
|
via static analysis. This package declares gem dependencies instead of bundling
|
231
217
|
them.
|
@@ -315,8 +301,10 @@ files:
|
|
315
301
|
- lib/brakeman/checks/check_template_injection.rb
|
316
302
|
- lib/brakeman/checks/check_translate_bug.rb
|
317
303
|
- lib/brakeman/checks/check_unsafe_reflection.rb
|
304
|
+
- lib/brakeman/checks/check_unsafe_reflection_methods.rb
|
318
305
|
- lib/brakeman/checks/check_unscoped_find.rb
|
319
306
|
- lib/brakeman/checks/check_validation_regex.rb
|
307
|
+
- lib/brakeman/checks/check_verb_confusion.rb
|
320
308
|
- lib/brakeman/checks/check_weak_hash.rb
|
321
309
|
- lib/brakeman/checks/check_without_protection.rb
|
322
310
|
- lib/brakeman/checks/check_xml_dos.rb
|
@@ -347,6 +335,7 @@ files:
|
|
347
335
|
- lib/brakeman/processors/haml_template_processor.rb
|
348
336
|
- lib/brakeman/processors/lib/basic_processor.rb
|
349
337
|
- lib/brakeman/processors/lib/call_conversion_helper.rb
|
338
|
+
- lib/brakeman/processors/lib/file_type_detector.rb
|
350
339
|
- lib/brakeman/processors/lib/find_all_calls.rb
|
351
340
|
- lib/brakeman/processors/lib/find_call.rb
|
352
341
|
- lib/brakeman/processors/lib/find_return_value.rb
|
@@ -383,6 +372,7 @@ files:
|
|
383
372
|
- lib/brakeman/report/report_junit.rb
|
384
373
|
- lib/brakeman/report/report_markdown.rb
|
385
374
|
- lib/brakeman/report/report_sarif.rb
|
375
|
+
- lib/brakeman/report/report_sonar.rb
|
386
376
|
- lib/brakeman/report/report_table.rb
|
387
377
|
- lib/brakeman/report/report_tabs.rb
|
388
378
|
- lib/brakeman/report/report_text.rb
|
@@ -432,14 +422,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
432
422
|
requirements:
|
433
423
|
- - ">="
|
434
424
|
- !ruby/object:Gem::Version
|
435
|
-
version:
|
425
|
+
version: 2.4.0
|
436
426
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
437
427
|
requirements:
|
438
|
-
- - "
|
428
|
+
- - ">"
|
439
429
|
- !ruby/object:Gem::Version
|
440
|
-
version:
|
430
|
+
version: 1.3.1
|
441
431
|
requirements: []
|
442
|
-
rubygems_version: 3.
|
432
|
+
rubygems_version: 3.1.2
|
443
433
|
signing_key:
|
444
434
|
specification_version: 4
|
445
435
|
summary: Security vulnerability scanner for Ruby on Rails.
|