brakeman-lib 4.8.1 → 4.10.1

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 (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