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 +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.
|