brakeman-min 4.8.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +39 -1
  3. data/README.md +12 -4
  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_json_entity_escape.rb +38 -0
  10. data/lib/brakeman/checks/check_mass_assignment.rb +33 -3
  11. data/lib/brakeman/checks/check_model_attr_accessible.rb +1 -1
  12. data/lib/brakeman/checks/check_model_attributes.rb +1 -1
  13. data/lib/brakeman/checks/check_page_caching_cve.rb +37 -0
  14. data/lib/brakeman/checks/check_permit_attributes.rb +1 -1
  15. data/lib/brakeman/checks/check_skip_before_filter.rb +4 -4
  16. data/lib/brakeman/checks/check_sql.rb +1 -1
  17. data/lib/brakeman/checks/check_template_injection.rb +32 -0
  18. data/lib/brakeman/commandline.rb +25 -1
  19. data/lib/brakeman/options.rb +21 -1
  20. data/lib/brakeman/processors/alias_processor.rb +2 -3
  21. data/lib/brakeman/processors/lib/call_conversion_helper.rb +1 -1
  22. data/lib/brakeman/processors/lib/find_all_calls.rb +28 -13
  23. data/lib/brakeman/processors/lib/render_helper.rb +3 -1
  24. data/lib/brakeman/report.rb +7 -0
  25. data/lib/brakeman/report/ignore/config.rb +4 -0
  26. data/lib/brakeman/report/report_sarif.rb +114 -0
  27. data/lib/brakeman/report/report_text.rb +37 -16
  28. data/lib/brakeman/scanner.rb +4 -1
  29. data/lib/brakeman/tracker.rb +3 -1
  30. data/lib/brakeman/tracker/config.rb +6 -4
  31. data/lib/brakeman/tracker/constants.rb +8 -7
  32. data/lib/brakeman/util.rb +16 -0
  33. data/lib/brakeman/version.rb +1 -1
  34. data/lib/brakeman/warning_codes.rb +7 -0
  35. metadata +21 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5df247822922b2eb40924d6fc0f4c5ce34b11a49f06a1d1dd8fce1aeea96dff7
4
- data.tar.gz: 559f77dde2d0539c0e2a3f24b10de1e1c63dee0f51ddfbeaf1b7a02bb1ac333c
3
+ metadata.gz: 7bfea3fcc26490216cb839ad7e23f3db30312439b996719bc36e446d81ee6c21
4
+ data.tar.gz: 0ec67a45a20be3d85c28ebf4250abf4528fc30e661c9bd4bd149ce024b0b8e81
5
5
  SHA512:
6
- metadata.gz: 6b236f461c09d6a3d90f2be348e641028f44f3776fb4372a18a542c5735c5453c9e9a451c959ab31f565afb61dba96fb5837b957f16185dc84516d080a010948
7
- data.tar.gz: 58a8c876b73cf7d02b27aa4e438f0092f0ce8487d3458c311016c500ac1961a55b7b22af303c7c1db67e007a887f841661cdd2a2e005d5db7dd117938329794d
6
+ metadata.gz: a356ae3757074a222d1b8044e228b6d0848942bffbe7bbcf84bd08bd1793767c66e8e71d93bea0053281efeda5aabcca968482d7dde65f7490fe6091e5857e8c
7
+ data.tar.gz: d233b34ddf6c0d85b0f44fb32b02e782750b0dc50bc476606719777f73ecaf7c49639114947942cbac9ef9c1ba671a874584b651fc06162482296279aa118aef
data/CHANGES.md CHANGED
@@ -1,4 +1,42 @@
1
- # Unreleased
1
+ # 4.10.0 - 2020-09-28
2
+
3
+ * Add SARIF report format (Steve Winton)
4
+
5
+ # 4.9.1 - 2020-09-04
6
+
7
+ * Check `chomp`ed strings for SQL injection
8
+ * Use version from `active_record` for non-Rails apps (Ulysse Buonomo)
9
+ * Always set line number for joined arrays
10
+ * Avoid warning about missing `attr_accessible` if `protected_attributes` gem is used
11
+
12
+ # 4.9.0 - 2020-08-04
13
+
14
+ * Add check for CVE-2020-8166 (Jamie Finnigan)
15
+ * Avoid warning when `safe_yaml` is used via `YAML.load(..., safe: true)`
16
+ * Add check for user input in `ERB.new` (Matt Hickman)
17
+ * Add `--ensure-ignore-notes` (Eli Block)
18
+ * Remove whitelist/blacklist language, add clarifications
19
+ * Do not warn about mass assignment with `params.permit!.slice`
20
+ * Add "full call" information to call index results
21
+ * Ignore `params.permit!` in path helpers
22
+ * Treat `Dir.glob` as safe source of values in guards
23
+ * Always scan `environment.rb`
24
+
25
+ # 4.8.2 - 2020-05-12
26
+
27
+ * Add check for CVE-2020-8159
28
+ * Fix `authenticate_or_request_with_http_basic` check for passed blocks (Hugo Corbucci)
29
+ * Add `--text-fields` option
30
+ * Add check for escaping HTML entities in JSON configuration
31
+
32
+ # 4.8.1 - 2020-04-06
33
+
34
+ * Check SQL query strings using `String#strip` or `String.squish`
35
+ * Handle non-symbol keys in locals hash for render()
36
+ * Warn about global(!) mass assignment
37
+ * Index calls in render arguments
38
+
39
+ # 4.8.0 - 2020-02-18
2
40
 
3
41
  * Add JUnit-XML report format (Naoki Kimura)
4
42
  * Sort ignore files by fingerprint and line (Ngan Pham)
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
 
@@ -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 = []
@@ -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
@@ -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
@@ -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
@@ -159,12 +160,27 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
159
160
  # Look for and warn about uses of Parameters#permit! for mass assignment
160
161
  def check_permit!
161
162
  tracker.find_call(:method => :permit!, :nested => true).each do |result|
162
- if params? result[:call].target and not result[:chain].include? :slice
163
- 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
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
@@ -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,
@@ -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, :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
@@ -102,6 +102,13 @@ module Brakeman
102
102
  app_path = "."
103
103
  end
104
104
 
105
+ if options[:ensure_ignore_notes] and options[:previous_results_json]
106
+ warn '[Notice] --ensure-ignore-notes may not be used at the same ' \
107
+ 'time as --compare. Deactivating --ensure-ignore-notes. ' \
108
+ 'Please see `brakeman --help` for valid options'
109
+ options[:ensure_ignore_notes] = false
110
+ end
111
+
105
112
  return options, app_path
106
113
  end
107
114
 
@@ -115,7 +122,20 @@ module Brakeman
115
122
 
116
123
  # Runs a regular report based on the options provided.
117
124
  def regular_report options
118
- tracker = run_brakeman options
125
+ tracker = run_brakeman options
126
+
127
+ ensure_ignore_notes_failed = false
128
+ if tracker.options[:ensure_ignore_notes]
129
+ fingerprints = Brakeman::ignore_file_entries_with_empty_notes tracker.ignored_filter&.file
130
+
131
+ unless fingerprints.empty?
132
+ ensure_ignore_notes_failed = true
133
+ warn '[Error] Notes required for all ignored warnings when ' \
134
+ '--ensure-ignore-notes is set. No notes provided for these ' \
135
+ 'warnings: '
136
+ fingerprints.each { |f| warn f }
137
+ end
138
+ end
119
139
 
120
140
  if tracker.options[:exit_on_warn] and not tracker.filtered_warnings.empty?
121
141
  quit Brakeman::Warnings_Found_Exit_Code
@@ -124,6 +144,10 @@ module Brakeman
124
144
  if tracker.options[:exit_on_error] and tracker.errors.any?
125
145
  quit Brakeman::Errors_Found_Exit_Code
126
146
  end
147
+
148
+ if ensure_ignore_notes_failed
149
+ quit Brakeman::Empty_Ignore_Note_Exit_Code
150
+ end
127
151
  end
128
152
 
129
153
  # Actually run Brakeman.
@@ -67,6 +67,10 @@ module Brakeman::Options
67
67
  options[:ensure_latest] = true
68
68
  end
69
69
 
70
+ opts.on "--ensure-ignore-notes", "Fail when an ignored warnings does not include a note" do
71
+ options[:ensure_ignore_notes] = true
72
+ end
73
+
70
74
  opts.on "-3", "--rails3", "Force Rails 3 mode" do
71
75
  options[:rails3] = true
72
76
  end
@@ -225,7 +229,7 @@ module Brakeman::Options
225
229
 
226
230
  opts.on "-f",
227
231
  "--format TYPE",
228
- [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit],
232
+ [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif],
229
233
  "Specify output formats. Default is text" do |type|
230
234
 
231
235
  type = "s" if type == :text
@@ -301,6 +305,22 @@ module Brakeman::Options
301
305
  options[:github_repo] = repo
302
306
  end
303
307
 
308
+ opts.on "--text-fields field1,field2,etc.", Array, "Specify fields for text report format" do |format|
309
+ valid_options = [:category, :category_id, :check, :code, :confidence, :file, :fingerprint, :line, :link, :message, :render_path]
310
+
311
+ options[:text_fields] = format.map(&:to_sym)
312
+
313
+ if options[:text_fields] == [:all]
314
+ options[:text_fields] = valid_options
315
+ else
316
+ invalid_options = (options[:text_fields] - valid_options)
317
+
318
+ unless invalid_options.empty?
319
+ raise OptionParser::ParseError, "\nInvalid format options: #{invalid_options.inspect}"
320
+ end
321
+ end
322
+ end
323
+
304
324
  opts.on "-w",
305
325
  "--confidence-level LEVEL",
306
326
  ["1", "2", "3"],
@@ -82,7 +82,6 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
82
82
  def replace exp, int = 0
83
83
  return exp if int > 3
84
84
 
85
-
86
85
  if replacement = env[exp] and not duplicate? replacement
87
86
  replace(replacement.deep_clone(exp.line), int + 1)
88
87
  elsif tracker and replacement = tracker.constant_lookup(exp) and not duplicate? replacement
@@ -731,14 +730,14 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
731
730
  def array_include_all_literals? exp
732
731
  call? exp and
733
732
  exp.method == :include? and
734
- all_literals? exp.target
733
+ (all_literals? exp.target or dir_glob? exp.target)
735
734
  end
736
735
 
737
736
  def array_detect_all_literals? exp
738
737
  call? exp and
739
738
  [:detect, :find].include? exp.method and
740
739
  exp.first_arg.nil? and
741
- all_literals? exp.target
740
+ (all_literals? exp.target or dir_glob? exp.target)
742
741
  end
743
742
 
744
743
  #Sets @inside_if = true
@@ -10,7 +10,7 @@ module Brakeman
10
10
  def join_arrays lhs, rhs, original_exp = nil
11
11
  if array? lhs and array? rhs
12
12
  result = Sexp.new(:array)
13
- result.line(lhs.line || rhs.line)
13
+ result.line(lhs.line || rhs.line || 1)
14
14
  result.concat lhs[1..-1]
15
15
  result.concat rhs[1..-1]
16
16
  result
@@ -20,6 +20,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
20
20
  @current_template = opts[:template]
21
21
  @current_file = opts[:file]
22
22
  @current_call = nil
23
+ @full_call = nil
23
24
  process exp
24
25
  end
25
26
 
@@ -89,7 +90,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
89
90
  #Calls to render() are converted to s(:render, ...) but we would
90
91
  #like them in the call cache still for speed
91
92
  def process_render exp
92
- process exp.last if sexp? exp.last
93
+ process_all exp
93
94
 
94
95
  add_simple_call :render, exp
95
96
 
@@ -137,7 +138,8 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
137
138
  :call => exp,
138
139
  :nested => false,
139
140
  :location => make_location,
140
- :parent => @current_call }.freeze
141
+ :parent => @current_call,
142
+ :full_call => @full_call }.freeze
141
143
  end
142
144
 
143
145
  #Gets the target of a call as a Symbol
@@ -214,34 +216,47 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
214
216
  #Return info hash for a call Sexp
215
217
  def create_call_hash exp
216
218
  target = get_target exp.target
217
-
218
- if call? target or node_type? target, :dxstr # need to index `` even if target of a call
219
- already_in_target = @in_target
220
- @in_target = true
221
- process target
222
- @in_target = already_in_target
223
-
224
- target = get_target(target, :include_calls)
225
- end
219
+ target_symbol = get_target(target, :include_calls)
226
220
 
227
221
  method = exp.method
228
222
 
229
223
  call_hash = {
230
- :target => target,
224
+ :target => target_symbol,
231
225
  :method => method,
232
226
  :call => exp,
233
227
  :nested => @in_target,
234
228
  :chain => get_chain(exp),
235
229
  :location => make_location,
236
- :parent => @current_call
230
+ :parent => @current_call,
231
+ :full_call => @full_call
237
232
  }
238
233
 
234
+ unless @in_target
235
+ @full_call = call_hash
236
+ end
237
+
238
+ # Process up the call chain
239
+ if call? target or node_type? target, :dxstr # need to index `` even if target of a call
240
+ already_in_target = @in_target
241
+ @in_target = true
242
+ process target
243
+ @in_target = already_in_target
244
+ end
245
+
246
+ # Process call arguments
247
+ # but add the current call as the 'parent'
248
+ # to any calls in the arguments
239
249
  old_parent = @current_call
240
250
  @current_call = call_hash
241
251
 
252
+ # Do not set @full_call when processing arguments
253
+ old_full_call = @full_call
254
+ @full_call = nil
255
+
242
256
  process_call_args exp
243
257
 
244
258
  @current_call = old_parent
259
+ @full_call = old_full_call
245
260
 
246
261
  call_hash
247
262
  end
@@ -98,7 +98,9 @@ module Brakeman::RenderHelper
98
98
 
99
99
  if hash? options[:locals]
100
100
  hash_iterate options[:locals] do |key, value|
101
- template_env[Sexp.new(:call, nil, key.value)] = value
101
+ if symbol? key
102
+ template_env[Sexp.new(:call, nil, key.value)] = value
103
+ end
102
104
  end
103
105
  end
104
106
 
@@ -43,6 +43,8 @@ class Brakeman::Report
43
43
  when :to_junit
44
44
  require_report 'junit'
45
45
  Brakeman::Report::JUnit
46
+ when :to_sarif
47
+ return self.to_sarif
46
48
  else
47
49
  raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}"
48
50
  end
@@ -85,6 +87,11 @@ class Brakeman::Report
85
87
  alias to_plain to_text
86
88
  alias to_s to_text
87
89
 
90
+ def to_sarif
91
+ require_report 'sarif'
92
+ generate Brakeman::Report::SARIF
93
+ end
94
+
88
95
  def generate reporter
89
96
  reporter.new(@tracker).generate_report
90
97
  end
@@ -94,6 +94,10 @@ module Brakeman
94
94
  end
95
95
  end
96
96
 
97
+ def already_ignored_entries_with_empty_notes
98
+ @already_ignored.select { |i| i if i[:note].strip.empty? }
99
+ end
100
+
97
101
  # Read configuration to file
98
102
  def read_from_file file = @file
99
103
  if File.exist? file
@@ -0,0 +1,114 @@
1
+ class Brakeman::Report::SARIF < Brakeman::Report::Base
2
+ def generate_report
3
+ sarif_log = {
4
+ :version => '2.1.0',
5
+ :$schema => 'https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json',
6
+ :runs => runs,
7
+ }
8
+ JSON.pretty_generate sarif_log
9
+ end
10
+
11
+ def runs
12
+ [
13
+ {
14
+ :tool => {
15
+ :driver => {
16
+ :name => 'Brakeman',
17
+ :informationUri => 'https://brakemanscanner.org',
18
+ :semanticVersion => Brakeman::Version,
19
+ :rules => rules,
20
+ },
21
+ },
22
+ :results => results,
23
+ },
24
+ ]
25
+ end
26
+
27
+ def rules
28
+ @rules ||= unique_warnings_by_warning_code.map do |warning|
29
+ rule_id = render_id warning
30
+ check_name = warning.check.gsub(/^Brakeman::Check/, '')
31
+ check_description = render_message check_descriptions[check_name]
32
+ {
33
+ :id => rule_id,
34
+ :name => "#{check_name}/#{warning.warning_type}",
35
+ :fullDescription => {
36
+ :text => check_description,
37
+ },
38
+ :helpUri => warning.link,
39
+ :help => {
40
+ :text => "More info: #{warning.link}.",
41
+ :markdown => "[More info](#{warning.link}).",
42
+ },
43
+ :properties => {
44
+ :tags => [check_name],
45
+ },
46
+ }
47
+ end
48
+ end
49
+
50
+ def results
51
+ @results ||= all_warnings.map do |warning|
52
+ rule_id = render_id warning
53
+ result_level = infer_level warning
54
+ message_text = render_message warning.message.to_s
55
+ result = {
56
+ :ruleId => rule_id,
57
+ :ruleIndex => rules.index { |r| r[:id] == rule_id },
58
+ :level => result_level,
59
+ :message => {
60
+ :text => message_text,
61
+ },
62
+ :locations => [
63
+ :physicalLocation => {
64
+ :artifactLocation => {
65
+ :uri => warning.file.relative,
66
+ :uriBaseId => '%SRCROOT%',
67
+ },
68
+ :region => {
69
+ :startLine => warning.line.is_a?(Integer) ? warning.line : 1,
70
+ },
71
+ },
72
+ ],
73
+ }
74
+
75
+ result
76
+ end
77
+ end
78
+
79
+ # Returns a hash of all check descriptions, keyed by check namne
80
+ def check_descriptions
81
+ @check_descriptions ||= Brakeman::Checks.checks.map do |check|
82
+ [check.name.gsub(/^Check/, ''), check.description]
83
+ end.to_h
84
+ end
85
+
86
+ # Returns a de-duplicated set of warnings, used to generate rules
87
+ def unique_warnings_by_warning_code
88
+ @unique_warnings_by_warning_code ||= all_warnings.uniq { |w| w.warning_code }
89
+ end
90
+
91
+ def render_id warning
92
+ # Include alpha prefix to provide 'compiler error' appearance
93
+ "BRAKE#{'%04d' % warning.warning_code}" # 46 becomes BRAKE0046, for example
94
+ end
95
+
96
+ def render_message message
97
+ # Ensure message ends with a period
98
+ if message.end_with? "."
99
+ message
100
+ else
101
+ "#{message}."
102
+ end
103
+ end
104
+
105
+ def infer_level warning
106
+ # Infer result level from warning confidence
107
+ @@levels_from_confidence ||= Hash.new('warning').update({
108
+ 0 => 'error', # 0 represents 'high confidence', which we infer as 'error'
109
+ 1 => 'warning', # 1 represents 'medium confidence' which we infer as 'warning'
110
+ 2 => 'note', # 2 represents 'weak, or low, confidence', which we infer as 'note'
111
+ })
112
+ @@levels_from_confidence[warning.confidence]
113
+ end
114
+ end
@@ -145,24 +145,45 @@ class Brakeman::Report::Text < Brakeman::Report::Base
145
145
  end
146
146
 
147
147
  def output_warning w
148
- out = [
149
- label('Confidence', confidence(w.confidence)),
150
- label('Category', w.warning_type.to_s),
151
- label('Check', w.check.gsub(/^Brakeman::Check/, '')),
148
+ text_format = tracker.options[:text_fields] ||
149
+ [:confidence, :category, :check, :message, :code, :file, :line]
150
+
151
+ text_format.map do |option|
152
+ format_line(w, option)
153
+ end.compact
154
+ end
155
+
156
+ def format_line w, option
157
+ case option
158
+ when :confidence
159
+ label('Confidence', confidence(w.confidence))
160
+ when :category
161
+ label('Category', w.warning_type.to_s)
162
+ when :check
163
+ label('Check', w.check.gsub(/^Brakeman::Check/, ''))
164
+ when :message
152
165
  label('Message', w.message)
153
- ]
154
-
155
- if w.code
156
- out << label('Code', format_code(w))
157
- end
158
-
159
- out << label('File', warning_file(w))
160
-
161
- if w.line
162
- out << label('Line', w.line)
166
+ when :code
167
+ if w.code
168
+ label('Code', format_code(w))
169
+ end
170
+ when :file
171
+ label('File', warning_file(w))
172
+ when :line
173
+ if w.line
174
+ label('Line', w.line)
175
+ end
176
+ when :link
177
+ label('Link', w.link)
178
+ when :fingerprint
179
+ label('Fingerprint', w.fingerprint)
180
+ when :category_id
181
+ label('Category ID', w.warning_code)
182
+ when :render_path
183
+ if w.called_from
184
+ label('Render Path', w.called_from.join(" > "))
185
+ end
163
186
  end
164
-
165
- out
166
187
  end
167
188
 
168
189
  def double_space title, values
@@ -94,11 +94,14 @@ class Brakeman::Scanner
94
94
  #
95
95
  #Stores parsed information in tracker.config
96
96
  def process_config
97
+ # Sometimes folks like to put constants in environment.rb
98
+ # so let's always process it even for newer Rails versions
99
+ process_config_file "environment.rb"
100
+
97
101
  if options[:rails3] or options[:rails4] or options[:rails5] or options[:rails6]
98
102
  process_config_file "application.rb"
99
103
  process_config_file "environments/production.rb"
100
104
  else
101
- process_config_file "environment.rb"
102
105
  process_config_file "gems.rb"
103
106
  end
104
107
 
@@ -198,8 +198,10 @@ class Brakeman::Tracker
198
198
  @constants.add name, value, context unless @options[:disable_constant_tracking]
199
199
  end
200
200
 
201
+ # This method does not return all constants at this time,
202
+ # just ones with "simple" values.
201
203
  def constant_lookup name
202
- @constants.get_literal name unless @options[:disable_constant_tracking]
204
+ @constants.get_simple_value name unless @options[:disable_constant_tracking]
203
205
  end
204
206
 
205
207
  def find_class name
@@ -54,7 +54,7 @@ module Brakeman
54
54
  end
55
55
 
56
56
  def gem_version name
57
- extract_version @gems.dig(name, :version)
57
+ extract_version @gems.dig(name.to_sym, :version)
58
58
  end
59
59
 
60
60
  def add_gem name, version, file, line
@@ -67,11 +67,11 @@ module Brakeman
67
67
  end
68
68
 
69
69
  def has_gem? name
70
- !!@gems[name]
70
+ !!@gems[name.to_sym]
71
71
  end
72
72
 
73
73
  def get_gem name
74
- @gems[name]
74
+ @gems[name.to_sym]
75
75
  end
76
76
 
77
77
  def set_rails_version version = nil
@@ -79,7 +79,9 @@ module Brakeman
79
79
  # Only used by Rails2ConfigProcessor right now
80
80
  extract_version(version)
81
81
  else
82
- gem_version(:rails) || gem_version(:railties)
82
+ gem_version(:rails) ||
83
+ gem_version(:railties) ||
84
+ gem_version(:activerecord)
83
85
  end
84
86
 
85
87
  if version
@@ -1,7 +1,10 @@
1
1
  require 'brakeman/processors/output_processor'
2
+ require 'brakeman/util'
2
3
 
3
4
  module Brakeman
4
5
  class Constant
6
+ include Brakeman::Util
7
+
5
8
  attr_reader :name, :name_array, :file, :value, :context
6
9
 
7
10
  def initialize name, value, context = {}
@@ -107,13 +110,11 @@ module Brakeman
107
110
  @constants[base_name] << Constant.new(name, value, context)
108
111
  end
109
112
 
110
- LITERALS = [:lit, :false, :str, :true, :array, :hash]
111
- def literal? exp
112
- exp.is_a? Sexp and LITERALS.include? exp.node_type
113
- end
114
-
115
- def get_literal name
116
- if x = self[name] and literal? x
113
+ # Returns constant values that are not too complicated.
114
+ # Right now that means literal values (string, array, etc.)
115
+ # or calls on Dir.glob(..).whatever.
116
+ def get_simple_value name
117
+ if x = self[name] and (literal? x or dir_glob? x)
117
118
  x
118
119
  else
119
120
  nil
@@ -293,6 +293,22 @@ module Brakeman::Util
293
293
  exp.is_a? Sexp and types.include? exp.node_type
294
294
  end
295
295
 
296
+ LITERALS = [:lit, :false, :str, :true, :array, :hash]
297
+
298
+ def literal? exp
299
+ exp.is_a? Sexp and LITERALS.include? exp.node_type
300
+ end
301
+
302
+ DIR_CONST = s(:const, :Dir)
303
+
304
+ # Dir.glob(...).whatever
305
+ def dir_glob? exp
306
+ exp = exp.block_call if node_type? exp, :iter
307
+ return unless call? exp
308
+
309
+ (exp.target == DIR_CONST and exp.method == :glob) or dir_glob? exp.target
310
+ end
311
+
296
312
  #Returns true if the given _exp_ contains a :class node.
297
313
  #
298
314
  #Useful for checking if a module is just a module or if it is a namespace.
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "4.8.0"
2
+ Version = "4.10.0"
3
3
  end
@@ -113,6 +113,13 @@ module Brakeman::WarningCodes
113
113
  :force_ssl_disabled => 109,
114
114
  :unsafe_cookie_serialization => 110,
115
115
  :reverse_tabnabbing => 111,
116
+ :mass_assign_permit_all => 112,
117
+ :json_html_escape_config => 113,
118
+ :json_html_escape_module => 114,
119
+ :CVE_2020_8159 => 115,
120
+ :CVE_2020_8166 => 116,
121
+ :erb_template_injection => 117,
122
+
116
123
  :custom_check => 9090,
117
124
  }
118
125
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman-min
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.8.0
4
+ version: 4.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-18 00:00:00.000000000 Z
11
+ date: 2020-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov-html
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '='
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.2
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: ruby_parser
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -146,6 +160,7 @@ files:
146
160
  - lib/brakeman/checks/check_cookie_serialization.rb
147
161
  - lib/brakeman/checks/check_create_with.rb
148
162
  - lib/brakeman/checks/check_cross_site_scripting.rb
163
+ - lib/brakeman/checks/check_csrf_token_forgery_cve.rb
149
164
  - lib/brakeman/checks/check_default_routes.rb
150
165
  - lib/brakeman/checks/check_deserialize.rb
151
166
  - lib/brakeman/checks/check_detailed_exceptions.rb
@@ -164,6 +179,7 @@ files:
164
179
  - lib/brakeman/checks/check_i18n_xss.rb
165
180
  - lib/brakeman/checks/check_jruby_xml.rb
166
181
  - lib/brakeman/checks/check_json_encoding.rb
182
+ - lib/brakeman/checks/check_json_entity_escape.rb
167
183
  - lib/brakeman/checks/check_json_parsing.rb
168
184
  - lib/brakeman/checks/check_link_to.rb
169
185
  - lib/brakeman/checks/check_link_to_href.rb
@@ -176,6 +192,7 @@ files:
176
192
  - lib/brakeman/checks/check_nested_attributes.rb
177
193
  - lib/brakeman/checks/check_nested_attributes_bypass.rb
178
194
  - lib/brakeman/checks/check_number_to_currency.rb
195
+ - lib/brakeman/checks/check_page_caching_cve.rb
179
196
  - lib/brakeman/checks/check_permit_attributes.rb
180
197
  - lib/brakeman/checks/check_quote_table_name.rb
181
198
  - lib/brakeman/checks/check_redirect.rb
@@ -205,6 +222,7 @@ files:
205
222
  - lib/brakeman/checks/check_strip_tags.rb
206
223
  - lib/brakeman/checks/check_symbol_dos.rb
207
224
  - lib/brakeman/checks/check_symbol_dos_cve.rb
225
+ - lib/brakeman/checks/check_template_injection.rb
208
226
  - lib/brakeman/checks/check_translate_bug.rb
209
227
  - lib/brakeman/checks/check_unsafe_reflection.rb
210
228
  - lib/brakeman/checks/check_unscoped_find.rb
@@ -274,6 +292,7 @@ files:
274
292
  - lib/brakeman/report/report_json.rb
275
293
  - lib/brakeman/report/report_junit.rb
276
294
  - lib/brakeman/report/report_markdown.rb
295
+ - lib/brakeman/report/report_sarif.rb
277
296
  - lib/brakeman/report/report_table.rb
278
297
  - lib/brakeman/report/report_tabs.rb
279
298
  - lib/brakeman/report/report_text.rb