brakeman-lib 5.3.1 → 5.4.1

Sign up to get free protection for your applications and to get access to all the features.
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: []