brakeman-lib 4.2.1 → 4.3.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: 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.