brakeman 4.10.0 → 5.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +10 -0
  3. data/README.md +1 -1
  4. data/bundle/load.rb +8 -8
  5. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/CHANGELOG.md +9 -0
  6. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/FAQ.md +0 -0
  7. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/Gemfile +1 -4
  8. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/MIT-LICENSE +0 -0
  9. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/README.md +2 -3
  10. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/REFERENCE.md +20 -2
  11. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/TODO +0 -0
  12. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/haml.gemspec +2 -1
  13. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml.rb +0 -0
  14. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/attribute_builder.rb +3 -3
  15. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/attribute_compiler.rb +42 -31
  16. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/attribute_parser.rb +0 -0
  17. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/buffer.rb +0 -0
  18. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/compiler.rb +0 -0
  19. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/engine.rb +0 -0
  20. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/error.rb +0 -0
  21. data/bundle/ruby/2.7.0/gems/haml-5.2.0/lib/haml/escapable.rb +77 -0
  22. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/exec.rb +0 -0
  23. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/filters.rb +0 -0
  24. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/generator.rb +0 -0
  25. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers.rb +7 -1
  26. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers/action_view_extensions.rb +0 -0
  27. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers/action_view_mods.rb +0 -0
  28. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers/action_view_xss_mods.rb +0 -0
  29. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers/safe_erubi_template.rb +0 -0
  30. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers/safe_erubis_template.rb +0 -0
  31. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/helpers/xss_mods.rb +6 -3
  32. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/options.rb +0 -0
  33. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/parser.rb +1 -1
  34. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/plugin.rb +0 -0
  35. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/railtie.rb +0 -0
  36. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/sass_rails_filter.rb +0 -0
  37. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/template.rb +0 -0
  38. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/template/options.rb +0 -0
  39. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/temple_engine.rb +0 -0
  40. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/temple_line_counter.rb +0 -0
  41. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/util.rb +0 -0
  42. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/lib/haml/version.rb +1 -1
  43. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/yard/default/fulldoc/html/css/common.sass +0 -0
  44. data/bundle/ruby/2.7.0/gems/{haml-5.1.2 → haml-5.2.0}/yard/default/layout/html/footer.erb +0 -0
  45. data/lib/brakeman.rb +6 -0
  46. data/lib/brakeman/app_tree.rb +36 -3
  47. data/lib/brakeman/checks/check_unsafe_reflection_methods.rb +68 -0
  48. data/lib/brakeman/checks/check_verb_confusion.rb +75 -0
  49. data/lib/brakeman/file_parser.rb +19 -18
  50. data/lib/brakeman/options.rb +5 -1
  51. data/lib/brakeman/parsers/template_parser.rb +2 -3
  52. data/lib/brakeman/processors/haml_template_processor.rb +8 -1
  53. data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
  54. data/lib/brakeman/report.rb +8 -0
  55. data/lib/brakeman/report/report_sonar.rb +38 -0
  56. data/lib/brakeman/rescanner.rb +7 -5
  57. data/lib/brakeman/scanner.rb +42 -18
  58. data/lib/brakeman/tracker.rb +6 -0
  59. data/lib/brakeman/util.rb +7 -2
  60. data/lib/brakeman/version.rb +1 -1
  61. data/lib/brakeman/warning_codes.rb +2 -0
  62. metadata +49 -45
  63. data/bundle/ruby/2.7.0/gems/haml-5.1.2/lib/haml/escapable.rb +0 -50
@@ -8,12 +8,15 @@ module Haml
8
8
  # to work with Rails' XSS protection methods.
9
9
  module XssMods
10
10
  def self.included(base)
11
- %w[html_escape find_and_preserve preserve list_of surround
12
- precede succeed capture_haml haml_concat haml_internal_concat haml_indent
13
- escape_once].each do |name|
11
+ %w[find_and_preserve preserve list_of surround
12
+ precede succeed capture_haml haml_concat haml_internal_concat haml_indent].each do |name|
14
13
  base.send(:alias_method, "#{name}_without_haml_xss", name)
15
14
  base.send(:alias_method, name, "#{name}_with_haml_xss")
16
15
  end
16
+ # Those two always have _without_haml_xss
17
+ %w[html_escape escape_once].each do |name|
18
+ base.send(:alias_method, name, "#{name}_with_haml_xss")
19
+ end
17
20
  end
18
21
 
19
22
  # Don't escape text that's already safe,
@@ -307,7 +307,7 @@ module Haml
307
307
  return ParseNode.new(:plain, line.index + 1, :text => line.text)
308
308
  end
309
309
 
310
- escape_html = @options.escape_html if escape_html.nil?
310
+ escape_html = @options.escape_html && @options.mime_type != 'text/plain' if escape_html.nil?
311
311
  line.text = unescape_interpolation(line.text, escape_html)
312
312
  script(line, false)
313
313
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Haml
4
- VERSION = "5.1.2"
4
+ VERSION = "5.2.0"
5
5
  end
@@ -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
@@ -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,32 +3,31 @@ 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
@@ -38,15 +37,17 @@ module Brakeman
38
37
  Brakeman.debug "Parsing #{path}"
39
38
  RubyParser.new.parse input, path, @timeout
40
39
  rescue Racc::ParseError => e
41
- @tracker.error e, "Could not parse #{path}"
42
- nil
40
+ error e.exception(e.message + "\nCould not parse #{path}")
43
41
  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
42
+ error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout")
46
43
  rescue => e
47
- @tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
48
- nil
44
+ error e.exception(e.message + "\nWhile processing #{path}")
49
45
  end
50
46
  end
47
+
48
+ def error exception
49
+ @errors << exception
50
+ nil
51
+ end
51
52
  end
52
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
@@ -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)