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 +4 -4
- data/CHANGES.md +29 -0
- data/README.md +2 -2
- data/lib/brakeman.rb +4 -2
- data/lib/brakeman/checks/base_check.rb +4 -1
- data/lib/brakeman/checks/check_execute.rb +10 -3
- data/lib/brakeman/checks/check_link_to_href.rb +3 -2
- data/lib/brakeman/checks/check_sanitize_methods.rb +2 -8
- data/lib/brakeman/checks/check_sql.rb +4 -2
- data/lib/brakeman/codeclimate/engine_configuration.rb +2 -2
- data/lib/brakeman/file_parser.rb +5 -1
- data/lib/brakeman/options.rb +9 -1
- data/lib/brakeman/parsers/template_parser.rb +6 -2
- data/lib/brakeman/processors/alias_processor.rb +107 -101
- data/lib/brakeman/processors/controller_alias_processor.rb +5 -2
- data/lib/brakeman/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/brakeman/processors/lib/find_all_calls.rb +18 -5
- data/lib/brakeman/processors/library_processor.rb +1 -1
- data/lib/brakeman/report/pager.rb +3 -1
- data/lib/brakeman/util.rb +18 -0
- data/lib/brakeman/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b66e4adfac1e60bdf4c0f7ba2962202120f879e0b15a456e8c6672ecd1320ad4
|
4
|
+
data.tar.gz: 9517cdd9c93e736e9fb8d4cf29c4aab8c9c232fc4166f52111b653982213e2e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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 -
|
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
|
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, :
|
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
|
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
|
data/lib/brakeman/file_parser.rb
CHANGED
@@ -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
|
data/lib/brakeman/options.rb
CHANGED
@@ -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
|
-
|
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.
|
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(
|
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 =
|
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
|
-
|
207
|
-
joined.line(exp.line)
|
208
|
-
exp = joined
|
207
|
+
exp = join_arrays(target, first_arg, exp)
|
209
208
|
elsif string? first_arg
|
210
|
-
|
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
|
-
|
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
|
-
|
245
|
-
exp = temp_exp if temp_exp
|
217
|
+
exp = process_array_access(target, exp.args, exp)
|
246
218
|
elsif hash? target
|
247
|
-
|
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.
|
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.
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
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
|
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] =
|
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] =
|
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
|
-
|
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
|
-
|
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
|
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
|
data/lib/brakeman/version.rb
CHANGED
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.
|
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-
|
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.
|
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.
|