brakeman-lib 5.3.1 → 5.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d1cfbfc4a477ca4da5ff22010526e792bc218fa2aa7abe454a8a9d0be4dd2128
4
- data.tar.gz: 5209d14eb749f8ec05ffd724e53a0cf575834c8f21fda1ef6c0481be4165133b
3
+ metadata.gz: 1bd15d1d3f41a0fe1f537728a63f5fe432eae4d4b82cdb07233007e794b4b19a
4
+ data.tar.gz: 8fd1c274006689e0e391c5586c9bd59c7ce776a998c9313561185216e51f9519
5
5
  SHA512:
6
- metadata.gz: 5e3deacdca32c220ca081e3ceff9f98f4a4152150b97e9fa2915c40569fc9a01b5f9a2b451593090e2ea20874ba741168ca2c1913a0fe77e59b215f58f2ea80e
7
- data.tar.gz: 8acf5cef0175a70381c5dfa6d015e9882a23786317b13cf16b6432376614dffb3bb30ead9fd12bb45f267f92a786012dd28d86728fc17e9ae7a4ef467951def9
6
+ metadata.gz: d3c99025931ba8a59c7852132cb80100f3f720de1ebfe632899e499a5784d5e92534efc6d9a04729bf3aec0d07c90fc2f69efccfd3d49558c277e370ae3d58f3
7
+ data.tar.gz: 571f89f8d5eb19b1d2c472517e3d66d819ab715379fd6b5bdd15cd136560499d8d86ff79da19a86a2c03830b8e0b0d0bf0dec3935267f75eacc95396a71d1f81
data/CHANGES.md CHANGED
@@ -1,3 +1,24 @@
1
+ # 5.4.1 - 2023-02-21
2
+
3
+ * Fix file/line location for EOL software warnings
4
+ * Revise checking for request.env to only consider request headers
5
+ * Add `redirect_back` and `redirect_back_or_to` to open redirect check
6
+ * Support Rails 7 redirect options
7
+ * Add Rails 6.1 and 7.0 default configuration values
8
+ * Prevent redirects using `url_from` being marked as unsafe (Lachlan Sylvester)
9
+ * Warn about unscoped find for `find_by(id: ...)`
10
+ * Support `presence`, `presence_in` and `in?`
11
+ * Fix issue with `if` expressions in `when` clauses
12
+
13
+ # 5.4.0 - 2022-11-17
14
+
15
+ * Use relative paths for CodeClimate report format (Mike Poage)
16
+ * Add check for weak RSA key sizes and padding modes
17
+ * Handle multiple values and splats in case/when
18
+ * Ignore more model methods in redirects
19
+ * Add check for absolute paths issue with Pathname
20
+ * Fix `load_rails_defaults` overwriting settings in the Rails application (James Gregory-Monk)
21
+
1
22
  # 5.3.1 - 2022-08-09
2
23
 
3
24
  * Fix version range for CVE-2022-32209
data/README.md CHANGED
@@ -64,9 +64,9 @@ Outside of Rails root (note that the output file is relative to path/to/rails/ap
64
64
 
65
65
  # Compatibility
66
66
 
67
- Brakeman should work with any version of Rails from 2.3.x to 6.x.
67
+ Brakeman should work with any version of Rails from 2.3.x to 7.x.
68
68
 
69
- Brakeman can analyze code written with Ruby 1.8 syntax and newer, but requires at least Ruby 2.4.0 to run.
69
+ Brakeman can analyze code written with Ruby 1.8 syntax and newer, but requires at least Ruby 2.5.0 to run.
70
70
 
71
71
  # Basic Options
72
72
 
@@ -76,7 +76,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
76
76
  @has_user_input = Match.new(:params, exp)
77
77
  elsif cookies? target
78
78
  @has_user_input = Match.new(:cookies, exp)
79
- elsif request_env? target
79
+ elsif request_headers? target
80
80
  @has_user_input = Match.new(:request, exp)
81
81
  elsif sexp? target and model_name? target[1] #TODO: Can this be target.target?
82
82
  @has_user_input = Match.new(:model, exp)
@@ -313,7 +313,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
313
313
  return Match.new(:params, exp)
314
314
  elsif cookies? exp
315
315
  return Match.new(:cookies, exp)
316
- elsif request_env? exp
316
+ elsif request_headers? exp
317
317
  return Match.new(:request, exp)
318
318
  else
319
319
  has_immediate_user_input? exp.target
@@ -467,7 +467,6 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
467
467
  version_between? version, "2.3.18.99", tracker.config.gem_version(:'railslts-version')
468
468
  end
469
469
 
470
-
471
470
  def version_between? low_version, high_version, current_version = nil
472
471
  tracker.config.version_between? low_version, high_version, current_version
473
472
  end
@@ -0,0 +1,48 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckPathname < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check for unexpected Pathname behavior"
7
+
8
+ def run_check
9
+ check_rails_root_join
10
+ check_pathname_join
11
+
12
+ end
13
+
14
+ def check_rails_root_join
15
+ tracker.find_call(target: :'Rails.root', method: :join, nested: true).each do |result|
16
+ check_result result
17
+ end
18
+ end
19
+
20
+ def check_pathname_join
21
+ pathname_methods = [
22
+ :'Pathname.new',
23
+ :'Pathname.getwd',
24
+ :'Pathname.glob',
25
+ :'Pathname.pwd',
26
+ ]
27
+
28
+ tracker.find_call(targets: pathname_methods, method: :join, nested: true).each do |result|
29
+ check_result result
30
+ end
31
+ end
32
+
33
+ def check_result result
34
+ return unless original? result
35
+
36
+ result[:call].each_arg do |arg|
37
+ if match = has_immediate_user_input?(arg)
38
+ warn :result => result,
39
+ :warning_type => "Path Traversal",
40
+ :warning_code => :pathname_traversal,
41
+ :message => "Absolute paths in `Pathname#join` cause the entire path to be relative to the absolute path, ignoring any prior values",
42
+ :user_input => match,
43
+ :confidence => :high,
44
+ :cwe_id => [22]
45
+ end
46
+ end
47
+ end
48
+ end
@@ -11,9 +11,7 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
11
11
  @description = "Looks for calls to redirect_to with user input as arguments"
12
12
 
13
13
  def run_check
14
- Brakeman.debug "Finding calls to redirect_to()"
15
-
16
- @model_find_calls = Set[:all, :create, :create!, :find, :find_by_sql, :first, :last, :new]
14
+ @model_find_calls = Set[:all, :create, :create!, :find, :find_by_sql, :first, :first!, :last, :last!, :new, :sole]
17
15
 
18
16
  if tracker.options[:rails3]
19
17
  @model_find_calls.merge [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where]
@@ -23,7 +21,13 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
23
21
  @model_find_calls.merge [:find_by, :find_by!, :take]
24
22
  end
25
23
 
26
- @tracker.find_call(:target => false, :method => :redirect_to).each do |res|
24
+ if version_between? "7.0.0", "9.9.9"
25
+ @model_find_calls << :find_sole_by
26
+ end
27
+
28
+ methods = [:redirect_to, :redirect_back, :redirect_back_or_to]
29
+
30
+ @tracker.find_call(:target => false, :methods => methods).each do |res|
27
31
  process_result res
28
32
  end
29
33
  end
@@ -32,18 +36,28 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
32
36
  return unless original? result
33
37
 
34
38
  call = result[:call]
35
- method = call.method
36
-
37
39
  opt = call.first_arg
38
40
 
39
- if method == :redirect_to and
41
+ # Location is specified with `fallback_location:`
42
+ # otherwise the arguments do not contain a location and
43
+ # the call can be ignored
44
+ if call.method == :redirect_back
45
+ if hash? opt and location = hash_access(opt, :fallback_location)
46
+ opt = location
47
+ else
48
+ return
49
+ end
50
+ end
51
+
52
+ if not protected_by_raise?(call) and
40
53
  not only_path?(call) and
41
54
  not explicit_host?(opt) and
42
55
  not slice_call?(opt) and
43
56
  not safe_permit?(opt) and
44
- res = include_user_input?(call)
57
+ not disallow_other_host?(call) and
58
+ res = include_user_input?(opt)
45
59
 
46
- if res.type == :immediate
60
+ if res.type == :immediate and not allow_other_host?(call)
47
61
  confidence = :high
48
62
  else
49
63
  confidence = :weak
@@ -64,42 +78,42 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
64
78
  #is being output directly. This is necessary because of tracker.options[:check_arguments]
65
79
  #which can be used to enable/disable reporting output of method calls which use
66
80
  #user input as arguments.
67
- def include_user_input? call, immediate = :immediate
81
+ def include_user_input? opt, immediate = :immediate
68
82
  Brakeman.debug "Checking if call includes user input"
69
83
 
70
- arg = call.first_arg
71
-
72
84
  # if the first argument is an array, rails assumes you are building a
73
85
  # polymorphic route, which will never jump off-host
74
- return false if array? arg
86
+ return false if array? opt
75
87
 
76
88
  if tracker.options[:ignore_redirect_to_model]
77
- if model_instance?(arg) or decorated_model?(arg)
89
+ if model_instance?(opt) or decorated_model?(opt)
78
90
  return false
79
91
  end
80
92
  end
81
93
 
82
- if res = has_immediate_model?(arg)
83
- unless call? arg and arg.method.to_s =~ /_path/
94
+ if res = has_immediate_model?(opt)
95
+ unless call? opt and opt.method.to_s =~ /_path/
84
96
  return Match.new(immediate, res)
85
97
  end
86
- elsif call? arg
87
- if request_value? arg
88
- return Match.new(immediate, arg)
89
- elsif request_value? arg.target
90
- return Match.new(immediate, arg.target)
91
- elsif arg.method == :url_for and include_user_input? arg
92
- return Match.new(immediate, arg)
98
+ elsif call? opt
99
+ if request_value? opt
100
+ return Match.new(immediate, opt)
101
+ elsif opt.method == :url_for and include_user_input? opt.first_arg
102
+ return Match.new(immediate, opt)
93
103
  #Ignore helpers like some_model_url?
94
- elsif arg.method.to_s =~ /_(url|path)\z/
104
+ elsif opt.method.to_s =~ /_(url|path)\z/
105
+ return false
106
+ elsif opt.method == :url_from
95
107
  return false
96
108
  end
97
- elsif request_value? arg
98
- return Match.new(immediate, arg)
109
+ elsif request_value? opt
110
+ return Match.new(immediate, opt)
111
+ elsif node_type? opt, :or
112
+ return (include_user_input?(opt.lhs) or include_user_input?(opt.rhs))
99
113
  end
100
114
 
101
- if tracker.options[:check_arguments] and call? arg
102
- include_user_input? arg, false #I'm doubting if this is really necessary...
115
+ if tracker.options[:check_arguments] and call? opt
116
+ include_user_input? opt.first_arg, false #I'm doubting if this is really necessary...
103
117
  else
104
118
  false
105
119
  end
@@ -204,7 +218,7 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
204
218
  def friendly_model? exp
205
219
  call? exp and model_name? exp.target and exp.method == :friendly
206
220
  end
207
-
221
+
208
222
  #Returns true if exp is (probably) a decorated model instance
209
223
  #using the Draper gem
210
224
  def decorated_model? exp
@@ -245,7 +259,7 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
245
259
  if call? exp and params? exp.target and exp.method == :permit
246
260
  exp.each_arg do |opt|
247
261
  if symbol? opt and DANGEROUS_KEYS.include? opt.value
248
- return false
262
+ return false
249
263
  end
250
264
  end
251
265
 
@@ -254,4 +268,25 @@ class Brakeman::CheckRedirect < Brakeman::BaseCheck
254
268
 
255
269
  false
256
270
  end
271
+
272
+ def protected_by_raise? call
273
+ raise_on_redirects? and
274
+ not allow_other_host? call
275
+ end
276
+
277
+ def raise_on_redirects?
278
+ @raise_on_redirects ||= true?(tracker.config.rails.dig(:action_controller, :raise_on_open_redirects))
279
+ end
280
+
281
+ def allow_other_host? call
282
+ opt = call.last_arg
283
+
284
+ hash? opt and true? hash_access(opt, :allow_other_host)
285
+ end
286
+
287
+ def disallow_other_host? call
288
+ opt = call.last_arg
289
+
290
+ hash? opt and false? hash_access(opt, :allow_other_host)
291
+ end
257
292
  end
@@ -23,6 +23,14 @@ class Brakeman::CheckUnscopedFind < Brakeman::BaseCheck
23
23
  calls.each do |call|
24
24
  process_result call
25
25
  end
26
+
27
+ tracker.find_call(:method => :find_by, :targets => associated_model_names).each do |result|
28
+ arg = result[:call].first_arg
29
+
30
+ if hash? arg and hash_access(arg, :id)
31
+ process_result result
32
+ end
33
+ end
26
34
  end
27
35
 
28
36
  def process_result result
@@ -0,0 +1,112 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckWeakRSAKey < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for weak uses RSA keys"
7
+
8
+ def run_check
9
+ check_rsa_key_creation
10
+ check_rsa_operations
11
+ end
12
+
13
+ def check_rsa_key_creation
14
+ tracker.find_call(targets: [:'OpenSSL::PKey::RSA'], method: [:new, :generate], nested: true).each do |result|
15
+ key_size_arg = result[:call].first_arg
16
+ check_key_size(result, key_size_arg)
17
+ end
18
+
19
+ tracker.find_call(targets: [:'OpenSSL::PKey'], method: [:generate_key], nested: true).each do |result|
20
+ call = result[:call]
21
+ key_type = call.first_arg
22
+ options_arg = call.second_arg
23
+
24
+ next unless options_arg and hash? options_arg
25
+
26
+ if string? key_type and key_type.value.upcase == 'RSA'
27
+ key_size_arg = (hash_access(options_arg, :rsa_keygen_bits) || hash_access(options_arg, s(:str, 'rsa_key_gen_bits')))
28
+ check_key_size(result, key_size_arg)
29
+ end
30
+ end
31
+ end
32
+
33
+ def check_rsa_operations
34
+ tracker.find_call(targets: [:'OpenSSL::PKey::RSA.new'], methods: [:public_encrypt, :public_decrypt, :private_encrypt, :private_decrypt], nested: true).each do |result|
35
+ padding_arg = result[:call].second_arg
36
+ check_padding(result, padding_arg)
37
+ end
38
+
39
+ tracker.find_call(targets: [:'OpenSSL::PKey.generate_key'], methods: [:encrypt, :decrypt, :sign, :verify, :sign_raw, :verify_raw], nested: true).each do |result|
40
+ call = result[:call]
41
+ options_arg = call.last_arg
42
+
43
+ if options_arg and hash? options_arg
44
+ padding_arg = (hash_access(options_arg, :rsa_padding_mode) || hash_access(options_arg, s(:str, 'rsa_padding_mode')))
45
+ else
46
+ padding_arg = nil
47
+ end
48
+
49
+ check_padding(result, padding_arg)
50
+ end
51
+ end
52
+
53
+ def check_key_size result, key_size_arg
54
+ return unless number? key_size_arg
55
+ return unless original? result
56
+
57
+ key_size = key_size_arg.value
58
+
59
+ if key_size < 1024
60
+ confidence = :high
61
+ message = msg("RSA key with size ", msg_code(key_size.to_s), " is considered very weak. Use at least 2048 bit key size")
62
+ elsif key_size < 2048
63
+ confidence = :medium
64
+ message = msg("RSA key with size ", msg_code(key_size.to_s), " is considered weak. Use at least 2048 bit key size")
65
+ else
66
+ return
67
+ end
68
+
69
+ warn result: result,
70
+ warning_type: "Weak Cryptography",
71
+ warning_code: :small_rsa_key_size,
72
+ message: message,
73
+ confidence: confidence,
74
+ user_input: key_size_arg,
75
+ cwe_id: [326]
76
+ end
77
+
78
+ PKCS1_PADDING = s(:colon2, s(:colon2, s(:colon2, s(:const, :OpenSSL), :PKey), :RSA), :PKCS1_PADDING).freeze
79
+ PKCS1_PADDING_STR = s(:str, 'pkcs1').freeze
80
+ SSLV23_PADDING = s(:colon2, s(:colon2, s(:colon2, s(:const, :OpenSSL), :PKey), :RSA), :SSLV23_PADDING).freeze
81
+ SSLV23_PADDING_STR = s(:str, 'sslv23').freeze
82
+ NO_PADDING = s(:colon2, s(:colon2, s(:colon2, s(:const, :OpenSSL), :PKey), :RSA), :NO_PADDING).freeze
83
+ NO_PADDING_STR = s(:str, 'none').freeze
84
+
85
+ def check_padding result, padding_arg
86
+ return unless original? result
87
+
88
+ if string? padding_arg
89
+ padding_arg = padding_arg.deep_clone(padding_arg.line)
90
+ padding_arg.value.downcase!
91
+ end
92
+
93
+ case padding_arg
94
+ when PKCS1_PADDING, PKCS1_PADDING_STR, nil
95
+ message = "Use of padding mode PKCS1 (default if not specified), which is known to be insecure. Use OAEP instead"
96
+ when SSLV23_PADDING, SSLV23_PADDING_STR
97
+ message = "Use of padding mode SSLV23 for RSA key, which is only useful for outdated versions of SSL. Use OAEP instead"
98
+ when NO_PADDING, NO_PADDING_STR
99
+ message = "No padding mode used for RSA key. A safe padding mode (OAEP) should be specified for RSA keys"
100
+ else
101
+ return
102
+ end
103
+
104
+ warn result: result,
105
+ warning_type: "Weak Cryptography",
106
+ warning_code: :insecure_rsa_padding_mode,
107
+ message: message,
108
+ confidence: :high,
109
+ user_input: padding_arg,
110
+ cwe_id: [780]
111
+ end
112
+ end
@@ -34,7 +34,7 @@ class Brakeman::EOLCheck < Brakeman::BaseCheck
34
34
  warning_code: :"pending_eol_#{library}",
35
35
  message: msg("Support for ", msg_version(version, library.capitalize), " ends on #{eol_date}"),
36
36
  confidence: confidence,
37
- gem_info: gemfile_or_environment,
37
+ gem_info: gemfile_or_environment(library),
38
38
  :cwe_id => [1104]
39
39
  end
40
40
 
@@ -43,7 +43,7 @@ class Brakeman::EOLCheck < Brakeman::BaseCheck
43
43
  warning_code: :"eol_#{library}",
44
44
  message: msg("Support for ", msg_version(version, library.capitalize), " ended on #{eol_date}"),
45
45
  confidence: :high,
46
- gem_info: gemfile_or_environment,
46
+ gem_info: gemfile_or_environment(library),
47
47
  :cwe_id => [1104]
48
48
  end
49
49
  end
@@ -300,11 +300,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
300
300
  if array? target and first_arg.nil? and sexp? target[1]
301
301
  exp = target[1]
302
302
  end
303
- when :freeze
304
- unless target.nil?
305
- exp = target
306
- end
307
- when :dup
303
+ when :freeze, :dup, :presence
308
304
  unless target.nil?
309
305
  exp = target
310
306
  end
@@ -332,6 +328,17 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
332
328
  exp = res
333
329
  end
334
330
  end
331
+ when :presence_in
332
+ arg = exp.first_arg
333
+
334
+ if node_type? arg, :array
335
+ # 1.presence_in [1,2,3]
336
+ if arg.include? target
337
+ exp = target
338
+ elsif all_literals? arg
339
+ exp = safe_literal(exp.line)
340
+ end
341
+ end
335
342
  end
336
343
 
337
344
  exp
@@ -862,6 +869,17 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
862
869
  (all_literals? exp.target or dir_glob? exp.target)
863
870
  end
864
871
 
872
+ # Check if exp is a call to Array#include? on an array literal
873
+ # that contains all literal values. For example:
874
+ #
875
+ # x.in? [1, 2, "a"]
876
+ #
877
+ def in_array_all_literals? exp
878
+ call? exp and
879
+ exp.method == :in? and
880
+ all_literals? exp.first_arg
881
+ end
882
+
865
883
  # Check if exp is a call to Hash#include? on a hash literal
866
884
  # that contains all literal values. For example:
867
885
  #
@@ -915,28 +933,30 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
915
933
  scope do
916
934
  @branch_env = env.current
917
935
  branch_index = 2 + i # s(:if, condition, then_branch, else_branch)
918
- if i == 0 and hash_or_array_include_all_literals? condition
936
+ exp[branch_index] = if i == 0 and hash_or_array_include_all_literals? condition
919
937
  # If the condition is ["a", "b"].include? x
920
- # set x to "a" inside the true branch
938
+ # set x to safe_literal inside the true branch
921
939
  var = condition.first_arg
922
- previous_value = env.current[var]
923
- env.current[var] = safe_literal(var.line)
924
- exp[branch_index] = process_if_branch branch
925
- env.current[var] = previous_value
940
+ value = safe_literal(var.line)
941
+ process_branch_with_value(var, value, branch, i)
942
+ elsif i == 0 and in_array_all_literals? condition
943
+ # If the condition is x.in? ["a", "b"]
944
+ # set x to safe_literal inside the true branch
945
+ var = condition.target
946
+ value = safe_literal(var.line)
947
+ process_branch_with_value(var, value, branch, i)
926
948
  elsif i == 0 and equality_check? condition
927
949
  # For conditions like a == b,
928
950
  # set a to b inside the true branch
929
951
  var = condition.target
930
- previous_value = env.current[var]
931
- env.current[var] = condition.first_arg
932
- exp[branch_index] = process_if_branch branch
933
- env.current[var] = previous_value
952
+ value = condition.first_arg
953
+ process_branch_with_value(var, value, branch, i)
934
954
  elsif i == 1 and hash_or_array_include_all_literals? condition and early_return? branch
935
955
  var = condition.first_arg
936
956
  env.current[var] = safe_literal(var.line)
937
- exp[branch_index] = process_if_branch branch
957
+ process_if_branch branch
938
958
  else
939
- exp[branch_index] = process_if_branch branch
959
+ process_if_branch branch
940
960
  end
941
961
  branch_scopes << env.current
942
962
  @branch_env = nil
@@ -953,6 +973,14 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
953
973
  exp
954
974
  end
955
975
 
976
+ def process_branch_with_value var, value, branch, branch_index
977
+ previous_value = env.current[var]
978
+ env.current[var] = value
979
+ result = process_if_branch branch
980
+ env.current[var] = previous_value
981
+ result
982
+ end
983
+
956
984
  def early_return? exp
957
985
  return true if node_type? exp, :return
958
986
  return true if call? exp and [:fail, :raise].include? exp.method
@@ -970,11 +998,27 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
970
998
  exp.method == :==
971
999
  end
972
1000
 
1001
+ # Not a list of values
1002
+ # when :example
973
1003
  def simple_when? exp
974
1004
  node_type? exp[1], :array and
975
- not node_type? exp[1][1], :splat, :array and
976
- (exp[1].length == 2 or
977
- exp[1].all? { |e| e.is_a? Symbol or node_type? e, :lit, :str })
1005
+ exp[1].length == 2 and # only one element in the array
1006
+ not node_type? exp[1][1], :splat, :array
1007
+ end
1008
+
1009
+ # A list of literal values
1010
+ #
1011
+ # when 1,2,3
1012
+ #
1013
+ # or
1014
+ #
1015
+ # when *[:a, :b]
1016
+ def all_literals_when? exp
1017
+ if array? exp[1] # pretty sure this is always true
1018
+ all_literals? exp[1] or # simple list, not actually array
1019
+ (splat_array? exp[1][1] and
1020
+ all_literals? exp[1][1][1])
1021
+ end
978
1022
  end
979
1023
 
980
1024
  def process_case exp
@@ -1000,11 +1044,21 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
1000
1044
  exp.each_sexp do |e|
1001
1045
  if node_type? e, :when
1002
1046
  scope do
1047
+ # Process the when value for matching
1048
+ process_default e[1]
1049
+
1050
+ # Moved here to avoid @branch_env being cleared out
1051
+ # in process_default
1052
+ # Maybe in the future don't set it to nil?
1003
1053
  @branch_env = env.current
1004
1054
 
1005
1055
  # set value of case var if possible
1006
- if case_value and simple_when? e
1007
- @branch_env[case_value] = e[1][1]
1056
+ if case_value
1057
+ if simple_when? e
1058
+ @branch_env[case_value] = e[1][1]
1059
+ elsif all_literals_when? e
1060
+ @branch_env[case_value] = safe_literal(e.line + 1)
1061
+ end
1008
1062
  end
1009
1063
 
1010
1064
  # when blocks aren't blocks, they are lists of expressions
@@ -56,7 +56,7 @@ class Brakeman::GemProcessor < Brakeman::BasicProcessor
56
56
  elsif exp.method == :ruby
57
57
  version = exp.first_arg
58
58
  if string? version
59
- @tracker.config.set_ruby_version version.value
59
+ @tracker.config.set_ruby_version version.value, @gemfile, exp.line
60
60
  end
61
61
  end
62
62
  elsif @inside_gemspec and exp.method == :add_dependency
@@ -97,7 +97,7 @@ class Brakeman::GemProcessor < Brakeman::BasicProcessor
97
97
  if line =~ @gem_name_version
98
98
  @tracker.config.add_gem $1, $2, file, line_num
99
99
  elsif line =~ @ruby_version
100
- @tracker.config.set_ruby_version $1
100
+ @tracker.config.set_ruby_version $1, file, line_num
101
101
  end
102
102
  end
103
103
  end
@@ -86,7 +86,7 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BasicProcessor
86
86
  end
87
87
  elsif include_rails_config? exp
88
88
  options_path = get_rails_config exp
89
- @tracker.config.set_rails_config(exp.first_arg, *options_path)
89
+ @tracker.config.set_rails_config(value: exp.first_arg, path: options_path, overwrite: true)
90
90
  end
91
91
 
92
92
  exp
@@ -73,7 +73,7 @@ class Brakeman::Report::CodeClimate < Brakeman::Report::Base
73
73
  if tracker.options[:path_prefix]
74
74
  (Pathname.new(tracker.options[:path_prefix]) + Pathname.new(warning.file.relative)).to_s
75
75
  else
76
- warning.file
76
+ warning.relative_path
77
77
  end
78
78
  end
79
79
  end
@@ -6,7 +6,7 @@ require 'brakeman/differ'
6
6
  class Brakeman::Rescanner < Brakeman::Scanner
7
7
  include Brakeman::Util
8
8
  KNOWN_TEMPLATE_EXTENSIONS = Brakeman::TemplateParser::KNOWN_TEMPLATE_EXTENSIONS
9
- SCAN_ORDER = [:config, :gemfile, :initializer, :lib, :routes, :template,
9
+ SCAN_ORDER = [:gemfile, :config, :initializer, :lib, :routes, :template,
10
10
  :model, :controller]
11
11
 
12
12
  #Create new Rescanner to scan changed files
@@ -332,6 +332,8 @@ class Brakeman::Rescanner < Brakeman::Scanner
332
332
  :routes
333
333
  when /\/config\/.+\.(rb|yml)/
334
334
  :config
335
+ when /\.ruby-version/
336
+ :config
335
337
  when /Gemfile|gems\./
336
338
  :gemfile
337
339
  else
@@ -138,7 +138,7 @@ class Brakeman::Scanner
138
138
 
139
139
  if @app_tree.exists? ".ruby-version"
140
140
  if version = @app_tree.file_path(".ruby-version").read[/(\d\.\d.\d+)/]
141
- tracker.config.set_ruby_version version
141
+ tracker.config.set_ruby_version version, @app_tree.file_path(".ruby-version"), 1
142
142
  end
143
143
  end
144
144
 
@@ -129,8 +129,9 @@ module Brakeman
129
129
  @rails_version
130
130
  end
131
131
 
132
- def set_ruby_version version
132
+ def set_ruby_version version, file, line
133
133
  @ruby_version = extract_version(version)
134
+ add_gem :ruby, @ruby_version, file, line
134
135
  end
135
136
 
136
137
  def extract_version version
@@ -166,7 +167,7 @@ module Brakeman
166
167
  # then this will set
167
168
  #
168
169
  # rails[:action_controller][:perform_caching] = value
169
- def set_rails_config value, *path
170
+ def set_rails_config value:, path:, overwrite: false
170
171
  config = self.rails
171
172
 
172
173
  path[0..-2].each do |o|
@@ -182,7 +183,9 @@ module Brakeman
182
183
  config = option
183
184
  end
184
185
 
185
- config[path.last] = value
186
+ if overwrite || config[path.last].nil?
187
+ config[path.last] = value
188
+ end
186
189
  end
187
190
 
188
191
  # Load defaults based on config.load_defaults value
@@ -195,38 +198,78 @@ module Brakeman
195
198
  false_value = Sexp.new(:false)
196
199
 
197
200
  if version >= 5.0
198
- set_rails_config(true_value, :action_controller, :per_form_csrf_tokens)
199
- set_rails_config(true_value, :action_controller, :forgery_protection_origin_check)
200
- set_rails_config(true_value, :active_record, :belongs_to_required_by_default)
201
+ set_rails_config(value: true_value, path: [:action_controller, :per_form_csrf_tokens])
202
+ set_rails_config(value: true_value, path: [:action_controller, :forgery_protection_origin_check])
203
+ set_rails_config(value: true_value, path: [:active_record, :belongs_to_required_by_default])
201
204
  # Note: this may need to be changed, because ssl_options is a Hash
202
- set_rails_config(true_value, :ssl_options, :hsts, :subdomains)
205
+ set_rails_config(value: true_value, path: [:ssl_options, :hsts, :subdomains])
203
206
  end
204
207
 
205
208
  if version >= 5.1
206
- set_rails_config(false_value, :assets, :unknown_asset_fallback)
207
- set_rails_config(true_value, :action_view, :form_with_generates_remote_forms)
209
+ set_rails_config(value: false_value, path: [:assets, :unknown_asset_fallback])
210
+ set_rails_config(value: true_value, path: [:action_view, :form_with_generates_remote_forms])
208
211
  end
209
212
 
210
213
  if version >= 5.2
211
- set_rails_config(true_value, :active_record, :cache_versioning)
212
- set_rails_config(true_value, :action_dispatch, :use_authenticated_cookie_encryption)
213
- set_rails_config(true_value, :active_support, :use_authenticated_message_encryption)
214
- set_rails_config(true_value, :active_support, :use_sha1_digests)
215
- set_rails_config(true_value, :action_controller, :default_protect_from_forgery)
216
- set_rails_config(true_value, :action_view, :form_with_generates_ids)
214
+ set_rails_config(value: true_value, path: [:active_record, :cache_versioning])
215
+ set_rails_config(value: true_value, path: [:action_dispatch, :use_authenticated_cookie_encryption])
216
+ set_rails_config(value: true_value, path: [:active_support, :use_authenticated_message_encryption])
217
+ set_rails_config(value: true_value, path: [:active_support, :use_sha1_digests])
218
+ set_rails_config(value: true_value, path: [:action_controller, :default_protect_from_forgery])
219
+ set_rails_config(value: true_value, path: [:action_view, :form_with_generates_ids])
217
220
  end
218
221
 
219
222
  if version >= 6.0
220
- set_rails_config(Sexp.new(:lit, :zeitwerk), :autoloader)
221
- set_rails_config(false_value, :action_view, :default_enforce_utf8)
222
- set_rails_config(true_value, :action_dispatch, :use_cookies_with_metadata)
223
- set_rails_config(false_value, :action_dispatch, :return_only_media_type_on_content_type)
224
- set_rails_config(Sexp.new(:str, 'ActionMailer::MailDeliveryJob'), :action_mailer, :delivery_job)
225
- set_rails_config(true_value, :active_job, :return_false_on_aborted_enqueue)
226
- set_rails_config(Sexp.new(:lit, :active_storage_analysis), :active_storage, :queues, :analysis)
227
- set_rails_config(Sexp.new(:lit, :active_storage_purge), :active_storage, :queues, :purge)
228
- set_rails_config(true_value, :active_storage, :replace_on_assign_to_many)
229
- set_rails_config(true_value, :active_record, :collection_cache_versioning)
223
+ set_rails_config(value: Sexp.new(:lit, :zeitwerk), path: [:autoloader])
224
+ set_rails_config(value: false_value, path: [:action_view, :default_enforce_utf8])
225
+ set_rails_config(value: true_value, path: [:action_dispatch, :use_cookies_with_metadata])
226
+ set_rails_config(value: false_value, path: [:action_dispatch, :return_only_media_type_on_content_type])
227
+ set_rails_config(value: Sexp.new(:str, 'ActionMailer::MailDeliveryJob'), path: [:action_mailer, :delivery_job])
228
+ set_rails_config(value: true_value, path: [:active_job, :return_false_on_aborted_enqueue])
229
+ set_rails_config(value: Sexp.new(:lit, :active_storage_analysis), path: [:active_storage, :queues, :analysis])
230
+ set_rails_config(value: Sexp.new(:lit, :active_storage_purge), path: [:active_storage, :queues, :purge])
231
+ set_rails_config(value: true_value, path: [:active_storage, :replace_on_assign_to_many])
232
+ set_rails_config(value: true_value, path: [:active_record, :collection_cache_versioning])
233
+ end
234
+
235
+ if version >= 6.1
236
+ set_rails_config(value: true_value, path: [:action_controller, :urlsafe_csrf_tokens])
237
+ set_rails_config(value: Sexp.new(:lit, :lax), path: [:action_dispatch, :cookies_same_site_protection])
238
+ set_rails_config(value: Sexp.new(:lit, 308), path: [:action_dispatch, :ssl_default_redirect_status])
239
+ set_rails_config(value: false_value, path: [:action_view, :form_with_generates_remote_forms])
240
+ set_rails_config(value: true_value, path: [:action_view, :preload_links_header])
241
+ set_rails_config(value: Sexp.new(:lit, 0.15), path: [:active_job, :retry_jitter])
242
+ set_rails_config(value: true_value, path: [:active_record, :has_many_inversing])
243
+ set_rails_config(value: false_value, path: [:active_record, :legacy_connection_handling])
244
+ set_rails_config(value: true_value, path: [:active_storage, :track_variants])
245
+ end
246
+
247
+ if version >= 7.0
248
+ video_args =
249
+ Sexp.new(:str, "-vf 'select=eq(n\\,0)+eq(key\\,1)+gt(scene\\,0.015),loop=loop=-1:size=2,trim=start_frame=1' -frames:v 1 -f image2")
250
+ hash_class = s(:colon2, s(:colon2, s(:const, :OpenSSL), :Digest), :SHA256)
251
+
252
+ set_rails_config(value: true_value, path: [:action_controller, :raise_on_open_redirects])
253
+ set_rails_config(value: true_value, path: [:action_controller, :wrap_parameters_by_default])
254
+ set_rails_config(value: Sexp.new(:lit, :json), path: [:action_dispatch, :cookies_serializer])
255
+ set_rails_config(value: false_value, path: [:action_dispatch, :return_only_request_media_type_on_content_type])
256
+ set_rails_config(value: Sexp.new(:lit, 5), path: [:action_mailer, :smtp_timeout])
257
+ set_rails_config(value: false_value, path: [:action_view, :apply_stylesheet_media_default])
258
+ set_rails_config(value: true_value, path: [:ction_view, :button_to_generates_button_tag])
259
+ set_rails_config(value: true_value, path: [:active_record, :automatic_scope_inversing])
260
+ set_rails_config(value: false_value, path: [:active_record, :partial_inserts])
261
+ set_rails_config(value: true_value, path: [:active_record, :verify_foreign_keys_for_fixtures])
262
+ set_rails_config(value: true_value, path: [:active_storage, :multiple_file_field_include_hidden])
263
+ set_rails_config(value: Sexp.new(:lit, :vips), path: [:active_storage, :variant_processor])
264
+ set_rails_config(value: video_args, path: [:active_storage, :video_preview_arguments])
265
+ set_rails_config(value: Sexp.new(:lit, 7.0), path: [:active_support, :cache_format_version])
266
+ set_rails_config(value: true_value, path: [:active_support, :disable_to_s_conversion])
267
+ set_rails_config(value: true_value, path: [:active_support, :executor_around_test_case])
268
+ set_rails_config(value: hash_class, path: [:active_support, :hash_digest_class])
269
+ set_rails_config(value: Sexp.new(:lit, :thread), path: [:active_support, :isolation_level])
270
+ set_rails_config(value: hash_class, path: [:active_support, :key_generator_hash_digest_class])
271
+ set_rails_config(value: true_value, path: [:active_support, :remove_deprecated_time_with_zone_name])
272
+ set_rails_config(value: true_value, path: [:active_support, :use_rfc4122_namespaced_uuids])
230
273
  end
231
274
  end
232
275
  end
@@ -371,7 +371,7 @@ class Brakeman::Tracker
371
371
  end
372
372
  end
373
373
 
374
- @models.delete model_name
374
+ @models.delete(model_name)
375
375
  end
376
376
 
377
377
  #Clear information related to model
data/lib/brakeman/util.rb CHANGED
@@ -265,15 +265,31 @@ module Brakeman::Util
265
265
  false
266
266
  end
267
267
 
268
- def request_env? exp
269
- call? exp and (exp == REQUEST_ENV or exp[1] == REQUEST_ENV)
268
+ # Only return true when accessing request headers via request.env[...]
269
+ def request_headers? exp
270
+ return unless sexp? exp
271
+
272
+ if exp[1] == REQUEST_ENV
273
+ if exp.method == :[]
274
+ if string? exp.first_arg
275
+ # Only care about HTTP headers, which are prefixed by 'HTTP_'
276
+ exp.first_arg.value.start_with?('HTTP_'.freeze)
277
+ else
278
+ true # request.env[something]
279
+ end
280
+ else
281
+ false # request.env.something
282
+ end
283
+ else
284
+ false
285
+ end
270
286
  end
271
287
 
272
- #Check if exp is params, cookies, or request_env
288
+ #Check if exp is params, cookies, or request_headers
273
289
  def request_value? exp
274
290
  params? exp or
275
291
  cookies? exp or
276
- request_env? exp
292
+ request_headers? exp
277
293
  end
278
294
 
279
295
  def constant? exp
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "5.3.1"
2
+ Version = "5.4.1"
3
3
  end
@@ -126,6 +126,10 @@ module Brakeman::WarningCodes
126
126
  :pending_eol_rails => 122,
127
127
  :pending_eol_ruby => 123,
128
128
  :CVE_2022_32209 => 124,
129
+ :pathname_traversal => 125,
130
+ :insecure_rsa_padding_mode => 126,
131
+ :missing_rsa_padding_mode => 127,
132
+ :small_rsa_key_size => 128,
129
133
 
130
134
  :custom_check => 9090,
131
135
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.3.1
4
+ version: 5.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-10 00:00:00.000000000 Z
11
+ date: 2023-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -299,6 +299,7 @@ files:
299
299
  - lib/brakeman/checks/check_nested_attributes_bypass.rb
300
300
  - lib/brakeman/checks/check_number_to_currency.rb
301
301
  - lib/brakeman/checks/check_page_caching_cve.rb
302
+ - lib/brakeman/checks/check_pathname.rb
302
303
  - lib/brakeman/checks/check_permit_attributes.rb
303
304
  - lib/brakeman/checks/check_quote_table_name.rb
304
305
  - lib/brakeman/checks/check_redirect.rb
@@ -337,6 +338,7 @@ files:
337
338
  - lib/brakeman/checks/check_validation_regex.rb
338
339
  - lib/brakeman/checks/check_verb_confusion.rb
339
340
  - lib/brakeman/checks/check_weak_hash.rb
341
+ - lib/brakeman/checks/check_weak_rsa_key.rb
340
342
  - lib/brakeman/checks/check_without_protection.rb
341
343
  - lib/brakeman/checks/check_xml_dos.rb
342
344
  - lib/brakeman/checks/check_yaml_parsing.rb
@@ -448,7 +450,7 @@ metadata:
448
450
  mailing_list_uri: https://gitter.im/presidentbeef/brakeman
449
451
  source_code_uri: https://github.com/presidentbeef/brakeman
450
452
  wiki_uri: https://github.com/presidentbeef/brakeman/wiki
451
- post_install_message:
453
+ post_install_message:
452
454
  rdoc_options: []
453
455
  require_paths:
454
456
  - lib
@@ -463,8 +465,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
463
465
  - !ruby/object:Gem::Version
464
466
  version: '0'
465
467
  requirements: []
466
- rubygems_version: 3.1.2
467
- signing_key:
468
+ rubygems_version: 3.3.3
469
+ signing_key:
468
470
  specification_version: 4
469
471
  summary: Security vulnerability scanner for Ruby on Rails.
470
472
  test_files: []