brakeman-min 4.9.1 → 5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +46 -0
- data/README.md +11 -2
- data/lib/brakeman.rb +21 -4
- data/lib/brakeman/app_tree.rb +36 -3
- data/lib/brakeman/checks/base_check.rb +7 -1
- data/lib/brakeman/checks/check_execute.rb +2 -1
- data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
- data/lib/brakeman/checks/check_regex_dos.rb +1 -1
- data/lib/brakeman/checks/check_sql.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 +24 -18
- data/lib/brakeman/options.rb +5 -1
- data/lib/brakeman/parsers/template_parser.rb +26 -3
- data/lib/brakeman/processors/alias_processor.rb +40 -13
- data/lib/brakeman/processors/base_processor.rb +4 -4
- data/lib/brakeman/processors/controller_processor.rb +1 -1
- data/lib/brakeman/processors/haml_template_processor.rb +8 -1
- data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
- data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
- data/lib/brakeman/processors/output_processor.rb +1 -1
- data/lib/brakeman/processors/template_alias_processor.rb +5 -0
- data/lib/brakeman/report.rb +15 -0
- data/lib/brakeman/report/report_base.rb +0 -2
- data/lib/brakeman/report/report_csv.rb +37 -60
- data/lib/brakeman/report/report_junit.rb +2 -2
- data/lib/brakeman/report/report_sarif.rb +114 -0
- data/lib/brakeman/report/report_sonar.rb +38 -0
- data/lib/brakeman/report/report_tabs.rb +1 -1
- data/lib/brakeman/report/report_text.rb +1 -1
- data/lib/brakeman/rescanner.rb +7 -5
- data/lib/brakeman/scanner.rb +44 -18
- data/lib/brakeman/tracker.rb +6 -0
- data/lib/brakeman/tracker/config.rb +73 -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.rb +10 -2
- data/lib/brakeman/warning_codes.rb +2 -0
- data/lib/ruby_parser/bm_sexp.rb +9 -9
- metadata +8 -3
data/lib/brakeman/file_parser.rb
CHANGED
@@ -3,50 +3,56 @@ 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
|
|
35
|
+
# _path_ can be a string or a Brakeman::FilePath
|
36
36
|
def parse_ruby input, path
|
37
|
+
if path.is_a? Brakeman::FilePath
|
38
|
+
path = path.relative
|
39
|
+
end
|
40
|
+
|
37
41
|
begin
|
38
42
|
Brakeman.debug "Parsing #{path}"
|
39
43
|
RubyParser.new.parse input, path, @timeout
|
40
44
|
rescue Racc::ParseError => e
|
41
|
-
|
42
|
-
nil
|
45
|
+
error e.exception(e.message + "\nCould not parse #{path}")
|
43
46
|
rescue Timeout::Error => e
|
44
|
-
|
45
|
-
nil
|
47
|
+
error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
|
46
48
|
rescue => e
|
47
|
-
|
48
|
-
nil
|
49
|
+
error e.exception(e.message + "\nWhile processing #{path}")
|
49
50
|
end
|
50
51
|
end
|
52
|
+
|
53
|
+
def error exception
|
54
|
+
@errors << exception
|
55
|
+
nil
|
56
|
+
end
|
51
57
|
end
|
52
58
|
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],
|
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,7 @@ module Brakeman
|
|
9
9
|
def initialize tracker, file_parser
|
10
10
|
@tracker = tracker
|
11
11
|
@file_parser = file_parser
|
12
|
-
@
|
12
|
+
@slim_smart = nil # Load slim/smart ?
|
13
13
|
end
|
14
14
|
|
15
15
|
def parse_template path, text
|
@@ -33,7 +33,7 @@ module Brakeman
|
|
33
33
|
end
|
34
34
|
|
35
35
|
if src and ast = @file_parser.parse_ruby(src, path)
|
36
|
-
@file_parser.file_list
|
36
|
+
@file_parser.file_list << TemplateFile.new(path, ast, name, type)
|
37
37
|
end
|
38
38
|
rescue Racc::ParseError => e
|
39
39
|
tracker.error e, "Could not parse #{path}"
|
@@ -89,6 +89,14 @@ module Brakeman
|
|
89
89
|
|
90
90
|
def parse_slim path, text
|
91
91
|
Brakeman.load_brakeman_dependency 'slim'
|
92
|
+
|
93
|
+
if @slim_smart.nil? and load_slim_smart?
|
94
|
+
@slim_smart = true
|
95
|
+
Brakeman.load_brakeman_dependency 'slim/smart'
|
96
|
+
else
|
97
|
+
@slim_smart = false
|
98
|
+
end
|
99
|
+
|
92
100
|
require_relative 'slim_embedded'
|
93
101
|
|
94
102
|
Slim::Template.new(path,
|
@@ -96,8 +104,23 @@ module Brakeman
|
|
96
104
|
:generator => Temple::Generators::RailsOutputBuffer) { text }.precompiled_template
|
97
105
|
end
|
98
106
|
|
107
|
+
def load_slim_smart?
|
108
|
+
return !@slim_smart unless @slim_smart.nil?
|
109
|
+
|
110
|
+
# Terrible hack to find
|
111
|
+
# gem "slim", "~> 3.0.1", require: ["slim", "slim/smart"]
|
112
|
+
if tracker.app_tree.exists? 'Gemfile'
|
113
|
+
gemfile_contents = tracker.app_tree.file_path('Gemfile').read
|
114
|
+
if gemfile_contents.include? 'slim/smart'
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
false
|
120
|
+
end
|
121
|
+
|
99
122
|
def self.parse_inline_erb tracker, text
|
100
|
-
fp = Brakeman::FileParser.new(tracker)
|
123
|
+
fp = Brakeman::FileParser.new(tracker.app_tree, tracker.options[:parser_timeout])
|
101
124
|
tp = self.new(tracker, fp)
|
102
125
|
src = tp.parse_erb '_inline_', text
|
103
126
|
type = tp.erubis? ? :erubis : :erb
|
@@ -161,6 +161,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
161
161
|
ARRAY_CONST = s(:const, :Array)
|
162
162
|
HASH_CONST = s(:const, :Hash)
|
163
163
|
RAILS_TEST = s(:call, s(:call, s(:const, :Rails), :env), :test?)
|
164
|
+
RAILS_DEV = s(:call, s(:call, s(:const, :Rails), :env), :development?)
|
164
165
|
|
165
166
|
#Process a method call.
|
166
167
|
def process_call exp
|
@@ -182,11 +183,17 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
182
183
|
return exp
|
183
184
|
end
|
184
185
|
|
186
|
+
# If x(*[1,2,3]) change to x(1,2,3)
|
187
|
+
# if that's the only argument
|
188
|
+
if splat_array? exp.first_arg and exp.second_arg.nil?
|
189
|
+
exp.arglist = exp.first_arg[1].sexp_body
|
190
|
+
end
|
191
|
+
|
185
192
|
target = exp.target
|
186
193
|
method = exp.method
|
187
194
|
first_arg = exp.first_arg
|
188
195
|
|
189
|
-
if method == :send or method == :try
|
196
|
+
if method == :send or method == :__send__ or method == :try
|
190
197
|
collapse_send_call exp, first_arg
|
191
198
|
end
|
192
199
|
|
@@ -194,11 +201,11 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
194
201
|
res = process_or_simple_operation(exp)
|
195
202
|
return res if res
|
196
203
|
elsif target == ARRAY_CONST and method == :new
|
197
|
-
return Sexp.new(:array, *exp.args)
|
204
|
+
return Sexp.new(:array, *exp.args).line(exp.line)
|
198
205
|
elsif target == HASH_CONST and method == :new and first_arg.nil? and !node_type?(@exp_context.last, :iter)
|
199
|
-
return Sexp.new(:hash)
|
200
|
-
elsif exp == RAILS_TEST
|
201
|
-
return Sexp.new(:false)
|
206
|
+
return Sexp.new(:hash).line(exp.line)
|
207
|
+
elsif exp == RAILS_TEST or exp == RAILS_DEV
|
208
|
+
return Sexp.new(:false).line(exp.line)
|
202
209
|
end
|
203
210
|
|
204
211
|
#See if it is possible to simplify some basic cases
|
@@ -236,7 +243,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
236
243
|
env[target_var] = target
|
237
244
|
return target
|
238
245
|
elsif string? target and string_interp? first_arg
|
239
|
-
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg
|
246
|
+
exp = Sexp.new(:dstr, target.value + first_arg[1]).concat(first_arg.sexp_body(2)).line(exp.line)
|
240
247
|
env[target_var] = exp
|
241
248
|
elsif string? first_arg and string_interp? target
|
242
249
|
if string? target.last
|
@@ -287,7 +294,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
287
294
|
|
288
295
|
# Painful conversion of Array#join into string interpolation
|
289
296
|
def process_array_join array, join_str
|
290
|
-
result = s()
|
297
|
+
result = s().line(array.line)
|
291
298
|
|
292
299
|
join_value = if string? join_str
|
293
300
|
join_str.value
|
@@ -325,11 +332,11 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
325
332
|
result.unshift combined_first
|
326
333
|
|
327
334
|
# Have to fix up strings that follow interpolation
|
328
|
-
result.reduce(s(:dstr)) do |memo, e|
|
335
|
+
result.reduce(s(:dstr).line(array.line)) do |memo, e|
|
329
336
|
if string? e and node_type? memo.last, :evstr
|
330
337
|
e.value = "#{join_value}#{e.value}"
|
331
338
|
elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
|
332
|
-
memo << s(:str, join_value)
|
339
|
+
memo << s(:str, join_value).line(e.line)
|
333
340
|
end
|
334
341
|
|
335
342
|
memo << e
|
@@ -340,12 +347,29 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
340
347
|
if item.is_a? String
|
341
348
|
"#{item}#{join_value}"
|
342
349
|
elsif string? item or symbol? item or number? item
|
343
|
-
s(:str, "#{item.value}#{join_value}")
|
350
|
+
s(:str, "#{item.value}#{join_value}").line(item.line)
|
344
351
|
else
|
345
|
-
s(:evstr, item)
|
352
|
+
s(:evstr, item).line(item.line)
|
346
353
|
end
|
347
354
|
end
|
348
355
|
|
356
|
+
TEMP_FILE_CLASS = s(:const, :Tempfile)
|
357
|
+
|
358
|
+
def temp_file_open? exp
|
359
|
+
call? exp and
|
360
|
+
exp.target == TEMP_FILE_CLASS and
|
361
|
+
exp.method == :open
|
362
|
+
end
|
363
|
+
|
364
|
+
def temp_file_new line
|
365
|
+
s(:call, TEMP_FILE_CLASS, :new).line(line)
|
366
|
+
end
|
367
|
+
|
368
|
+
def splat_array? exp
|
369
|
+
node_type? exp, :splat and
|
370
|
+
node_type? exp[1], :array
|
371
|
+
end
|
372
|
+
|
349
373
|
def process_iter exp
|
350
374
|
@exp_context.push exp
|
351
375
|
exp[1] = process exp.block_call
|
@@ -363,6 +387,9 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
363
387
|
# Iterating over an array of all literal values
|
364
388
|
local = Sexp.new(:lvar, block_args.last)
|
365
389
|
env.current[local] = safe_literal(exp.line)
|
390
|
+
elsif temp_file_open? call
|
391
|
+
local = Sexp.new(:lvar, block_args.last)
|
392
|
+
env.current[local] = temp_file_new(exp.line)
|
366
393
|
else
|
367
394
|
block_args.each do |e|
|
368
395
|
#Force block arg(s) to be local
|
@@ -663,7 +690,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
663
690
|
end
|
664
691
|
end
|
665
692
|
else
|
666
|
-
new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value)
|
693
|
+
new_value = process s(:call, s(:call, target_var, :[], index), exp[3], value).line(exp.line)
|
667
694
|
|
668
695
|
env[match] = new_value
|
669
696
|
end
|
@@ -941,7 +968,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
941
968
|
args = exp.args
|
942
969
|
exp.pop # remove last arg
|
943
970
|
if args.length > 1
|
944
|
-
exp.arglist = args
|
971
|
+
exp.arglist = args.sexp_body
|
945
972
|
end
|
946
973
|
end
|
947
974
|
|
@@ -8,7 +8,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
8
8
|
include Brakeman::SafeCallHelper
|
9
9
|
include Brakeman::Util
|
10
10
|
|
11
|
-
IGNORE = Sexp.new
|
11
|
+
IGNORE = Sexp.new(:ignore).line(0)
|
12
12
|
|
13
13
|
#Return a new Processor.
|
14
14
|
def initialize tracker
|
@@ -216,7 +216,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
216
216
|
#
|
217
217
|
#And also :layout for inside templates
|
218
218
|
def find_render_type call, in_view = false
|
219
|
-
rest = Sexp.new(:hash)
|
219
|
+
rest = Sexp.new(:hash).line(call.line)
|
220
220
|
type = nil
|
221
221
|
value = nil
|
222
222
|
first_arg = call.first_arg
|
@@ -236,7 +236,7 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
236
236
|
end
|
237
237
|
elsif first_arg.is_a? Symbol or first_arg.is_a? String
|
238
238
|
type = :action
|
239
|
-
value = Sexp.new(:lit, first_arg.to_sym)
|
239
|
+
value = Sexp.new(:lit, first_arg.to_sym).line(call.line)
|
240
240
|
elsif first_arg.nil?
|
241
241
|
type = :default
|
242
242
|
elsif not hash? first_arg
|
@@ -293,6 +293,6 @@ class Brakeman::BaseProcessor < Brakeman::SexpProcessor
|
|
293
293
|
@tracker.processor.process_template(template_name, ast, type, nil, @current_file)
|
294
294
|
@tracker.processor.process_template_alias(@tracker.templates[template_name])
|
295
295
|
|
296
|
-
return s(:lit, template_name), options
|
296
|
+
return s(:lit, template_name).line(value.line), options
|
297
297
|
end
|
298
298
|
end
|
@@ -76,6 +76,13 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
+
ESCAPE_METHODS = [
|
80
|
+
:html_escape,
|
81
|
+
:html_escape_without_haml_xss,
|
82
|
+
:escape_once,
|
83
|
+
:escape_once_without_haml_xss
|
84
|
+
]
|
85
|
+
|
79
86
|
def get_pushed_value exp, default = :output
|
80
87
|
return exp unless sexp? exp
|
81
88
|
|
@@ -105,7 +112,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
105
112
|
when :call
|
106
113
|
if exp.method == :to_s or exp.method == :strip
|
107
114
|
get_pushed_value(exp.target, default)
|
108
|
-
elsif haml_helpers? exp.target and exp.method
|
115
|
+
elsif haml_helpers? exp.target and ESCAPE_METHODS.include? exp.method
|
109
116
|
get_pushed_value(exp.first_arg, :escaped_output)
|
110
117
|
elsif @javascript and (exp.method == :j or exp.method == :escape_javascript) # TODO: Remove - this is not safe
|
111
118
|
get_pushed_value(exp.first_arg, :escaped_output)
|
@@ -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
|
@@ -57,6 +57,20 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BasicProcessor
|
|
57
57
|
exp
|
58
58
|
end
|
59
59
|
|
60
|
+
#Look for configuration settings that
|
61
|
+
#are just a call like
|
62
|
+
#
|
63
|
+
# config.load_defaults 5.2
|
64
|
+
def process_call exp
|
65
|
+
return exp unless @inside_config
|
66
|
+
|
67
|
+
if exp.target == RAILS_CONFIG and exp.first_arg
|
68
|
+
@tracker.config.rails[exp.method] = exp.first_arg
|
69
|
+
end
|
70
|
+
|
71
|
+
exp
|
72
|
+
end
|
73
|
+
|
60
74
|
#Look for configuration settings
|
61
75
|
def process_attrasgn exp
|
62
76
|
return exp unless @inside_config
|
@@ -71,22 +85,8 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BasicProcessor
|
|
71
85
|
@tracker.config.rails[attribute] = exp.first_arg
|
72
86
|
end
|
73
87
|
elsif include_rails_config? exp
|
74
|
-
|
75
|
-
|
76
|
-
options[0..-2].each do |o|
|
77
|
-
level[o] ||= {}
|
78
|
-
|
79
|
-
option = level[o]
|
80
|
-
|
81
|
-
if not option.is_a? Hash
|
82
|
-
Brakeman.debug "[Notice] Skipping config setting: #{options.map(&:to_s).join(".")}"
|
83
|
-
return exp
|
84
|
-
end
|
85
|
-
|
86
|
-
level = level[o]
|
87
|
-
end
|
88
|
-
|
89
|
-
level[options.last] = exp.first_arg
|
88
|
+
options_path = get_rails_config exp
|
89
|
+
@tracker.config.set_rails_config(exp.first_arg, *options_path)
|
90
90
|
end
|
91
91
|
|
92
92
|
exp
|