brakeman-lib 4.8.1 → 4.10.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 +39 -0
  3. data/README.md +5 -3
  4. data/lib/brakeman.rb +20 -0
  5. data/lib/brakeman/checks/base_check.rb +1 -1
  6. data/lib/brakeman/checks/check_basic_auth.rb +2 -0
  7. data/lib/brakeman/checks/check_csrf_token_forgery_cve.rb +28 -0
  8. data/lib/brakeman/checks/check_deserialize.rb +21 -1
  9. data/lib/brakeman/checks/check_execute.rb +1 -1
  10. data/lib/brakeman/checks/check_json_entity_escape.rb +38 -0
  11. data/lib/brakeman/checks/check_mass_assignment.rb +19 -4
  12. data/lib/brakeman/checks/check_model_attr_accessible.rb +1 -1
  13. data/lib/brakeman/checks/check_model_attributes.rb +1 -1
  14. data/lib/brakeman/checks/check_page_caching_cve.rb +37 -0
  15. data/lib/brakeman/checks/check_permit_attributes.rb +1 -1
  16. data/lib/brakeman/checks/check_regex_dos.rb +1 -1
  17. data/lib/brakeman/checks/check_skip_before_filter.rb +4 -4
  18. data/lib/brakeman/checks/check_sql.rb +1 -1
  19. data/lib/brakeman/checks/check_template_injection.rb +32 -0
  20. data/lib/brakeman/commandline.rb +25 -1
  21. data/lib/brakeman/file_parser.rb +5 -0
  22. data/lib/brakeman/options.rb +21 -1
  23. data/lib/brakeman/processors/alias_processor.rb +4 -5
  24. data/lib/brakeman/processors/controller_processor.rb +1 -1
  25. data/lib/brakeman/processors/haml_template_processor.rb +8 -1
  26. data/lib/brakeman/processors/lib/call_conversion_helper.rb +1 -1
  27. data/lib/brakeman/processors/lib/find_all_calls.rb +27 -12
  28. data/lib/brakeman/processors/output_processor.rb +1 -1
  29. data/lib/brakeman/processors/template_alias_processor.rb +5 -0
  30. data/lib/brakeman/report.rb +7 -0
  31. data/lib/brakeman/report/ignore/config.rb +4 -0
  32. data/lib/brakeman/report/report_sarif.rb +114 -0
  33. data/lib/brakeman/report/report_text.rb +37 -16
  34. data/lib/brakeman/scanner.rb +4 -1
  35. data/lib/brakeman/tracker.rb +3 -1
  36. data/lib/brakeman/tracker/config.rb +6 -4
  37. data/lib/brakeman/tracker/constants.rb +8 -7
  38. data/lib/brakeman/tracker/controller.rb +1 -1
  39. data/lib/brakeman/util.rb +18 -2
  40. data/lib/brakeman/version.rb +1 -1
  41. data/lib/brakeman/warning_codes.rb +6 -0
  42. data/lib/ruby_parser/bm_sexp.rb +9 -9
  43. metadata +38 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2e421e421971f6309de15b50d5305a37acf839e4023cd5b976cfb644f62b635
4
- data.tar.gz: cb219b5f4cac1dd286e88b6048dd675adb062be8d273d960364f830ed3fa9493
3
+ metadata.gz: 9433874563193068795eb4d4a90dd176132b04130cf68a9689753caff3c9df1e
4
+ data.tar.gz: 19e73894774e624624edecd48c28dd53aa4da68e7482afe272fec48398551040
5
5
  SHA512:
6
- metadata.gz: b7f76e5da87ef345f47de2a8c489e94f07ba5892a5ab796d9cc5ad147036d599d273d1b339e126ed4f6288efd1b6bfa3a77201787afbcbf83b0279acb6a57459
7
- data.tar.gz: 1544f86037df9fd49e3674c7bd39b4eec1f1e41378078a6077647506f7ba257db4aaa48191df5b8c11c736bc44ac054fc0e96e6b67d21e155977a2f8b7ce44f0
6
+ metadata.gz: fbbd606baa82361d62752a44a0c3a095083d8e3b89c213cad7e8bdc97341a7fd09c3973c80c50d58349dc5bc9e98fe2ca390710d2419636a81f40d6dd75add6f
7
+ data.tar.gz: 89331cbf5168e088bdac777e65b3ee4cbe3244792f64f3c13db084e00db0fc3ebf792801d7c1f7fcc2b3c929cafd83e27381ecd03550d69b761cb94cf228856b
data/CHANGES.md CHANGED
@@ -1,3 +1,42 @@
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
8
+
9
+ # 4.10.0 - 2020-09-28
10
+
11
+ * Add SARIF report format (Steve Winton)
12
+
13
+ # 4.9.1 - 2020-09-04
14
+
15
+ * Check `chomp`ed strings for SQL injection
16
+ * Use version from `active_record` for non-Rails apps (Ulysse Buonomo)
17
+ * Always set line number for joined arrays
18
+ * Avoid warning about missing `attr_accessible` if `protected_attributes` gem is used
19
+
20
+ # 4.9.0 - 2020-08-04
21
+
22
+ * Add check for CVE-2020-8166 (Jamie Finnigan)
23
+ * Avoid warning when `safe_yaml` is used via `YAML.load(..., safe: true)`
24
+ * Add check for user input in `ERB.new` (Matt Hickman)
25
+ * Add `--ensure-ignore-notes` (Eli Block)
26
+ * Remove whitelist/blacklist language, add clarifications
27
+ * Do not warn about mass assignment with `params.permit!.slice`
28
+ * Add "full call" information to call index results
29
+ * Ignore `params.permit!` in path helpers
30
+ * Treat `Dir.glob` as safe source of values in guards
31
+ * Always scan `environment.rb`
32
+
33
+ # 4.8.2 - 2020-05-12
34
+
35
+ * Add check for CVE-2020-8159
36
+ * Fix `authenticate_or_request_with_http_basic` check for passed blocks (Hugo Corbucci)
37
+ * Add `--text-fields` option
38
+ * Add check for escaping HTML entities in JSON configuration
39
+
1
40
  # 4.8.1 - 2020-04-06
2
41
 
3
42
  * Check SQL query strings using `String#strip` or `String.squish`
data/README.md CHANGED
@@ -16,9 +16,11 @@ Using RubyGems:
16
16
 
17
17
  Using Bundler:
18
18
 
19
- group :development do
20
- gem 'brakeman'
21
- end
19
+ ```ruby
20
+ group :development do
21
+ gem 'brakeman'
22
+ end
23
+ ```
22
24
 
23
25
  Using Docker:
24
26
 
@@ -20,6 +20,10 @@ module Brakeman
20
20
  #option is set
21
21
  Errors_Found_Exit_Code = 7
22
22
 
23
+ #Exit code returned when an ignored warning has no note and
24
+ #--ensure-ignore-notes is set
25
+ Empty_Ignore_Note_Exit_Code = 8
26
+
23
27
  @debug = false
24
28
  @quiet = false
25
29
  @loaded_dependencies = []
@@ -233,6 +237,8 @@ module Brakeman
233
237
  [:to_table]
234
238
  when :junit, :to_junit
235
239
  [:to_junit]
240
+ when :sarif, :to_sarif
241
+ [:to_sarif]
236
242
  else
237
243
  [:to_text]
238
244
  end
@@ -262,6 +268,8 @@ module Brakeman
262
268
  :to_table
263
269
  when /\.junit$/i
264
270
  :to_junit
271
+ when /\.sarif$/i
272
+ :to_sarif
265
273
  else
266
274
  :to_text
267
275
  end
@@ -498,6 +506,18 @@ module Brakeman
498
506
  end
499
507
  end
500
508
 
509
+ # Returns an array of alert fingerprints for any ignored warnings without
510
+ # notes found in the specified ignore file (if it exists).
511
+ def self.ignore_file_entries_with_empty_notes file
512
+ return [] unless file
513
+
514
+ require 'brakeman/report/ignore/config'
515
+
516
+ config = IgnoreConfig.new(file, nil)
517
+ config.read_from_file
518
+ config.already_ignored_entries_with_empty_notes.map { |i| i[:fingerprint] }
519
+ end
520
+
501
521
  def self.filter_warnings tracker, options
502
522
  require 'brakeman/report/ignore/config'
503
523
 
@@ -467,7 +467,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
467
467
  end
468
468
 
469
469
  def gemfile_or_environment gem_name = :rails
470
- if gem_name and info = tracker.config.get_gem(gem_name)
470
+ if gem_name and info = tracker.config.get_gem(gem_name.to_sym)
471
471
  info
472
472
  elsif @app_tree.exists?("Gemfile")
473
473
  @app_tree.file_path "Gemfile"
@@ -57,6 +57,8 @@ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
57
57
 
58
58
  # Check if the block of a result contains a comparison of password to string
59
59
  def include_password_literal? result
60
+ return false if result[:block_args].nil?
61
+
60
62
  @password_var = result[:block_args].last
61
63
  @include_password = false
62
64
  process result[:block]
@@ -0,0 +1,28 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckCSRFTokenForgeryCVE < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for versions with CSRF token forgery vulnerability (CVE-2020-8166)"
7
+
8
+ def run_check
9
+ fix_version = case
10
+ when version_between?('0.0.0', '5.2.4.2')
11
+ '5.2.4.3'
12
+ when version_between?('6.0.0', '6.0.3')
13
+ '6.0.3.1'
14
+ else
15
+ nil
16
+ end
17
+
18
+ if fix_version
19
+ warn :warning_type => "Cross-Site Request Forgery",
20
+ :warning_code => :CVE_2020_8166,
21
+ :message => msg(msg_version(rails_version), " has a vulnerability that may allow CSRF token forgery. Upgrade to ", msg_version(fix_version), " or patch"),
22
+ :confidence => :medium,
23
+ :gem_info => gemfile_or_environment,
24
+ :link => "https://groups.google.com/g/rubyonrails-security/c/NOjKiGeXUgw"
25
+ end
26
+ end
27
+ end
28
+
@@ -13,7 +13,23 @@ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
13
13
  end
14
14
 
15
15
  def check_yaml
16
- check_methods :YAML, :load, :load_documents, :load_stream, :parse_documents, :parse_stream
16
+ check_methods :YAML, :load_documents, :load_stream, :parse_documents, :parse_stream
17
+
18
+ # Check for safe_yaml gem use with YAML.load(..., safe: true)
19
+ if uses_safe_yaml?
20
+ tracker.find_call(target: :YAML, method: :load).each do |result|
21
+ call = result[:call]
22
+ options = call.second_arg
23
+
24
+ if hash? options and true? hash_access(options, :safe)
25
+ next
26
+ else
27
+ check_deserialize result, :YAML
28
+ end
29
+ end
30
+ else
31
+ check_methods :YAML, :load
32
+ end
17
33
  end
18
34
 
19
35
  def check_csv
@@ -102,4 +118,8 @@ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
102
118
 
103
119
  false
104
120
  end
121
+
122
+ def uses_safe_yaml?
123
+ tracker.config.has_gem? :safe_yaml
124
+ end
105
125
  end
@@ -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.values[1..-1])
211
+ if res = dangerous?(e.sexp_body.sexp_body)
212
212
  return res
213
213
  end
214
214
  elsif node_type? e, :or, :evstr, :dstr
@@ -0,0 +1,38 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckJSONEntityEscape < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check if HTML escaping is disabled for JSON output"
7
+
8
+ def run_check
9
+ check_config_setting
10
+ check_manual_disable
11
+ end
12
+
13
+ def check_config_setting
14
+ if false? tracker.config.rails.dig(:active_support, :escape_html_entities_in_json)
15
+ warn :warning_type => "Cross-Site Scripting",
16
+ :warning_code => :json_html_escape_config,
17
+ :message => msg("HTML entities in JSON are not escaped by default"),
18
+ :confidence => :medium,
19
+ :file => "config/environments/production.rb",
20
+ :line => 1
21
+ end
22
+ end
23
+
24
+ def check_manual_disable
25
+ tracker.find_call(targets: [:ActiveSupport, :'ActiveSupport::JSON::Encoding'], method: :escape_html_entities_in_json=).each do |result|
26
+ setting = result[:call].first_arg
27
+
28
+ if false? setting
29
+ warn :result => result,
30
+ :warning_type => "Cross-Site Scripting",
31
+ :warning_code => :json_html_escape_module,
32
+ :message => msg("HTML entities in JSON are not escaped by default"),
33
+ :confidence => :medium,
34
+ :file => "config/environments/production.rb"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -160,12 +160,27 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
160
160
  # Look for and warn about uses of Parameters#permit! for mass assignment
161
161
  def check_permit!
162
162
  tracker.find_call(:method => :permit!, :nested => true).each do |result|
163
- if params? result[:call].target and not result[:chain].include? :slice
164
- warn_on_permit! result
163
+ if params? result[:call].target
164
+ unless inside_safe_method? result or calls_slice? result
165
+ warn_on_permit! result
166
+ end
165
167
  end
166
168
  end
167
169
  end
168
170
 
171
+ # Ignore blah_some_path(params.permit!)
172
+ def inside_safe_method? result
173
+ parent_call = result.dig(:parent, :call)
174
+
175
+ call? parent_call and
176
+ parent_call.method.match(/_path$/)
177
+ end
178
+
179
+ def calls_slice? result
180
+ result[:chain].include? :slice or
181
+ (result[:full_call] and result[:full_call][:chain].include? :slice)
182
+ end
183
+
169
184
  # Look for actual use of params in mass assignment to avoid
170
185
  # warning about uses of Parameters#permit! without any mass assignment
171
186
  # or when mass assignment is restricted by model instead.
@@ -191,7 +206,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
191
206
  warn :result => result,
192
207
  :warning_type => "Mass Assignment",
193
208
  :warning_code => :mass_assign_permit!,
194
- :message => "Parameters should be whitelisted for mass assignment",
209
+ :message => msg('Specify exact keys allowed for mass assignment instead of using ', msg_code('permit!'), ' which allows any keys'),
195
210
  :confidence => confidence
196
211
  end
197
212
 
@@ -203,7 +218,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
203
218
  warn :result => result,
204
219
  :warning_type => "Mass Assignment",
205
220
  :warning_code => :mass_assign_permit_all,
206
- :message => "Parameters should be whitelisted for mass assignment",
221
+ :message => msg('Mass assignment is globally enabled. Disable and specify exact keys using ', msg_code('params.permit'), ' instead'),
207
222
  :confidence => :high
208
223
  end
209
224
  end
@@ -8,7 +8,7 @@ require 'brakeman/checks/base_check'
8
8
  class Brakeman::CheckModelAttrAccessible < Brakeman::BaseCheck
9
9
  Brakeman::Checks.add self
10
10
 
11
- @description = "Reports models which have dangerous attributes defined under the attr_accessible whitelist."
11
+ @description = "Reports models which have dangerous attributes defined via attr_accessible"
12
12
 
13
13
  SUSP_ATTRS = [
14
14
  [:admin, :high], # Very dangerous unless some Rails authorization used
@@ -8,7 +8,7 @@ class Brakeman::CheckModelAttributes < Brakeman::BaseCheck
8
8
  @description = "Reports models which do not use attr_restricted and warns on models that use attr_protected"
9
9
 
10
10
  def run_check
11
- return if mass_assign_disabled?
11
+ return if mass_assign_disabled? or tracker.config.has_gem?(:protected_attributes)
12
12
 
13
13
  #Roll warnings into one warning for all models
14
14
  if tracker.options[:collapse_mass_assignment]
@@ -0,0 +1,37 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckPageCachingCVE < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check for page caching vulnerability (CVE-2020-8159)"
7
+
8
+ def run_check
9
+ gem_name = 'actionpack-page_caching'
10
+ gem_version = tracker.config.gem_version(gem_name.to_sym)
11
+ upgrade_version = '1.2.2'
12
+ cve = 'CVE-2020-8159'
13
+
14
+ return unless gem_version and version_between?('0.0.0', '1.2.1', gem_version)
15
+
16
+ message = msg("Directory traversal vulnerability in ", msg_version(gem_version, gem_name), " ", msg_cve(cve), ". Upgrade to ", msg_version(upgrade_version, gem_name))
17
+
18
+ if uses_caches_page?
19
+ confidence = :high
20
+ else
21
+ confidence = :weak
22
+ end
23
+
24
+ warn :warning_type => 'Directory Traversal',
25
+ :warning_code => :CVE_2020_8159,
26
+ :message => message,
27
+ :confidence => confidence,
28
+ :link_path => 'https://groups.google.com/d/msg/rubyonrails-security/CFRVkEytdP8/c5gmICECAgAJ',
29
+ :gem_info => gemfile_or_environment(gem_name)
30
+ end
31
+
32
+ def uses_caches_page?
33
+ tracker.controllers.any? do |name, controller|
34
+ controller.options.has_key? :caches_page
35
+ end
36
+ end
37
+ end
@@ -3,7 +3,7 @@ require 'brakeman/checks/base_check'
3
3
  class Brakeman::CheckPermitAttributes < Brakeman::BaseCheck
4
4
  Brakeman::Checks.add self
5
5
 
6
- @description = "Warn on potentially dangerous attributes whitelisted via permit"
6
+ @description = "Warn on potentially dangerous attributes allowed via permit"
7
7
 
8
8
  SUSPICIOUS_KEYS = {
9
9
  admin: :high,
@@ -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[1..-1]
32
+ components = call.sexp_body
33
33
 
34
34
  components.any? do |component|
35
35
  next unless sexp? component
@@ -4,8 +4,8 @@ require 'brakeman/checks/base_check'
4
4
  #
5
5
  # skip_before_filter :verify_authenticity_token, :except => [...]
6
6
  #
7
- #which is essentially a blacklist approach (no actions are checked EXCEPT the
8
- #ones listed) versus a whitelist approach (ONLY the actions listed will skip
7
+ #which is essentially a skip-by-default approach (no actions are checked EXCEPT the
8
+ #ones listed) versus a enforce-by-default approach (ONLY the actions listed will skip
9
9
  #the check)
10
10
  class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
11
11
  Brakeman::Checks.add self
@@ -26,7 +26,7 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
26
26
  warn :class => controller.name, #ugh this should be a controller warning, too
27
27
  :warning_type => "Cross-Site Request Forgery",
28
28
  :warning_code => :csrf_blacklist,
29
- :message => msg("Use whitelist (", msg_code(":only => [..]"), ") when skipping CSRF check"),
29
+ :message => msg("List specific actions (", msg_code(":only => [..]"), ") when skipping CSRF check"),
30
30
  :code => filter,
31
31
  :confidence => :medium,
32
32
  :file => controller.file
@@ -35,7 +35,7 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
35
35
  warn :controller => controller.name,
36
36
  :warning_code => :auth_blacklist,
37
37
  :warning_type => "Authentication",
38
- :message => msg("Use whitelist (", msg_code(":only => [..]"), ") when skipping authentication"),
38
+ :message => msg("List specific actions (", msg_code(":only => [..]"), ") when skipping authentication"),
39
39
  :code => filter,
40
40
  :confidence => :medium,
41
41
  :link_path => "authentication_whitelist",
@@ -393,7 +393,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
393
393
  nil
394
394
  end
395
395
 
396
- TO_STRING_METHODS = [:to_s, :squish, :strip, :strip_heredoc]
396
+ TO_STRING_METHODS = [:chomp, :to_s, :squish, :strip, :strip_heredoc]
397
397
 
398
398
  #Returns value if interpolated value is not something safe
399
399
  def unsafe_string_interp? exp
@@ -0,0 +1,32 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckTemplateInjection < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Searches for evaluation of user input through template injection"
7
+
8
+ #Process calls
9
+ def run_check
10
+ Brakeman.debug "Finding ERB.new calls"
11
+ erb_calls = tracker.find_call :target => :ERB, :method => :new, :nested => true
12
+
13
+ Brakeman.debug "Processing ERB.new calls"
14
+ erb_calls.each do |call|
15
+ process_result call
16
+ end
17
+ end
18
+
19
+ #Warns if eval includes user input
20
+ def process_result result
21
+ return unless original? result
22
+
23
+ if input = include_user_input?(result[:call].arglist)
24
+ warn :result => result,
25
+ :warning_type => "Template Injection",
26
+ :warning_code => :erb_template_injection,
27
+ :message => msg(msg_input(input), " used directly in ", msg_code("ERB"), " template, which might enable remote code execution"),
28
+ :user_input => input,
29
+ :confidence => :high
30
+ end
31
+ end
32
+ end