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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9433874563193068795eb4d4a90dd176132b04130cf68a9689753caff3c9df1e
4
- data.tar.gz: 19e73894774e624624edecd48c28dd53aa4da68e7482afe272fec48398551040
3
+ metadata.gz: d69b909ed56306516d662fbc2bd25c22c3886925a8a98ca81ea761f0bb3851ca
4
+ data.tar.gz: bb2f1fe108cccdefb21da6cce220489d0890659bfaf2a773e3e9201892db0f9b
5
5
  SHA512:
6
- metadata.gz: fbbd606baa82361d62752a44a0c3a095083d8e3b89c213cad7e8bdc97341a7fd09c3973c80c50d58349dc5bc9e98fe2ca390710d2419636a81f40d6dd75add6f
7
- data.tar.gz: 89331cbf5168e088bdac777e65b3ee4cbe3244792f64f3c13db084e00db0fc3ebf792801d7c1f7fcc2b3c929cafd83e27381ecd03550d69b761cb94cf228856b
6
+ metadata.gz: 004d9116ce3b94abe715744571e124b477b9e30acaa623e3e46dc28493e9829d9c7cee9a060024eb6aa8158336d0622e81fb68a43343e5a4dddf837c214e62c8
7
+ data.tar.gz: af6556b75109436f49a1087ace59aab6c235a82452d92159770fbd8248c56aa458d254307046e73b649563d7471c88a31b049257ad5305498963d815aa1f2439
data/CHANGES.md CHANGED
@@ -1,10 +1,12 @@
1
- # 4.10.1 - 2020-12-24
2
-
3
- * Declare REXML as a dependency (Ruby 3.0 compatibility)
4
- * Use `Sexp#sexp_body` instead of `Sexp#[..]` (Ruby 3.0 compatibility)
5
- * Prevent render loops when template names are absolute paths
6
- * Ensure RubyParser is passed file path as a String
7
- * Support new Haml 5.2.0 escaping method
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 `codeclimate`.
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
@@ -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("app/**/views", "*.{#{VIEW_EXTENSIONS}}") +
113
- find_paths("app/**/views", "*.{erb,haml,slim}").reject { |path| File.basename(path).count(".") > 1 }
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.sexp_body.sexp_body)
211
+ if res = dangerous?(e.values[1..-1])
212
212
  return res
213
213
  end
214
214
  elsif node_type? e, :or, :evstr, :dstr
@@ -29,7 +29,7 @@ class Brakeman::CheckRegexDoS < Brakeman::BaseCheck
29
29
  return unless original? result
30
30
 
31
31
  call = result[:call]
32
- components = call.sexp_body
32
+ components = call[1..-1]
33
33
 
34
34
  components.any? do |component|
35
35
  next unless sexp? component
@@ -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
@@ -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 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
 
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
- @tracker.error e, "Could not parse #{path}"
47
- nil
40
+ error e.exception(e.message + "\nCould not parse #{path}")
48
41
  rescue Timeout::Error => e
49
- @tracker.error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout"), caller
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
- @tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
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
@@ -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[:templates] << TemplateFile.new(path, ast, name, type)
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.sexp_body(2))
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.sexp_body
944
+ exp.arglist = args[1..-1]
945
945
  end
946
946
  end
947
947
 
@@ -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.sexp_body
205
+ block_inner = exp.block[1..-1]
206
206
  else
207
207
  block_inner = [exp.block]
208
208
  end
@@ -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
@@ -88,7 +88,7 @@ class Brakeman::OutputProcessor < Ruby2Ruby
88
88
 
89
89
  def process_iter exp
90
90
  call = process exp[1]
91
- block = process_rlist exp.sexp_body(3)
91
+ block = process_rlist exp[3..-1]
92
92
  out = "#{call} do\n #{block}\n end"
93
93
 
94
94
  out
@@ -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}"
@@ -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
@@ -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
- process_template fp.file_list[:templates].first
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, :rescan
395
- file_parser.file_list[:rescan]
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
 
@@ -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
- raise Brakeman::NoApplication, "Please supply the path to a Rails application (looking in #{@app_tree.root})."
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
- files.each do |name, paths|
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, :templates) do |path, contents|
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
- @file_list = fp.file_list
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(self.tracker)
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
@@ -68,6 +68,12 @@ class Brakeman::Tracker
68
68
  }
69
69
  end
70
70
 
71
+ def add_errors exceptions
72
+ exceptions.each do |e|
73
+ error(e)
74
+ end
75
+ end
76
+
71
77
  #Run a set of checks on the current information. Results will be stored
72
78
  #in Tracker#checks.
73
79
  def run_checks
@@ -125,7 +125,7 @@ module Brakeman
125
125
  value = args[-1][2]
126
126
  case value.node_type
127
127
  when :array
128
- filter[option] = value.sexp_body.map {|v| v[1] }
128
+ filter[option] = value[1..-1].map {|v| v[1] }
129
129
  when :lit, :str
130
130
  filter[option] = value[1]
131
131
  else
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.sexp_body.concat todo
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.sexp_body
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
- names[(names.index("views") + 1)..-1].join("/").to_sym
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
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "4.10.1"
2
+ Version = "5.0.0.pre1"
3
3
  end
@@ -119,6 +119,8 @@ module Brakeman::WarningCodes
119
119
  :CVE_2020_8159 => 115,
120
120
  :CVE_2020_8166 => 116,
121
121
  :erb_template_injection => 117,
122
+ :http_verb_confusion => 118,
123
+ :unsafe_method_reflection => 119,
122
124
 
123
125
  :custom_check => 9090,
124
126
  }
@@ -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.sexp_body
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.sexp_body(3).unshift :arglist
201
+ self[3..-1].unshift :arglist
202
202
  when :super, :zsuper
203
203
  if self[1]
204
- self.sexp_body.unshift :arglist
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.sexp_body(3)
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.sexp_body
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.sexp_body
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.sexp_body(3)
532
+ self[3..-1]
533
533
  when :defs
534
- self.sexp_body(4)
534
+ self[4..-1]
535
535
  when :module
536
- self.sexp_body(2)
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.10.1
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-12-25 00:00:00.000000000 Z
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: '0'
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: '0'
430
+ version: 1.3.1
441
431
  requirements: []
442
- rubygems_version: 3.2.2
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.