brakeman-lib 4.10.1 → 5.0.4

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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +44 -0
  3. data/README.md +11 -2
  4. data/lib/brakeman.rb +17 -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 +1 -0
  8. data/lib/brakeman/checks/check_mass_assignment.rb +4 -6
  9. data/lib/brakeman/checks/check_sanitize_methods.rb +2 -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 +19 -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 +39 -12
  17. data/lib/brakeman/processors/base_processor.rb +4 -4
  18. data/lib/brakeman/processors/lib/file_type_detector.rb +64 -0
  19. data/lib/brakeman/processors/lib/rails3_config_processor.rb +16 -16
  20. data/lib/brakeman/processors/lib/rails4_config_processor.rb +2 -1
  21. data/lib/brakeman/report.rb +8 -0
  22. data/lib/brakeman/report/report_base.rb +0 -2
  23. data/lib/brakeman/report/report_csv.rb +37 -60
  24. data/lib/brakeman/report/report_junit.rb +2 -2
  25. data/lib/brakeman/report/report_sarif.rb +1 -1
  26. data/lib/brakeman/report/report_sonar.rb +38 -0
  27. data/lib/brakeman/report/report_tabs.rb +1 -1
  28. data/lib/brakeman/report/report_text.rb +1 -1
  29. data/lib/brakeman/rescanner.rb +7 -5
  30. data/lib/brakeman/scanner.rb +44 -18
  31. data/lib/brakeman/tracker.rb +6 -0
  32. data/lib/brakeman/tracker/config.rb +73 -0
  33. data/lib/brakeman/util.rb +7 -2
  34. data/lib/brakeman/version.rb +1 -1
  35. data/lib/brakeman/warning.rb +10 -2
  36. data/lib/brakeman/warning_codes.rb +2 -0
  37. metadata +8 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9433874563193068795eb4d4a90dd176132b04130cf68a9689753caff3c9df1e
4
- data.tar.gz: 19e73894774e624624edecd48c28dd53aa4da68e7482afe272fec48398551040
3
+ metadata.gz: 2bc22b69b0b137fe9f223c2469fe6e3857054b0b98621645b52ed94af7fa4886
4
+ data.tar.gz: 183f206e691c8251adef49319ca76939a4bf079cf5b14ead1f3f7923754ff9ff
5
5
  SHA512:
6
- metadata.gz: fbbd606baa82361d62752a44a0c3a095083d8e3b89c213cad7e8bdc97341a7fd09c3973c80c50d58349dc5bc9e98fe2ca390710d2419636a81f40d6dd75add6f
7
- data.tar.gz: 89331cbf5168e088bdac777e65b3ee4cbe3244792f64f3c13db084e00db0fc3ebf792801d7c1f7fcc2b3c929cafd83e27381ecd03550d69b761cb94cf228856b
6
+ metadata.gz: b902bcfbc2be499f0a892534bf443d88ce92f5a0b47edd31b7ac01a964e9ec13230f03f5ba8bb246dcdee27a7e6bd72b873d2826ce2f6b2486f64999f45b52e8
7
+ data.tar.gz: 62f878559fd4aa1f2d96c35742d0c490c0d57c53e07d9a3619675f6519c0fafbaace29e0b616c217ea796132e7810b51b3f06cf1ff60a3a28a34ae9482069f32
data/CHANGES.md CHANGED
@@ -1,3 +1,37 @@
1
+ # 5.0.4 - 2021-06-08
2
+
3
+ (brakeman gem release only)
4
+
5
+ * Update bundled `ruby_parser` to include argument forwarding support
6
+
7
+ # 5.0.2 - 2021-06-07
8
+
9
+ * Fix Loofah version check
10
+
11
+ # 5.0.1 - 2021-04-27
12
+
13
+ * Detect `::Rails.application.configure` too
14
+ * Set more line numbers on Sexps
15
+ * Support loading `slim/smart`
16
+ * Don't fail if $HOME/$USER are not defined
17
+ * Always ignore slice/only calls for mass assignment
18
+ * Convert splat array arguments to arguments
19
+
20
+ # 5.0.0 - 2021-01-26
21
+
22
+ * Ignore `uuid` as a safe attribute
23
+ * Collapse `__send__` calls
24
+ * Ignore `Tempfile#path` in shell commands
25
+ * Ignore development environment
26
+ * Revamp CSV report to a CSV list of warnings
27
+ * Set Rails configuration defaults based on `load_defaults` version
28
+ * Add check for (more) unsafe method reflection
29
+ * Suggest using `--force` if no Rails application is detected
30
+ * Add Sonarqube report format (Adam England)
31
+ * Add check for potential HTTP verb confusion
32
+ * Add `--[no-]skip-vendor` option
33
+ * Scan (almost) all Ruby files in project
34
+
1
35
  # 4.10.1 - 2020-12-24
2
36
 
3
37
  * Declare REXML as a dependency (Ruby 3.0 compatibility)
@@ -6,6 +40,16 @@
6
40
  * Ensure RubyParser is passed file path as a String
7
41
  * Support new Haml 5.2.0 escaping method
8
42
 
43
+ # 5.0.0.pre1 - 2020-11-17
44
+
45
+ * Add check for (more) unsafe method reflection
46
+ * Suggest using `--force` if no Rails application is detected
47
+ * Add Sonarqube report format (Adam England)
48
+ * Add check for potential HTTP verb confusion
49
+ * Add `--[no-]skip-vendor` option
50
+ * Scan (almost) all Ruby files in project
51
+ * Add support for Haml 5.2.0
52
+
9
53
  # 4.10.0 - 2020-09-28
10
54
 
11
55
  * Add SARIF report format (Steve Winton)
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
 
@@ -159,7 +159,16 @@ The `-w` switch takes a number from 1 to 3, with 1 being low (all warnings) and
159
159
 
160
160
  # Configuration files
161
161
 
162
- Brakeman options can stored and read from YAML files. To simplify the process of writing a configuration file, the `-C` option will output the currently set options.
162
+ Brakeman options can be stored and read from YAML files.
163
+
164
+ To simplify the process of writing a configuration file, the `-C` option will output the currently set options:
165
+
166
+ ```sh
167
+ $ brakeman -C --skip-files plugins/
168
+ ---
169
+ :skip_files:
170
+ - plugins/
171
+ ```
163
172
 
164
173
  Options passed in on the commandline have priority over configuration files.
165
174
 
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)
@@ -156,10 +157,17 @@ module Brakeman
156
157
  end
157
158
  end
158
159
 
159
- CONFIG_FILES = [
160
- File.expand_path("~/.brakeman/config.yml"),
161
- File.expand_path("/etc/brakeman/config.yml")
162
- ]
160
+ CONFIG_FILES = begin
161
+ [
162
+ File.expand_path("~/.brakeman/config.yml"),
163
+ File.expand_path("/etc/brakeman/config.yml")
164
+ ]
165
+ rescue ArgumentError
166
+ # In case $HOME or $USER aren't defined for use of `~`
167
+ [
168
+ File.expand_path("/etc/brakeman/config.yml")
169
+ ]
170
+ end
163
171
 
164
172
  def self.config_file custom_location, app_path
165
173
  app_config = File.expand_path(File.join(app_path, "config", "brakeman.yml"))
@@ -191,6 +199,7 @@ module Brakeman
191
199
  :report_progress => true,
192
200
  :safe_methods => Set.new,
193
201
  :skip_checks => Set.new,
202
+ :skip_vendor => true,
194
203
  }
195
204
  end
196
205
 
@@ -239,6 +248,8 @@ module Brakeman
239
248
  [:to_junit]
240
249
  when :sarif, :to_sarif
241
250
  [:to_sarif]
251
+ when :sonar, :to_sonar
252
+ [:to_sonar]
242
253
  else
243
254
  [:to_text]
244
255
  end
@@ -270,6 +281,8 @@ module Brakeman
270
281
  :to_junit
271
282
  when /\.sarif$/i
272
283
  :to_sarif
284
+ when /\.sonar$/i
285
+ :to_sonar
273
286
  else
274
287
  :to_text
275
288
  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
@@ -40,7 +40,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
40
40
  @mass_assign_disabled = nil
41
41
  @has_user_input = nil
42
42
  @in_array = false
43
- @safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id]
43
+ @safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id, :uuid]
44
44
  @comparison_ops = Set[:==, :!=, :>, :<, :>=, :<=]
45
45
  end
46
46
 
@@ -151,6 +151,12 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
151
151
  method[-1] == "?"
152
152
  end
153
153
 
154
+ TEMP_FILE_PATH = s(:call, s(:call, s(:const, :Tempfile), :new), :path).freeze
155
+
156
+ def temp_file_path? exp
157
+ exp == TEMP_FILE_PATH
158
+ end
159
+
154
160
  #Report a warning
155
161
  def warn options
156
162
  extra_opts = { :check => self.class.to_s }
@@ -204,6 +204,7 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
204
204
  next if node_type? e, :lit, :str
205
205
  next if SAFE_VALUES.include? e
206
206
  next if shell_escape? e
207
+ next if temp_file_path? e
207
208
 
208
209
  if node_type? e, :if
209
210
  # If we're in a conditional, evaluate the `then` and `else` clauses to
@@ -69,17 +69,15 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
69
69
  if check and original? res
70
70
 
71
71
  model = tracker.models[res[:chain].first]
72
-
73
72
  attr_protected = (model and model.attr_protected)
73
+ first_arg = call.first_arg
74
74
 
75
75
  if attr_protected and tracker.options[:ignore_attr_protected]
76
76
  return
77
+ elsif call? first_arg and (first_arg.method == :slice or first_arg.method == :only)
78
+ return
77
79
  elsif input = include_user_input?(call.arglist)
78
- first_arg = call.first_arg
79
-
80
- if call? first_arg and (first_arg.method == :slice or first_arg.method == :only)
81
- return
82
- elsif not node_type? first_arg, :hash
80
+ if not node_type? first_arg, :hash
83
81
  if attr_protected
84
82
  confidence = :medium
85
83
  else
@@ -90,7 +90,8 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
90
90
  def loofah_vulnerable_cve_2018_8048?
91
91
  loofah_version = tracker.config.gem_version(:loofah)
92
92
 
93
- loofah_version and loofah_version < "2.2.1"
93
+ # 2.2.1 is fix version
94
+ loofah_version and version_between?("0.0.0", "2.2.0", loofah_version)
94
95
  end
95
96
 
96
97
  def warn_sanitizer_cve cve, link, upgrade_version
@@ -576,7 +576,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
576
576
  :sanitize_sql_for_assignment, :sanitize_sql_for_conditions, :sanitize_sql_hash,
577
577
  :sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions,
578
578
  :to_sql, :sanitize, :primary_key, :table_name_prefix, :table_name_suffix,
579
- :where_values_hash, :foreign_key
579
+ :where_values_hash, :foreign_key, :uuid
580
580
  ]
581
581
 
582
582
  def safe_value? exp
@@ -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