brakeman-lib 4.7.1 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +47 -0
  3. data/README.md +13 -5
  4. data/lib/brakeman.rb +20 -0
  5. data/lib/brakeman/checks/base_check.rb +13 -10
  6. data/lib/brakeman/checks/check_basic_auth.rb +2 -0
  7. data/lib/brakeman/checks/check_content_tag.rb +1 -2
  8. data/lib/brakeman/checks/check_csrf_token_forgery_cve.rb +28 -0
  9. data/lib/brakeman/checks/check_deserialize.rb +21 -1
  10. data/lib/brakeman/checks/check_execute.rb +40 -5
  11. data/lib/brakeman/checks/check_json_entity_escape.rb +38 -0
  12. data/lib/brakeman/checks/check_link_to.rb +1 -1
  13. data/lib/brakeman/checks/check_link_to_href.rb +1 -3
  14. data/lib/brakeman/checks/check_mass_assignment.rb +34 -4
  15. data/lib/brakeman/checks/check_model_attr_accessible.rb +1 -1
  16. data/lib/brakeman/checks/check_page_caching_cve.rb +37 -0
  17. data/lib/brakeman/checks/check_permit_attributes.rb +1 -1
  18. data/lib/brakeman/checks/check_skip_before_filter.rb +4 -4
  19. data/lib/brakeman/checks/check_sql.rb +24 -33
  20. data/lib/brakeman/checks/check_template_injection.rb +32 -0
  21. data/lib/brakeman/commandline.rb +25 -1
  22. data/lib/brakeman/differ.rb +0 -5
  23. data/lib/brakeman/options.rb +21 -1
  24. data/lib/brakeman/processor.rb +1 -1
  25. data/lib/brakeman/processors/alias_processor.rb +2 -3
  26. data/lib/brakeman/processors/lib/find_all_calls.rb +30 -14
  27. data/lib/brakeman/processors/lib/render_helper.rb +3 -1
  28. data/lib/brakeman/report.rb +4 -1
  29. data/lib/brakeman/report/ignore/config.rb +10 -2
  30. data/lib/brakeman/report/report_junit.rb +104 -0
  31. data/lib/brakeman/report/report_markdown.rb +0 -1
  32. data/lib/brakeman/report/report_text.rb +37 -16
  33. data/lib/brakeman/scanner.rb +4 -1
  34. data/lib/brakeman/tracker.rb +3 -1
  35. data/lib/brakeman/tracker/config.rb +4 -3
  36. data/lib/brakeman/tracker/constants.rb +8 -7
  37. data/lib/brakeman/util.rb +21 -3
  38. data/lib/brakeman/version.rb +1 -1
  39. data/lib/brakeman/warning_codes.rb +7 -0
  40. metadata +33 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a0bb1fb9eebcf11e5493213b5cd4a40b6c5359952f754bd7aab4fe727fa3950
4
- data.tar.gz: '07648dfb71e125045d345bd154a56547f0b8168f6ecd29f6b47442e7923fbd3a'
3
+ metadata.gz: 43b7d1a166362e6f078be06194adde0e9acc6f6ee5bbe6b54212a4dddb0335ad
4
+ data.tar.gz: fd3fcf8965f5125991e51dce67d18415ca3f5db6f431a4076c16acbf1a3bd906
5
5
  SHA512:
6
- metadata.gz: dd82f20198e48a73d7c7ddac934fcc9b192400e0b333f665033b7375c63ed5ffd5baf130ce31afd5aab48f7661cc93694a87b81ba87e29ad5a879c897052e1df
7
- data.tar.gz: 5975dcd2ef58e1007d45b2206fcdf8232962b22739ccde18c9e72f3aafe72196efb11b4627051ca43bad2aaf7881dc0a7e35b25ef32436b33fea2853bb65b65b
6
+ metadata.gz: a929f04cb48c9ccb434cfa3ee47791d263a1fc3d30acdea4459c25c8c7bcab7d72887369f893de1eed5418a059dd07e55a98157bd2729967f1b9e4c72a4b94f5
7
+ data.tar.gz: ead62901264f2d1230512894820ad8160ce6115a19ac32dd2ea3474ebb9b9a723e2090077ea6780b38c0b9d3f3b59e4c93ad55d8dbf613f9426475796a167ab3
data/CHANGES.md CHANGED
@@ -1,3 +1,50 @@
1
+ # 4.9.0 - 2020-08-04
2
+
3
+ * Add check for CVE-2020-8166 (Jamie Finnigan)
4
+ * Avoid warning when `safe_yaml` is used via `YAML.load(..., safe: true)`
5
+ * Add check for user input in `ERB.new` (Matt Hickman)
6
+ * Add `--ensure-ignore-notes` (Eli Block)
7
+ * Remove whitelist/blacklist language, add clarifications
8
+ * Do not warn about mass assignment with `params.permit!.slice`
9
+ * Add "full call" information to call index results
10
+ * Ignore `params.permit!` in path helpers
11
+ * Treat `Dir.glob` as safe source of values in guards
12
+ * Always scan `environment.rb`
13
+
14
+ # 4.8.2 - 2020-05-12
15
+
16
+ * Add check for CVE-2020-8159
17
+ * Fix `authenticate_or_request_with_http_basic` check for passed blocks (Hugo Corbucci)
18
+ * Add `--text-fields` option
19
+ * Add check for escaping HTML entities in JSON configuration
20
+
21
+ # 4.8.1 - 2020-04-06
22
+
23
+ * Check SQL query strings using `String#strip` or `String.squish`
24
+ * Handle non-symbol keys in locals hash for render()
25
+ * Warn about global(!) mass assignment
26
+ * Index calls in render arguments
27
+
28
+ # 4.8.0 - 2020-02-18
29
+
30
+ * Add JUnit-XML report format (Naoki Kimura)
31
+ * Sort ignore files by fingerprint and line (Ngan Pham)
32
+ * Freeze call index results
33
+ * Fix output test when using newer Minitest
34
+ * Properly render confidence in Markdown report
35
+ * Report old warnings as fixed if zero warnings reported
36
+ * Catch dangerous concatenation in `CheckExecute` (Jacob Evelyn)
37
+ * Show user-friendly message when ignore config file has invalid JSON (D. Hicks)
38
+ * Initialize Rails version with `nil` (Carsten Wirth)
39
+
40
+ # 4.7.2 - 2019-11-25
41
+
42
+ * Remove version guard for `named_scope` vs. `scope`
43
+ * Find SQL injection in `String#strip_heredoc` target
44
+ * Handle more `permit!` cases
45
+ * Ensure file name is set when processing model
46
+ * Add `request.params` as query parameters
47
+
1
48
  # 4.7.1 - 2019-10-29
2
49
 
3
50
  * Check string length against limit before joining
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
 
@@ -62,7 +64,7 @@ Outside of Rails root (note that the output file is relative to path/to/rails/ap
62
64
 
63
65
  # Compatibility
64
66
 
65
- Brakeman should work with any version of Rails from 2.3.x to 5.x.
67
+ Brakeman should work with any version of Rails from 2.3.x to 6.x.
66
68
 
67
69
  Brakeman can analyze code written with Ruby 1.8 syntax and newer, but requires at least Ruby 2.3.0 to run.
68
70
 
@@ -74,12 +76,16 @@ To specify an output file for the results:
74
76
 
75
77
  brakeman -o output_file
76
78
 
77
- The output format is determined by the file extension or by using the `-f` option. Current options are: `text`, `html`, `tabs`, `json`, `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`, and `codeclimate`.
78
80
 
79
81
  Multiple output files can be specified:
80
82
 
81
83
  brakeman -o output.html -o output.json
82
84
 
85
+ To output to both a file and to the console, with color:
86
+
87
+ brakeman --color -o /dev/stdout -o output.json
88
+
83
89
  To suppress informational warnings and just output the report:
84
90
 
85
91
  brakeman -q
@@ -167,6 +173,8 @@ There is a [plugin available](http://brakemanscanner.org/docs/jenkins/) for Jenk
167
173
 
168
174
  For even more continuous testing, try the [Guard plugin](https://github.com/guard/guard-brakeman).
169
175
 
176
+ There are a couple [Github Actions](https://github.com/marketplace?type=actions&query=brakeman) available.
177
+
170
178
  # Building
171
179
 
172
180
  git clone git://github.com/presidentbeef/brakeman.git
@@ -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 = []
@@ -231,6 +235,8 @@ module Brakeman
231
235
  [:to_text]
232
236
  when :table, :to_table
233
237
  [:to_table]
238
+ when :junit, :to_junit
239
+ [:to_junit]
234
240
  else
235
241
  [:to_text]
236
242
  end
@@ -258,6 +264,8 @@ module Brakeman
258
264
  :to_text
259
265
  when /\.table$/i
260
266
  :to_table
267
+ when /\.junit$/i
268
+ :to_junit
261
269
  else
262
270
  :to_text
263
271
  end
@@ -494,6 +502,18 @@ module Brakeman
494
502
  end
495
503
  end
496
504
 
505
+ # Returns an array of alert fingerprints for any ignored warnings without
506
+ # notes found in the specified ignore file (if it exists).
507
+ def self.ignore_file_entries_with_empty_notes file
508
+ return [] unless file
509
+
510
+ require 'brakeman/report/ignore/config'
511
+
512
+ config = IgnoreConfig.new(file, nil)
513
+ config.read_from_file
514
+ config.already_ignored_entries_with_empty_notes.map { |i| i[:fingerprint] }
515
+ end
516
+
497
517
  def self.filter_warnings tracker, options
498
518
  require 'brakeman/report/ignore/config'
499
519
 
@@ -280,15 +280,6 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
280
280
  return location, line
281
281
  end
282
282
 
283
- #Checks if an expression contains string interpolation.
284
- #
285
- #Returns Match with :interp type if found.
286
- def include_interp? exp
287
- @string_interp = false
288
- process exp
289
- @string_interp
290
- end
291
-
292
283
  #Checks if _exp_ includes user input in the form of cookies, parameters,
293
284
  #request environment, or model attributes.
294
285
  #
@@ -476,7 +467,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
476
467
  end
477
468
 
478
469
  def gemfile_or_environment gem_name = :rails
479
- 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)
480
471
  info
481
472
  elsif @app_tree.exists?("Gemfile")
482
473
  @app_tree.file_path "Gemfile"
@@ -504,4 +495,16 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
504
495
 
505
496
  @active_record_models
506
497
  end
498
+
499
+ STRING_METHODS = Set[:<<, :+, :concat, :prepend]
500
+ private_constant :STRING_METHODS
501
+
502
+ def string_building? exp
503
+ return false unless call? exp and STRING_METHODS.include? exp.method
504
+
505
+ node_type? exp.target, :str, :dstr or
506
+ node_type? exp.first_arg, :str, :dstr or
507
+ string_building? exp.target or
508
+ string_building? exp.first_arg
509
+ end
507
510
  end
@@ -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]
@@ -55,8 +55,7 @@ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
55
55
 
56
56
  @current_file = result[:location][:file]
57
57
 
58
- call = result[:call] = result[:call].dup
59
-
58
+ call = result[:call]
60
59
  args = call.arglist
61
60
 
62
61
  tag_name = args[1]
@@ -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
@@ -56,8 +56,20 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
56
56
 
57
57
  case call.method
58
58
  when :popen
59
- unless array? first_arg
60
- failure = include_user_input?(args) || dangerous_interp?(args)
59
+ # Normally, if we're in a `popen` call, we only are worried about shell
60
+ # injection when the argument is not an array, because array elements
61
+ # are always escaped by Ruby. However, an exception is when the array
62
+ # contains two values are something like "bash -c" because then the third
63
+ # element is effectively the command being run and might be a malicious
64
+ # executable if it comes (partially or fully) from user input.
65
+ if !array?(first_arg)
66
+ failure = include_user_input?(first_arg) ||
67
+ dangerous_interp?(first_arg) ||
68
+ dangerous_string_building?(first_arg)
69
+ elsif dash_c_shell_command?(first_arg[1], first_arg[2])
70
+ failure = include_user_input?(first_arg[3]) ||
71
+ dangerous_interp?(first_arg[3]) ||
72
+ dangerous_string_building?(first_arg[3])
61
73
  end
62
74
  when :system, :exec
63
75
  # Normally, if we're in a `system` or `exec` call, we only are worried
@@ -67,12 +79,18 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
67
79
  # the third argument is effectively the command being run and might be
68
80
  # a malicious executable if it comes (partially or fully) from user input.
69
81
  if dash_c_shell_command?(first_arg, call.second_arg)
70
- failure = include_user_input?(args[3]) || dangerous_interp?(args[3])
82
+ failure = include_user_input?(args[3]) ||
83
+ dangerous_interp?(args[3]) ||
84
+ dangerous_string_building?(args[3])
71
85
  else
72
- failure = include_user_input?(first_arg) || dangerous_interp?(first_arg)
86
+ failure = include_user_input?(first_arg) ||
87
+ dangerous_interp?(first_arg) ||
88
+ dangerous_string_building?(first_arg)
73
89
  end
74
90
  else
75
- failure = include_user_input?(args) || dangerous_interp?(args)
91
+ failure = include_user_input?(args) ||
92
+ dangerous_interp?(args) ||
93
+ dangerous_string_building?(args)
76
94
  end
77
95
 
78
96
  if failure and original? result
@@ -219,6 +237,23 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
219
237
  false
220
238
  end
221
239
 
240
+ #Checks if an expression contains string interpolation.
241
+ #
242
+ #Returns Match with :interp type if found.
243
+ def include_interp? exp
244
+ @string_interp = false
245
+ process exp
246
+ @string_interp
247
+ end
248
+
249
+ def dangerous_string_building? exp
250
+ if string_building?(exp) && res = dangerous?(exp)
251
+ return Match.new(:interp, res)
252
+ end
253
+
254
+ false
255
+ end
256
+
222
257
  def shell_escape? exp
223
258
  return false unless call? exp
224
259
 
@@ -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
@@ -34,7 +34,7 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
34
34
 
35
35
  #Have to make a copy of this, otherwise it will be changed to
36
36
  #an ignored method call by the code above.
37
- call = result[:call] = result[:call].dup
37
+ call = result[:call]
38
38
 
39
39
  first_arg = call.first_arg
40
40
  second_arg = call.second_arg
@@ -30,9 +30,7 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
30
30
  end
31
31
 
32
32
  def process_result result
33
- #Have to make a copy of this, otherwise it will be changed to
34
- #an ignored method call by the code above.
35
- call = result[:call] = result[:call].dup
33
+ call = result[:call]
36
34
  @matched = false
37
35
 
38
36
  url_arg = if result[:block]
@@ -17,6 +17,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
17
17
  def run_check
18
18
  check_mass_assignment
19
19
  check_permit!
20
+ check_permit_all_parameters
20
21
  end
21
22
 
22
23
  def find_mass_assign_calls
@@ -158,13 +159,28 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
158
159
 
159
160
  # Look for and warn about uses of Parameters#permit! for mass assignment
160
161
  def check_permit!
161
- tracker.find_call(:method => :permit!).each do |result|
162
- if params? result[:call].target and not result[:chain].include? :slice
163
- warn_on_permit! result
162
+ tracker.find_call(:method => :permit!, :nested => true).each do |result|
163
+ if params? result[:call].target
164
+ unless inside_safe_method? result or calls_slice? result
165
+ warn_on_permit! result
166
+ end
164
167
  end
165
168
  end
166
169
  end
167
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
+
168
184
  # Look for actual use of params in mass assignment to avoid
169
185
  # warning about uses of Parameters#permit! without any mass assignment
170
186
  # or when mass assignment is restricted by model instead.
@@ -190,7 +206,21 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
190
206
  warn :result => result,
191
207
  :warning_type => "Mass Assignment",
192
208
  :warning_code => :mass_assign_permit!,
193
- :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'),
194
210
  :confidence => confidence
195
211
  end
212
+
213
+ def check_permit_all_parameters
214
+ tracker.find_call(target: :"ActionController::Parameters", method: :permit_all_parameters=).each do |result|
215
+ call = result[:call]
216
+
217
+ if true? call.first_arg
218
+ warn :result => result,
219
+ :warning_type => "Mass Assignment",
220
+ :warning_code => :mass_assign_permit_all,
221
+ :message => msg('Mass assignment is globally enabled. Disable and specify exact keys using ', msg_code('params.permit'), ' instead'),
222
+ :confidence => :high
223
+ end
224
+ end
225
+ end
196
226
  end