brakeman-lib 4.2.1 → 4.3.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: 7070db1a411e9196bbb90fe7a34209ecd8cf15210e5fa21be1442c668e8f00d0
4
- data.tar.gz: 940132f27d3803e1fa445d51c869cf426a712d065892f49b6e138222dc66ba71
3
+ metadata.gz: b66e4adfac1e60bdf4c0f7ba2962202120f879e0b15a456e8c6672ecd1320ad4
4
+ data.tar.gz: 9517cdd9c93e736e9fb8d4cf29c4aab8c9c232fc4166f52111b653982213e2e2
5
5
  SHA512:
6
- metadata.gz: dfbb4f4f11b1e8b00a206a6185e3e58862061f954a43cdf611fb38d6bd97b0e04ef6439adcc68b6ea6dc1675b5853f8a9af03c378be3d7cc6d9f4f49a61f5d5d
7
- data.tar.gz: 77cb923ae34ee9a094200e1dc08a120ca001b4f646907fcb896bc147694041c989bfea7dc3023d876c0ee1461bedd5dc35cae53b94ef36ee6971baff980ceaae
6
+ metadata.gz: 6cb3584795f515314616a5f2e1116feba305333672e599b1b4f9d492fe40895f3d6067e13ad6431f5fc5fd3dfce636fbe39644e2f366b61a9ab12b4030ec4592
7
+ data.tar.gz: 455f61c1c33200355090cf541e57109416199b6bfe898c69cb972b537374cf315d485422b2065464a4a7519c5bb0e7f446d0f10356a959e9e0c8d776fcc5fb13
data/CHANGES.md CHANGED
@@ -1,3 +1,32 @@
1
+ # 4.3.1
2
+
3
+ * Ignore `Object#freeze`, use the target instead
4
+ * Ignore `foreign_key` calls in SQL
5
+ * Handle `included` calls outside of classes/modules
6
+ * Add `:BRAKEMAN_SAFE_LITERAL` to represent known-safe literals
7
+ * Handle `Array#map` and `Array#each` over literal arrays
8
+ * Use safe literal when accessing literal hash with unknown key
9
+ * Avoid deprecated use of ERB in Ruby 2.6 (Koichi ITO)
10
+ * Allow `symbolize_keys` to be called on `params` in SQL (Jacob Evelyn)
11
+ * Improve handling of conditionals in shell commands (Jacob Evenlyn)
12
+ * Fix error when setting line number in implicit renders
13
+
14
+ # 4.3.0
15
+
16
+ * Check exec-type calls even if they are targets
17
+ * Convert `Array#join` to string interpolation
18
+ * `BaseCheck#include_interp?` should return first string interpolation
19
+ * Add `--parser-timeout` option
20
+ * Track parent calls in CallIndex
21
+ * Warn about dangerous `link_to` href with `sanitize()`
22
+ * Ignore `params#to_h` and `params#to_hash` in SQL checks
23
+ * Change "".freeze to just ""
24
+ * Ignore `Process.pid` in system calls
25
+ * Index Kernel#\` calls even if they are targets
26
+ * Code Climate: omit leading dot from `only_files` (Todd Mazierski)
27
+ * `--color` can be used to force color output
28
+ * Fix reported line numbers for CVE-2018-3741 and CVE-2018-8048
29
+
1
30
  # 4.2.1
2
31
 
3
32
  * Add warning for CVE-2018-3741
data/README.md CHANGED
@@ -82,9 +82,9 @@ If Brakeman is running a bit slow, try
82
82
 
83
83
  This will disable some features, but will probably be much faster (currently it is the same as `--skip-libs --no-branching`). *WARNING*: This may cause Brakeman to miss some vulnerabilities.
84
84
 
85
- By default, Brakeman will return 0 as an exit code unless something went very wrong. To return an error code when warnings were found:
85
+ By default, Brakeman will return a non-zero exit code if any security warnings are found or scanning errors are encountered. To disable this:
86
86
 
87
- brakeman -z
87
+ brakeman --no-exit-on-warn --no-exit-on-error
88
88
 
89
89
  To skip certain files or directories that Brakeman may have trouble parsing, use:
90
90
 
data/lib/brakeman.rb CHANGED
@@ -51,6 +51,7 @@ module Brakeman
51
51
  # * :output_files - files for output
52
52
  # * :output_formats - formats for output (:to_s, :to_tabs, :to_csv, :to_html)
53
53
  # * :parallel_checks - run checks in parallel (default: true)
54
+ # * :parser_timeout - set timeout for parsing an individual file (default: 10 seconds)
54
55
  # * :print_report - if no output file specified, print to stdout (default: false)
55
56
  # * :quiet - suppress most messages (default: true)
56
57
  # * :rails3 - force Rails 3 mode (automatic)
@@ -174,6 +175,7 @@ module Brakeman
174
175
  :output_color => true,
175
176
  :pager => true,
176
177
  :parallel_checks => true,
178
+ :parser_timeout => 10,
177
179
  :relative_path => false,
178
180
  :report_progress => true,
179
181
  :safe_methods => Set.new,
@@ -376,7 +378,7 @@ module Brakeman
376
378
 
377
379
  def self.write_report_to_files tracker, output_files
378
380
  require 'fileutils'
379
- tracker.options[:output_color] = false
381
+ tracker.options[:output_color] = false unless tracker.options[:output_color] == :force
380
382
 
381
383
  output_files.each_with_index do |output_file, idx|
382
384
  dir = File.dirname(output_file)
@@ -393,7 +395,7 @@ module Brakeman
393
395
  private_class_method :write_report_to_files
394
396
 
395
397
  def self.write_report_to_formats tracker, output_formats
396
- unless $stdout.tty?
398
+ unless $stdout.tty? or tracker.options[:output_color] == :force
397
399
  tracker.options[:output_color] = false
398
400
  end
399
401
 
@@ -119,7 +119,10 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
119
119
 
120
120
  #Does not actually process string interpolation, but notes that it occurred.
121
121
  def process_dstr exp
122
- @string_interp = Match.new(:interp, exp)
122
+ unless @string_interp # don't overwrite existing value
123
+ @string_interp = Match.new(:interp, exp)
124
+ end
125
+
123
126
  process_default exp
124
127
  end
125
128
 
@@ -15,7 +15,8 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
15
15
 
16
16
  SAFE_VALUES = [s(:const, :RAILS_ROOT),
17
17
  s(:call, s(:const, :Rails), :root),
18
- s(:call, s(:const, :Rails), :env)]
18
+ s(:call, s(:const, :Rails), :env),
19
+ s(:call, s(:const, :Process), :pid)]
19
20
 
20
21
  SHELL_ESCAPES = [:escape, :shellescape, :join]
21
22
 
@@ -32,7 +33,7 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
32
33
  calls = tracker.find_call :targets => [:IO, :Open3, :Kernel, :'POSIX::Spawn', :Process, nil],
33
34
  :methods => [:capture2, :capture2e, :capture3, :exec, :pipeline, :pipeline_r,
34
35
  :pipeline_rw, :pipeline_start, :pipeline_w, :popen, :popen2, :popen2e,
35
- :popen3, :spawn, :syscall, :system]
36
+ :popen3, :spawn, :syscall, :system], :nested => true
36
37
 
37
38
  Brakeman.debug "Processing system calls"
38
39
  calls.each do |result|
@@ -142,7 +143,13 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
142
143
  next if SAFE_VALUES.include? e
143
144
  next if shell_escape? e
144
145
 
145
- if node_type? e, :or, :evstr, :dstr
146
+ if node_type? e, :if
147
+ # If we're in a conditional, evaluate the `then` and `else` clauses to
148
+ # see if they're dangerous.
149
+ if res = dangerous?(e.values[1..-1])
150
+ return res
151
+ end
152
+ elsif node_type? e, :or, :evstr, :dstr
146
153
  if res = dangerous?(e)
147
154
  return res
148
155
  end
@@ -71,14 +71,15 @@ class Brakeman::CheckLinkToHref < Brakeman::CheckLinkTo
71
71
  end
72
72
  end
73
73
 
74
+ CHECK_INSIDE_METHODS = [:url_for, :h, :sanitize]
75
+
74
76
  def check_argument? url_arg
75
77
  return unless call? url_arg
76
78
 
77
79
  target = url_arg.target
78
80
  method = url_arg.method
79
81
 
80
- method == :url_for or
81
- method == :h or
82
+ CHECK_INSIDE_METHODS.include? method or
82
83
  cgi_escaped? target, method
83
84
  end
84
85
 
@@ -46,12 +46,6 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
46
46
 
47
47
  message = "Rails #{rails_version} has a vulnerability in #{method}: upgrade to #{@fix_version} or patch"
48
48
 
49
- if include_user_input? result[:call]
50
- confidence = :high
51
- else
52
- confidence = :medium
53
- end
54
-
55
49
  warn :result => result,
56
50
  :warning_type => "Cross-Site Scripting",
57
51
  :warning_code => code,
@@ -87,7 +81,7 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
87
81
  warn :warning_type => "Cross-Site Scripting",
88
82
  :warning_code => :CVE_2018_8048,
89
83
  :message => message,
90
- :gem_info => gemfile_or_environment,
84
+ :gem_info => gemfile_or_environment(:loofah),
91
85
  :confidence => confidence,
92
86
  :link_path => "https://github.com/flavorjones/loofah/issues/144"
93
87
  end
@@ -111,7 +105,7 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
111
105
  warn :warning_type => "Cross-Site Scripting",
112
106
  :warning_code => cve.tr('-', '_').to_sym,
113
107
  :message => message,
114
- :gem_info => gemfile_or_environment,
108
+ :gem_info => gemfile_or_environment(:'rails-html-sanitizer'),
115
109
  :confidence => confidence,
116
110
  :link_path => link
117
111
  end
@@ -290,7 +290,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
290
290
  end
291
291
 
292
292
  if request_value? arg
293
- unless call? arg and params? arg.target and [:permit, :slice].include? arg.method
293
+ unless call? arg and params? arg.target and [:permit, :slice, :to_h, :to_hash, :symbolize_keys].include? arg.method
294
294
  # Model.where(params[:where])
295
295
  arg
296
296
  end
@@ -404,6 +404,8 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
404
404
  nil
405
405
  elsif call? value and value.method == :to_s
406
406
  unsafe_string_interp? value.target
407
+ elsif call? value and safe_literal_target? value
408
+ nil
407
409
  else
408
410
  case value.node_type
409
411
  when :or
@@ -576,7 +578,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
576
578
  :sanitize_sql_for_assignment, :sanitize_sql_for_conditions, :sanitize_sql_hash,
577
579
  :sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions,
578
580
  :to_sql, :sanitize, :primary_key, :table_name_prefix, :table_name_suffix,
579
- :where_values_hash
581
+ :where_values_hash, :foreign_key
580
582
  ]
581
583
 
582
584
  def safe_value? exp
@@ -87,9 +87,9 @@ module Brakeman
87
87
 
88
88
  def stripped_include_path(prefix, subprefixes, path)
89
89
  if path.start_with?(prefix)
90
- path.sub(%r{^#{prefix}/?}, "./")
90
+ path.sub(%r{^#{prefix}/?}, "/")
91
91
  elsif subprefixes.any? { |subprefix| path =~ %r{^#{subprefix}/?$} }
92
- "./"
92
+ "/"
93
93
  end
94
94
  end
95
95
  end
@@ -7,6 +7,7 @@ module Brakeman
7
7
 
8
8
  def initialize tracker, app_tree
9
9
  @tracker = tracker
10
+ @timeout = @tracker.options[:parser_timeout]
10
11
  @app_tree = app_tree
11
12
  @file_list = {}
12
13
  end
@@ -33,10 +34,13 @@ module Brakeman
33
34
  def parse_ruby input, path
34
35
  begin
35
36
  Brakeman.debug "Parsing #{path}"
36
- RubyParser.new.parse input, path
37
+ RubyParser.new.parse input, path, @timeout
37
38
  rescue Racc::ParseError => e
38
39
  @tracker.error e, "Could not parse #{path}"
39
40
  nil
41
+ rescue Timeout::Error => e
42
+ @tracker.error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout"), caller
43
+ nil
40
44
  rescue => e
41
45
  @tracker.error e.exception(e.message + "\nWhile processing #{path}"), e.backtrace
42
46
  nil
@@ -127,6 +127,10 @@ module Brakeman::Options
127
127
  options[:branch_limit] = limit
128
128
  end
129
129
 
130
+ opts.on "--parser-timeout SECONDS", Integer, "Set parse timeout (Default: 10)" do |timeout|
131
+ options[:parser_timeout] = timeout
132
+ end
133
+
130
134
  opts.on "-r", "--report-direct", "Only report direct use of untrusted data" do |option|
131
135
  options[:check_arguments] = !option
132
136
  end
@@ -229,7 +233,11 @@ module Brakeman::Options
229
233
  end
230
234
 
231
235
  opts.on "--[no-]color", "Use ANSI colors in report (Default)" do |color|
232
- options[:output_color] = color
236
+ if color
237
+ options[:output_color] = :force
238
+ else
239
+ options[:output_color] = color
240
+ end
233
241
  end
234
242
 
235
243
  opts.on "-m", "--routes", "Report controller information" do
@@ -58,7 +58,11 @@ module Brakeman
58
58
  Brakeman::ScannerErubis.new(text, :filename => path).src
59
59
  else
60
60
  require 'erb'
61
- src = ERB.new(text, nil, path).src
61
+ src = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
62
+ ERB.new(text, trim_mode: path).src
63
+ else
64
+ ERB.new(text, nil, path).src
65
+ end
62
66
  src.sub!(/^#.*\n/, '') if Brakeman::Scanner::RUBY_1_9
63
67
  src
64
68
  end
@@ -93,7 +97,7 @@ module Brakeman
93
97
  end
94
98
 
95
99
  def self.parse_inline_erb tracker, text
96
- fp = Brakeman::FileParser.new(nil, nil)
100
+ fp = Brakeman::FileParser.new(tracker, nil)
97
101
  tp = self.new(tracker, fp)
98
102
  src = tp.parse_erb '_inline_', text
99
103
  type = tp.erubis? ? :erubis : :erb
@@ -2,6 +2,7 @@ require 'brakeman/util'
2
2
  require 'ruby_parser/bm_sexp_processor'
3
3
  require 'brakeman/processors/lib/processor_helper'
4
4
  require 'brakeman/processors/lib/safe_call_helper'
5
+ require 'brakeman/processors/lib/call_conversion_helper'
5
6
 
6
7
  #Returns an s-expression with aliases replaced with their value.
7
8
  #This does not preserve semantics (due to side effects, etc.), but it makes
@@ -10,6 +11,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
10
11
  include Brakeman::ProcessorHelper
11
12
  include Brakeman::SafeCallHelper
12
13
  include Brakeman::Util
14
+ include Brakeman::CallConversionHelper
13
15
 
14
16
  attr_reader :result, :tracker
15
17
 
@@ -122,7 +124,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
122
124
  end
123
125
 
124
126
  if hash? t
125
- if v = hash_access(t, exp.first_arg)
127
+ if v = process_hash_access(t, exp.first_arg)
126
128
  v.deep_clone(exp.line)
127
129
  else
128
130
  case t.node_type
@@ -197,55 +199,24 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
197
199
  return Sexp.new(:false)
198
200
  end
199
201
 
200
-
201
202
  #See if it is possible to simplify some basic cases
202
203
  #of addition/concatenation.
203
204
  case method
204
205
  when :+
205
206
  if array? target and array? first_arg
206
- joined = join_arrays target, first_arg
207
- joined.line(exp.line)
208
- exp = joined
207
+ exp = join_arrays(target, first_arg, exp)
209
208
  elsif string? first_arg
210
- if string? target # "blah" + "blah"
211
- joined = join_strings target, first_arg
212
- joined.line(exp.line)
213
- exp = joined
214
- elsif call? target and target.method == :+ and string? target.first_arg
215
- joined = join_strings target.first_arg, first_arg
216
- joined.line(exp.line)
217
- target.first_arg = joined
218
- exp = target
219
- end
209
+ exp = join_strings(target, first_arg, exp)
220
210
  elsif number? first_arg
221
- if number? target
222
- exp = Sexp.new(:lit, target.value + first_arg.value)
223
- elsif call? target and target.method == :+ and number? target.first_arg
224
- target.first_arg = Sexp.new(:lit, target.first_arg.value + first_arg.value)
225
- exp = target
226
- end
227
- end
228
- when :-
229
- if number? target and number? first_arg
230
- exp = Sexp.new(:lit, target.value - first_arg.value)
231
- end
232
- when :*
233
- if number? target and number? first_arg
234
- exp = Sexp.new(:lit, target.value * first_arg.value)
235
- end
236
- when :/
237
- if number? target and number? first_arg
238
- unless first_arg.value == 0 and not target.value.is_a? Float
239
- exp = Sexp.new(:lit, target.value / first_arg.value)
240
- end
211
+ exp = math_op(:+, target, first_arg, exp)
241
212
  end
213
+ when :-, :*, :/
214
+ exp = math_op(method, target, first_arg, exp)
242
215
  when :[]
243
216
  if array? target
244
- temp_exp = process_array_access target, exp.args
245
- exp = temp_exp if temp_exp
217
+ exp = process_array_access(target, exp.args, exp)
246
218
  elsif hash? target
247
- temp_exp = process_hash_access target, first_arg
248
- exp = temp_exp if temp_exp
219
+ exp = process_hash_access(target, first_arg, exp)
249
220
  end
250
221
  when :merge!, :update
251
222
  if hash? target and hash? first_arg
@@ -287,37 +258,115 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
287
258
  if array? target and first_arg.nil? and sexp? target[1]
288
259
  exp = target[1]
289
260
  end
261
+ when :freeze
262
+ unless target.nil?
263
+ exp = target
264
+ end
265
+ when :join
266
+ if array? target and target.length > 2 and (string? first_arg or first_arg.nil?)
267
+ exp = process_array_join(target, first_arg)
268
+ end
290
269
  end
291
270
 
292
271
  exp
293
272
  end
294
273
 
274
+ # Painful conversion of Array#join into string interpolation
275
+ def process_array_join array, join_str
276
+ result = s()
277
+
278
+ join_value = if string? join_str
279
+ join_str.value
280
+ else
281
+ nil
282
+ end
283
+
284
+ array[1..-2].each do |e|
285
+ result << join_item(e, join_value)
286
+ end
287
+
288
+ result << join_item(array.last, nil)
289
+
290
+ # Combine the strings at the beginning because that's what RubyParser does
291
+ combined_first = ""
292
+ result.each do |e|
293
+ if string? e
294
+ combined_first << e.value
295
+ elsif e.is_a? String
296
+ combined_first << e
297
+ else
298
+ break
299
+ end
300
+ end
301
+
302
+ # Remove the strings at the beginning
303
+ result.reject! do |e|
304
+ if e.is_a? String or string? e
305
+ true
306
+ else
307
+ break
308
+ end
309
+ end
310
+
311
+ result.unshift combined_first
312
+
313
+ # Have to fix up strings that follow interpolation
314
+ result.reduce(s(:dstr)) do |memo, e|
315
+ if string? e and node_type? memo.last, :evstr
316
+ e.value = "#{join_value}#{e.value}"
317
+ elsif join_value and node_type? memo.last, :evstr and node_type? e, :evstr
318
+ memo << s(:str, join_value)
319
+ end
320
+
321
+ memo << e
322
+ end
323
+ end
324
+
325
+ def join_item item, join_value
326
+ if item.is_a? String
327
+ "#{item}#{join_value}"
328
+ elsif string? item or symbol? item or number? item
329
+ s(:str, "#{item.value}#{join_value}")
330
+ else
331
+ s(:evstr, item)
332
+ end
333
+ end
334
+
295
335
  def process_iter exp
296
336
  @exp_context.push exp
297
337
  exp[1] = process exp.block_call
298
338
  if array_detect_all_literals? exp[1]
299
- return exp.block_call.target[1]
339
+ return safe_literal(exp.line)
300
340
  end
301
341
 
302
342
  @exp_context.pop
303
343
 
304
344
  env.scope do
305
- exp.block_args.each do |e|
306
- #Force block arg(s) to be local
307
- if node_type? e, :lasgn
308
- env.current[Sexp.new(:lvar, e.lhs)] = Sexp.new(:lvar, e.lhs)
309
- elsif node_type? e, :kwarg
310
- env.current[Sexp.new(:lvar, e[1])] = e[2]
311
- elsif node_type? e, :masgn, :shadow
312
- e[1..-1].each do |var|
313
- local = Sexp.new(:lvar, var)
345
+ call = exp.block_call
346
+ block_args = exp.block_args
347
+
348
+ if call? call and [:each, :map].include? call.method and all_literals? call.target and block_args.length == 2 and block_args.last.is_a? Symbol
349
+ # Iterating over an array of all literal values
350
+ local = Sexp.new(:lvar, block_args.last)
351
+ env.current[local] = safe_literal(exp.line)
352
+ else
353
+ block_args.each do |e|
354
+ #Force block arg(s) to be local
355
+ if node_type? e, :lasgn
356
+ env.current[Sexp.new(:lvar, e.lhs)] = Sexp.new(:lvar, e.lhs)
357
+ elsif node_type? e, :kwarg
358
+ env.current[Sexp.new(:lvar, e[1])] = e[2]
359
+ elsif node_type? e, :masgn, :shadow
360
+ e[1..-1].each do |var|
361
+ local = Sexp.new(:lvar, var)
362
+ env.current[local] = local
363
+ end
364
+ elsif e.is_a? Symbol
365
+ local = Sexp.new(:lvar, e)
314
366
  env.current[local] = local
367
+ else
368
+ raise "Unexpected value in block args: #{e.inspect}"
315
369
  end
316
- elsif e.is_a? Symbol
317
- local = Sexp.new(:lvar, e)
318
- env.current[local] = local
319
- else
320
- raise "Unexpected value in block args: #{e.inspect}"
321
370
  end
322
371
  end
323
372
 
@@ -647,18 +696,14 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
647
696
  def array_include_all_literals? exp
648
697
  call? exp and
649
698
  exp.method == :include? and
650
- node_type? exp.target, :array and
651
- exp.target.length > 1 and
652
- exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
699
+ all_literals? exp.target
653
700
  end
654
701
 
655
702
  def array_detect_all_literals? exp
656
703
  call? exp and
657
704
  [:detect, :find].include? exp.method and
658
- node_type? exp.target, :array and
659
- exp.target.length > 1 and
660
705
  exp.first_arg.nil? and
661
- exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
706
+ all_literals? exp.target
662
707
  end
663
708
 
664
709
  #Sets @inside_if = true
@@ -699,12 +744,12 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
699
744
  # set x to "a" inside the true branch
700
745
  var = condition.first_arg
701
746
  previous_value = env.current[var]
702
- env.current[var] = condition.target[1]
747
+ env.current[var] = safe_literal(var.line)
703
748
  exp[branch_index] = process_if_branch branch
704
749
  env.current[var] = previous_value
705
750
  elsif i == 1 and array_include_all_literals? condition and early_return? branch
706
751
  var = condition.first_arg
707
- env.current[var] = condition.target[1]
752
+ env.current[var] = safe_literal(var.line)
708
753
  exp[branch_index] = process_if_branch branch
709
754
  else
710
755
  exp[branch_index] = process_if_branch branch
@@ -843,45 +888,6 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
843
888
  exp.or_depth >= @or_depth_limit
844
889
  end
845
890
 
846
- #Process single integer access to an array.
847
- #
848
- #Returns the value inside the array, if possible.
849
- def process_array_access target, args
850
- if args.length == 1 and integer? args.first
851
- index = args.first.value
852
-
853
- #Have to do this because first element is :array and we have to skip it
854
- target[1..-1][index]
855
- else
856
- nil
857
- end
858
- end
859
-
860
- #Process hash access by returning the value associated
861
- #with the given argument.
862
- def process_hash_access target, index
863
- hash_access(target, index)
864
- end
865
-
866
- #Join two array literals into one.
867
- def join_arrays array1, array2
868
- result = Sexp.new(:array)
869
- result.concat array1[1..-1]
870
- result.concat array2[1..-1]
871
- end
872
-
873
- #Join two string literals into one.
874
- def join_strings string1, string2
875
- result = Sexp.new(:str)
876
- result.value = string1.value + string2.value
877
-
878
- if result.value.length > 50
879
- string1
880
- else
881
- result
882
- end
883
- end
884
-
885
891
  # Change x.send(:y, 1) to x.y(1)
886
892
  def collapse_send_call exp, first_arg
887
893
  # Handle try(&:id)
@@ -179,8 +179,11 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
179
179
  # method as the line number
180
180
  if line.nil? and controller = @tracker.controllers[@current_class]
181
181
  if meth = controller.get_method(@current_method)
182
- line = meth[:src] && meth[:src].last && meth[:src].last.line
183
- line += 1
182
+ if line = meth[:src] && meth[:src].last && meth[:src].last.line
183
+ line += 1
184
+ else
185
+ line = 1
186
+ end
184
187
  end
185
188
  end
186
189
 
@@ -0,0 +1,90 @@
1
+ module Brakeman
2
+ module CallConversionHelper
3
+ def all_literals? exp, expected_type = :array
4
+ node_type? exp, expected_type and
5
+ exp.length > 1 and
6
+ exp.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
7
+ end
8
+
9
+ # Join two array literals into one.
10
+ def join_arrays lhs, rhs, original_exp = nil
11
+ if array? lhs and array? rhs
12
+ result = Sexp.new(:array).line(lhs.line)
13
+ result.concat lhs[1..-1]
14
+ result.concat rhs[1..-1]
15
+ result
16
+ else
17
+ original_exp
18
+ end
19
+ end
20
+
21
+ # Join two string literals into one.
22
+ def join_strings lhs, rhs, original_exp = nil
23
+ if string? lhs and string? rhs
24
+ result = Sexp.new(:str).line(lhs.line)
25
+ result.value = lhs.value + rhs.value
26
+
27
+ if result.value.length > 50
28
+ # Avoid gigantic strings
29
+ lhs
30
+ else
31
+ result
32
+ end
33
+ elsif call? lhs and lhs.method == :+ and string? lhs.first_arg and string? rhs
34
+ joined = join_strings lhs.first_arg, rhs
35
+ lhs.first_arg = joined
36
+ lhs
37
+ elsif safe_literal? lhs or safe_literal? rhs
38
+ safe_literal(lhs.line)
39
+ else
40
+ original_exp
41
+ end
42
+ end
43
+
44
+ def math_op op, lhs, rhs, original_exp = nil
45
+ if number? lhs and number? rhs
46
+ if op == :/ and rhs.value == 0 and not lhs.value.is_a? Float
47
+ # Avoid division by zero
48
+ return original_exp
49
+ else
50
+ value = lhs.value.send(op, rhs.value)
51
+ Sexp.new(:lit, value).line(lhs.line)
52
+ end
53
+ elsif call? lhs and lhs.method == :+ and number? lhs.first_arg and number? rhs
54
+ # (x + 1) + 2 -> (x + 3)
55
+ lhs.first_arg = Sexp.new(:lit, lhs.first_arg.value + rhs.value).line(lhs.first_arg.line)
56
+ lhs
57
+ elsif safe_literal? lhs or safe_literal? rhs
58
+ safe_literal(lhs.line)
59
+ else
60
+ original_exp
61
+ end
62
+ end
63
+
64
+ # Process single integer access to an array.
65
+ #
66
+ # Returns the value inside the array, if possible.
67
+ def process_array_access array, args, original_exp = nil
68
+ if args.length == 1 and integer? args.first
69
+ index = args.first.value
70
+
71
+ #Have to do this because first element is :array and we have to skip it
72
+ array[1..-1][index] or original_exp
73
+ else
74
+ original_exp
75
+ end
76
+ end
77
+
78
+ # Process hash access by returning the value associated
79
+ # with the given argument.
80
+ def process_hash_access hash, index, original_exp = nil
81
+ if value = hash_access(hash, index)
82
+ value # deep_clone?
83
+ elsif all_literals? hash, :hash
84
+ safe_literal(hash.line)
85
+ else
86
+ original_exp
87
+ end
88
+ end
89
+ end
90
+ end
@@ -19,6 +19,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
19
19
  @current_method = opts[:method]
20
20
  @current_template = opts[:template]
21
21
  @current_file = opts[:file]
22
+ @current_call = nil
22
23
  process exp
23
24
  end
24
25
 
@@ -111,7 +112,8 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
111
112
  :method => method_name,
112
113
  :call => exp,
113
114
  :nested => false,
114
- :location => make_location }
115
+ :location => make_location,
116
+ :parent => @current_call }
115
117
  end
116
118
 
117
119
  #Gets the target of a call as a Symbol
@@ -189,7 +191,7 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
189
191
  def create_call_hash exp
190
192
  target = get_target exp.target
191
193
 
192
- if call? target
194
+ if call? target or node_type? target, :dxstr # need to index `` even if target of a call
193
195
  already_in_target = @in_target
194
196
  @in_target = true
195
197
  process target
@@ -199,13 +201,24 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
199
201
  end
200
202
 
201
203
  method = exp.method
202
- process_call_args exp
203
204
 
204
- { :target => target,
205
+ call_hash = {
206
+ :target => target,
205
207
  :method => method,
206
208
  :call => exp,
207
209
  :nested => @in_target,
208
210
  :chain => get_chain(exp),
209
- :location => make_location }
211
+ :location => make_location,
212
+ :parent => @current_call
213
+ }
214
+
215
+ old_parent = @current_call
216
+ @current_call = call_hash
217
+
218
+ process_call_args exp
219
+
220
+ @current_call = old_parent
221
+
222
+ call_hash
210
223
  end
211
224
  end
@@ -64,7 +64,7 @@ class Brakeman::LibraryProcessor < Brakeman::BaseProcessor
64
64
  res = process_default exp
65
65
 
66
66
  if node_type? res, :iter and call? exp.block_call # sometimes this changes after processing
67
- if exp.block_call.method == :included
67
+ if exp.block_call.method == :included and (@current_module or @current_class)
68
68
  (@current_module || @current_class).options[:included] = res.block
69
69
  end
70
70
  end
@@ -102,7 +102,9 @@ module Brakeman
102
102
  end
103
103
 
104
104
  def set_color
105
- unless @tracker and less_options.include? "-R "
105
+ return unless @tracker
106
+
107
+ unless less_options.include? "-R " or @tracker.options[:output_color] == :force
106
108
  @tracker.options[:output_color] = false
107
109
  end
108
110
  end
data/lib/brakeman/util.rb CHANGED
@@ -26,6 +26,8 @@ module Brakeman::Util
26
26
 
27
27
  ALL_COOKIES = Set[COOKIES, REQUEST_COOKIES]
28
28
 
29
+ SAFE_LITERAL = s(:lit, :BRAKEMAN_SAFE_LITERAL)
30
+
29
31
  #Convert a string from "something_like_this" to "SomethingLikeThis"
30
32
  #
31
33
  #Taken from ActiveSupport.
@@ -307,6 +309,22 @@ module Brakeman::Util
307
309
  call
308
310
  end
309
311
 
312
+ def safe_literal line = nil
313
+ s(:lit, :BRAKEMAN_SAFE_LITERAL).line(line || 0)
314
+ end
315
+
316
+ def safe_literal? exp
317
+ exp == SAFE_LITERAL
318
+ end
319
+
320
+ def safe_literal_target? exp
321
+ if call? exp
322
+ safe_literal_target? exp.target
323
+ else
324
+ safe_literal? exp
325
+ end
326
+ end
327
+
310
328
  def rails_version
311
329
  @tracker.config.rails_version
312
330
  end
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "4.2.1"
2
+ Version = "4.3.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman-lib
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.1
4
+ version: 4.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
11
  - brakeman-public_cert.pem
12
- date: 2018-03-24 00:00:00.000000000 Z
12
+ date: 2018-06-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
@@ -297,6 +297,7 @@ files:
297
297
  - lib/brakeman/processors/gem_processor.rb
298
298
  - lib/brakeman/processors/haml_template_processor.rb
299
299
  - lib/brakeman/processors/lib/basic_processor.rb
300
+ - lib/brakeman/processors/lib/call_conversion_helper.rb
300
301
  - lib/brakeman/processors/lib/find_all_calls.rb
301
302
  - lib/brakeman/processors/lib/find_call.rb
302
303
  - lib/brakeman/processors/lib/find_return_value.rb
@@ -380,7 +381,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
380
381
  version: '0'
381
382
  requirements: []
382
383
  rubyforge_project:
383
- rubygems_version: 2.7.3
384
+ rubygems_version: 2.7.6
384
385
  signing_key:
385
386
  specification_version: 4
386
387
  summary: Security vulnerability scanner for Ruby on Rails.