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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +46 -0
  3. data/README.md +11 -2
  4. data/lib/brakeman.rb +21 -4
  5. data/lib/brakeman/app_tree.rb +36 -3
  6. data/lib/brakeman/checks/base_check.rb +7 -1
  7. data/lib/brakeman/checks/check_execute.rb +2 -1
  8. data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
  9. data/lib/brakeman/checks/check_regex_dos.rb +1 -1
  10. data/lib/brakeman/checks/check_sql.rb +1 -1
  11. data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
  12. data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
  13. data/lib/brakeman/file_parser.rb +24 -18
  14. data/lib/brakeman/options.rb +5 -1
  15. data/lib/brakeman/parsers/template_parser.rb +26 -3
  16. data/lib/brakeman/processors/alias_processor.rb +40 -13
  17. data/lib/brakeman/processors/base_processor.rb +4 -4
  18. data/lib/brakeman/processors/controller_processor.rb +1 -1
  19. data/lib/brakeman/processors/haml_template_processor.rb +8 -1
  20. data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
  21. data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
  22. data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
  23. data/lib/brakeman/processors/output_processor.rb +1 -1
  24. data/lib/brakeman/processors/template_alias_processor.rb +5 -0
  25. data/lib/brakeman/report.rb +15 -0
  26. data/lib/brakeman/report/report_base.rb +0 -2
  27. data/lib/brakeman/report/report_csv.rb +37 -60
  28. data/lib/brakeman/report/report_junit.rb +2 -2
  29. data/lib/brakeman/report/report_sarif.rb +114 -0
  30. data/lib/brakeman/report/report_sonar.rb +38 -0
  31. data/lib/brakeman/report/report_tabs.rb +1 -1
  32. data/lib/brakeman/report/report_text.rb +1 -1
  33. data/lib/brakeman/rescanner.rb +7 -5
  34. data/lib/brakeman/scanner.rb +44 -18
  35. data/lib/brakeman/tracker.rb +6 -0
  36. data/lib/brakeman/tracker/config.rb +73 -0
  37. data/lib/brakeman/tracker/controller.rb +1 -1
  38. data/lib/brakeman/util.rb +9 -4
  39. data/lib/brakeman/version.rb +1 -1
  40. data/lib/brakeman/warning.rb +10 -2
  41. data/lib/brakeman/warning_codes.rb +2 -0
  42. data/lib/ruby_parser/bm_sexp.rb +9 -9
  43. metadata +8 -3
@@ -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 tracker
9
- @tracker = tracker
10
- @timeout = @tracker.options[:parser_timeout]
11
- @app_tree = @tracker.app_tree
12
- @file_list = {}
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, type
16
- read_files list, type do |path, contents|
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, type
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[type] << result
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
- @tracker.error e, "Could not parse #{path}"
42
- nil
45
+ error e.exception(e.message + "\nCould not parse #{path}")
43
46
  rescue Timeout::Error => e
44
- @tracker.error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout"), caller
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
- @tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
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
@@ -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
- @file_parser.file_list[:templates] ||= []
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[:templates] << TemplateFile.new(path, ast, name, type)
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[2..-1])
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[1..-1]
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 :ignore
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
@@ -202,7 +202,7 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
202
202
  end
203
203
 
204
204
  if node_type? exp.block, :block
205
- block_inner = exp.block[1..-1]
205
+ block_inner = exp.block.sexp_body
206
206
  else
207
207
  block_inner = [exp.block]
208
208
  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 == :html_escape
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
- options = get_rails_config exp
75
- level = @tracker.config.rails
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