brakeman-lib 4.5.1 → 4.6.0
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 +16 -0
- data/README.md +0 -1
- data/lib/brakeman/call_index.rb +54 -15
- data/lib/brakeman/checks/base_check.rb +27 -46
- data/lib/brakeman/checks/check_cookie_serialization.rb +22 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +3 -3
- data/lib/brakeman/checks/check_deserialize.rb +3 -6
- data/lib/brakeman/checks/check_file_access.rb +7 -1
- data/lib/brakeman/checks/check_header_dos.rb +2 -2
- data/lib/brakeman/checks/check_i18n_xss.rb +2 -2
- data/lib/brakeman/checks/check_jruby_xml.rb +2 -2
- data/lib/brakeman/checks/check_json_parsing.rb +2 -2
- data/lib/brakeman/checks/check_mime_type_dos.rb +2 -2
- data/lib/brakeman/checks/check_nested_attributes_bypass.rb +1 -1
- data/lib/brakeman/checks/check_reverse_tabnabbing.rb +54 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +2 -2
- data/lib/brakeman/checks/check_session_settings.rb +5 -2
- data/lib/brakeman/checks/check_xml_dos.rb +2 -2
- data/lib/brakeman/checks/check_yaml_parsing.rb +10 -18
- data/lib/brakeman/file_parser.rb +4 -8
- data/lib/brakeman/file_path.rb +14 -0
- data/lib/brakeman/processor.rb +1 -1
- data/lib/brakeman/processors/alias_processor.rb +5 -1
- data/lib/brakeman/processors/controller_processor.rb +4 -4
- data/lib/brakeman/processors/gem_processor.rb +10 -2
- data/lib/brakeman/processors/haml_template_processor.rb +1 -1
- data/lib/brakeman/processors/lib/find_all_calls.rb +27 -4
- data/lib/brakeman/processors/lib/find_call.rb +3 -64
- data/lib/brakeman/processors/template_processor.rb +10 -6
- data/lib/brakeman/rescanner.rb +4 -0
- data/lib/brakeman/tracker.rb +26 -2
- data/lib/brakeman/tracker/config.rb +39 -15
- data/lib/brakeman/tracker/constants.rb +2 -1
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +4 -0
- data/lib/brakeman/warning_codes.rb +3 -0
- data/lib/ruby_parser/bm_sexp.rb +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9801d95660e71117c4d1578f6413c32063a5da4b3dbfa50ef351a4b6b1cc2b0a
|
|
4
|
+
data.tar.gz: d0fa01b7a2ae2cca03656cdd54197198f4284dec2ae1a38e12a3b0859aae249a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 026dffce4f3530198f8dc2663455ff784d0155ff1b9a69daf2f60e9c2ca85839ea202c3f37d21f2942e70f129002d50073cb850ad023b687344b683c6256df89
|
|
7
|
+
data.tar.gz: 02f647f5fb135d6ff6dc446ac4dc2772fba090710fee36597329647e7d179f5ef407c7825105940063de8bb8dd07afcc73033bd90fc452f028d43cfff4daa773
|
data/CHANGES.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
# 4.6.0
|
|
2
|
+
|
|
3
|
+
* Skip calls to `dup`
|
|
4
|
+
* Add reverse tabnabbing check (Linos Giannopoulos)
|
|
5
|
+
* Better handling of gems with no version declared
|
|
6
|
+
* Warn people that Haml 5 is not fully supported (Jared Beck)
|
|
7
|
+
* Avoid warning about file access with `ActiveStorage::Filename#sanitized` (Tejas Bubane)
|
|
8
|
+
* Update loofah version for fixing CVE-2018-8048 (Markus Nölle)
|
|
9
|
+
* Restore `Warning#relative_path`
|
|
10
|
+
* Add check for cookie serialization with Marshal
|
|
11
|
+
* Index calls in initializers
|
|
12
|
+
* Improve template output handling in conditional branches
|
|
13
|
+
* Avoid assigning `nil` line numbers to `Sexp`s
|
|
14
|
+
* Add special warning code for custom checks
|
|
15
|
+
* Add call matching by regular expression
|
|
16
|
+
|
|
1
17
|
# 4.5.1
|
|
2
18
|
|
|
3
19
|
* Add `Brakeman::FilePath` to represent file paths
|
data/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
[](http://brakemanscanner.org/)
|
|
2
2
|
|
|
3
3
|
[](https://circleci.com/gh/presidentbeef/brakeman)
|
|
4
|
-
[](https://codeclimate.com/github/presidentbeef/brakeman/maintainability)
|
|
5
4
|
[](https://codeclimate.com/github/presidentbeef/brakeman/test_coverage)
|
|
6
5
|
[](https://gitter.im/presidentbeef/brakeman)
|
|
7
6
|
|
data/lib/brakeman/call_index.rb
CHANGED
|
@@ -27,7 +27,7 @@ class Brakeman::CallIndex
|
|
|
27
27
|
if options[:chained]
|
|
28
28
|
return find_chain options
|
|
29
29
|
#Find by narrowest category
|
|
30
|
-
elsif target
|
|
30
|
+
elsif target.is_a? Array and method.is_a? Array
|
|
31
31
|
if target.length > method.length
|
|
32
32
|
calls = filter_by_target calls_by_methods(method), target
|
|
33
33
|
else
|
|
@@ -35,6 +35,12 @@ class Brakeman::CallIndex
|
|
|
35
35
|
calls = filter_by_method calls, method
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
elsif target.is_a? Regexp and method
|
|
39
|
+
calls = filter_by_target(calls_by_method(method), target)
|
|
40
|
+
|
|
41
|
+
elsif method.is_a? Regexp and target
|
|
42
|
+
calls = filter_by_method(calls_by_target(target), method)
|
|
43
|
+
|
|
38
44
|
#Find by target, then by methods, if provided
|
|
39
45
|
elsif target
|
|
40
46
|
calls = calls_by_target target
|
|
@@ -85,6 +91,16 @@ class Brakeman::CallIndex
|
|
|
85
91
|
end
|
|
86
92
|
end
|
|
87
93
|
|
|
94
|
+
def remove_indexes_by_file file
|
|
95
|
+
[@calls_by_method, @calls_by_target].each do |calls_by|
|
|
96
|
+
calls_by.each do |_name, calls|
|
|
97
|
+
calls.delete_if do |call|
|
|
98
|
+
call[:location][:file] == file
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
88
104
|
def index_calls calls
|
|
89
105
|
calls.each do |call|
|
|
90
106
|
@calls_by_method[call[:method]] ||= []
|
|
@@ -116,8 +132,11 @@ class Brakeman::CallIndex
|
|
|
116
132
|
end
|
|
117
133
|
|
|
118
134
|
def calls_by_target target
|
|
119
|
-
|
|
135
|
+
case target
|
|
136
|
+
when Array
|
|
120
137
|
calls_by_targets target
|
|
138
|
+
when Regexp
|
|
139
|
+
calls_by_targets_regex target
|
|
121
140
|
else
|
|
122
141
|
@calls_by_target[target] || []
|
|
123
142
|
end
|
|
@@ -133,10 +152,24 @@ class Brakeman::CallIndex
|
|
|
133
152
|
calls
|
|
134
153
|
end
|
|
135
154
|
|
|
155
|
+
def calls_by_targets_regex targets_regex
|
|
156
|
+
calls = []
|
|
157
|
+
|
|
158
|
+
@calls_by_target.each do |key, value|
|
|
159
|
+
case key
|
|
160
|
+
when String, Symbol
|
|
161
|
+
calls.concat value if key.match targets_regex
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
calls
|
|
166
|
+
end
|
|
167
|
+
|
|
136
168
|
def calls_by_method method
|
|
137
|
-
|
|
169
|
+
case method
|
|
170
|
+
when Array
|
|
138
171
|
calls_by_methods method
|
|
139
|
-
|
|
172
|
+
when Regexp
|
|
140
173
|
calls_by_methods_regex method
|
|
141
174
|
else
|
|
142
175
|
@calls_by_method[method.to_sym] || []
|
|
@@ -156,26 +189,28 @@ class Brakeman::CallIndex
|
|
|
156
189
|
|
|
157
190
|
def calls_by_methods_regex methods_regex
|
|
158
191
|
calls = []
|
|
192
|
+
|
|
159
193
|
@calls_by_method.each do |key, value|
|
|
160
|
-
calls.concat value if key.
|
|
194
|
+
calls.concat value if key.match methods_regex
|
|
161
195
|
end
|
|
162
|
-
calls
|
|
163
|
-
end
|
|
164
196
|
|
|
165
|
-
|
|
166
|
-
@calls_by_target[nil]
|
|
197
|
+
calls
|
|
167
198
|
end
|
|
168
199
|
|
|
169
200
|
def filter calls, key, value
|
|
170
|
-
|
|
201
|
+
case value
|
|
202
|
+
when Array
|
|
171
203
|
values = Set.new value
|
|
172
204
|
|
|
173
205
|
calls.select do |call|
|
|
174
206
|
values.include? call[key]
|
|
175
207
|
end
|
|
176
|
-
|
|
208
|
+
when Regexp
|
|
177
209
|
calls.select do |call|
|
|
178
|
-
call[key]
|
|
210
|
+
case call[key]
|
|
211
|
+
when String, Symbol
|
|
212
|
+
call[key].match value
|
|
213
|
+
end
|
|
179
214
|
end
|
|
180
215
|
else
|
|
181
216
|
calls.select do |call|
|
|
@@ -197,15 +232,19 @@ class Brakeman::CallIndex
|
|
|
197
232
|
end
|
|
198
233
|
|
|
199
234
|
def filter_by_chain calls, target
|
|
200
|
-
|
|
235
|
+
case target
|
|
236
|
+
when Array
|
|
201
237
|
targets = Set.new target
|
|
202
238
|
|
|
203
239
|
calls.select do |call|
|
|
204
240
|
targets.include? call[:chain].first
|
|
205
241
|
end
|
|
206
|
-
|
|
242
|
+
when Regexp
|
|
207
243
|
calls.select do |call|
|
|
208
|
-
call[:chain].first
|
|
244
|
+
case call[:chain].first
|
|
245
|
+
when String, Symbol
|
|
246
|
+
call[:chain].first.match target
|
|
247
|
+
end
|
|
209
248
|
end
|
|
210
249
|
else
|
|
211
250
|
calls.select do |call|
|
|
@@ -44,19 +44,9 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
#Add result to result list, which is used to check for duplicates
|
|
47
|
-
def add_result result
|
|
48
|
-
location
|
|
49
|
-
location =
|
|
50
|
-
location = location.name if location.is_a? Brakeman::Collection
|
|
51
|
-
location = location.to_sym
|
|
52
|
-
|
|
53
|
-
if result.is_a? Hash
|
|
54
|
-
line = result[:call].original_line || result[:call].line
|
|
55
|
-
elsif sexp? result
|
|
56
|
-
line = result.original_line || result.line
|
|
57
|
-
else
|
|
58
|
-
raise ArgumentError
|
|
59
|
-
end
|
|
47
|
+
def add_result result
|
|
48
|
+
location = get_location result
|
|
49
|
+
location, line = get_location result
|
|
60
50
|
|
|
61
51
|
@results << [line, location, result]
|
|
62
52
|
end
|
|
@@ -170,8 +160,9 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
|
170
160
|
@mass_assign_disabled = true
|
|
171
161
|
else
|
|
172
162
|
#Check for ActiveRecord::Base.send(:attr_accessible, nil)
|
|
173
|
-
tracker.
|
|
174
|
-
call = result
|
|
163
|
+
tracker.find_call(target: :"ActiveRecord::Base", method: :attr_accessible).each do |result|
|
|
164
|
+
call = result[:call]
|
|
165
|
+
|
|
175
166
|
if call? call
|
|
176
167
|
if call.first_arg == Sexp.new(:nil)
|
|
177
168
|
@mass_assign_disabled = true
|
|
@@ -180,26 +171,12 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
|
180
171
|
end
|
|
181
172
|
end
|
|
182
173
|
|
|
183
|
-
unless @mass_assign_disabled
|
|
184
|
-
tracker.check_initializers(:"ActiveRecord::Base", :send).each do |result|
|
|
185
|
-
call = result.call
|
|
186
|
-
if call? call
|
|
187
|
-
if call.first_arg == Sexp.new(:lit, :attr_accessible) and call.second_arg == Sexp.new(:nil)
|
|
188
|
-
@mass_assign_disabled = true
|
|
189
|
-
break
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
174
|
unless @mass_assign_disabled
|
|
196
175
|
#Check for
|
|
197
176
|
# class ActiveRecord::Base
|
|
198
177
|
# attr_accessible nil
|
|
199
178
|
# end
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
matches.each do |result|
|
|
179
|
+
tracker.check_initializers([], :attr_accessible).each do |result|
|
|
203
180
|
if result.module == "ActiveRecord" and result.result_class == :Base
|
|
204
181
|
arg = result.call.first_arg
|
|
205
182
|
|
|
@@ -227,10 +204,8 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
|
227
204
|
end
|
|
228
205
|
|
|
229
206
|
unless @mass_assign_disabled
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
matches.each do |result|
|
|
233
|
-
call = result.call
|
|
207
|
+
tracker.find_call(target: :"ActiveRecord::Base", method: [:send, :include]).each do |result|
|
|
208
|
+
call = result[:call]
|
|
234
209
|
if call? call and (call.first_arg == forbidden_protection or call.second_arg == forbidden_protection)
|
|
235
210
|
@mass_assign_disabled = true
|
|
236
211
|
end
|
|
@@ -250,6 +225,22 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
|
250
225
|
#This is to avoid reporting duplicates. Checks if the result has been
|
|
251
226
|
#reported already from the same line number.
|
|
252
227
|
def duplicate? result, location = nil
|
|
228
|
+
location, line = get_location result
|
|
229
|
+
|
|
230
|
+
@results.each do |r|
|
|
231
|
+
if r[0] == line and r[1] == location
|
|
232
|
+
if tracker.options[:combine_locations]
|
|
233
|
+
return true
|
|
234
|
+
elsif r[2] == result
|
|
235
|
+
return true
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
false
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def get_location result
|
|
253
244
|
if result.is_a? Hash
|
|
254
245
|
line = result[:call].original_line || result[:call].line
|
|
255
246
|
elsif sexp? result
|
|
@@ -258,23 +249,13 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
|
|
|
258
249
|
raise ArgumentError
|
|
259
250
|
end
|
|
260
251
|
|
|
261
|
-
location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
|
|
252
|
+
location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template] || result[:location][:file].to_s
|
|
262
253
|
|
|
263
254
|
location = location[:name] if location.is_a? Hash
|
|
264
255
|
location = location.name if location.is_a? Brakeman::Collection
|
|
265
256
|
location = location.to_sym
|
|
266
257
|
|
|
267
|
-
|
|
268
|
-
if r[0] == line and r[1] == location
|
|
269
|
-
if tracker.options[:combine_locations]
|
|
270
|
-
return true
|
|
271
|
-
elsif r[2] == result
|
|
272
|
-
return true
|
|
273
|
-
end
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
false
|
|
258
|
+
return location, line
|
|
278
259
|
end
|
|
279
260
|
|
|
280
261
|
#Checks if an expression contains string interpolation.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Brakeman::CheckCookieSerialization < Brakeman::BaseCheck
|
|
4
|
+
Brakeman::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Check for use of Marshal for cookie serialization"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
tracker.find_call(target: :'Rails.application.config.action_dispatch', method: :cookies_serializer=).each do |result|
|
|
10
|
+
setting = result[:call].first_arg
|
|
11
|
+
|
|
12
|
+
if symbol? setting and setting.value == :marshal or setting.value == :hybrid
|
|
13
|
+
warn :result => result,
|
|
14
|
+
:warning_type => "Remote Code Execution",
|
|
15
|
+
:warning_code => :unsafe_cookie_serialization,
|
|
16
|
+
:message => msg("Use of unsafe cookie serialization strategy ", msg_code(setting.value.inspect), " might lead to remote code execution"),
|
|
17
|
+
:confidence => :medium,
|
|
18
|
+
:link_path => "unsafe_deserialization"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -316,11 +316,11 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
|
316
316
|
end
|
|
317
317
|
|
|
318
318
|
json_escape_on = false
|
|
319
|
-
initializers = tracker.
|
|
320
|
-
initializers.each {|result| json_escape_on = true?(result
|
|
319
|
+
initializers = tracker.find_call(target: :ActiveSupport, method: :escape_html_entities_in_json=)
|
|
320
|
+
initializers.each {|result| json_escape_on = true?(result[:call].first_arg) }
|
|
321
321
|
|
|
322
322
|
if tracker.config.escape_html_entities_in_json?
|
|
323
|
-
|
|
323
|
+
json_escape_on = true
|
|
324
324
|
elsif version_between? "4.0.0", "9.9.9"
|
|
325
325
|
json_escape_on = true
|
|
326
326
|
end
|
|
@@ -80,13 +80,10 @@ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
|
|
|
80
80
|
def oj_safe_default?
|
|
81
81
|
safe_default = false
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
if tracker.check_initializers(:Oj, :mimic_JSON).any?
|
|
83
|
+
if tracker.find_call(target: :Oj, method: :mimic_JSON).any?
|
|
85
84
|
safe_default = true
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if result = tracker.check_initializers(:Oj, :default_options=).first
|
|
89
|
-
options = result.call.first_arg
|
|
85
|
+
elsif result = tracker.find_call(target: :Oj, method: :default_options=).first
|
|
86
|
+
options = result[:call].first_arg
|
|
90
87
|
|
|
91
88
|
if oj_safe_mode? options
|
|
92
89
|
safe_default = true
|
|
@@ -32,7 +32,7 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
|
|
|
32
32
|
|
|
33
33
|
file_name = call.first_arg
|
|
34
34
|
|
|
35
|
-
return if called_on_tempfile?(file_name)
|
|
35
|
+
return if called_on_tempfile?(file_name) || sanitized?(file_name)
|
|
36
36
|
|
|
37
37
|
if match = has_immediate_user_input?(file_name)
|
|
38
38
|
confidence = :high
|
|
@@ -71,6 +71,12 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
|
|
|
71
71
|
call?(file_name) && file_name.target == s(:const, :Tempfile)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
def sanitized? file
|
|
75
|
+
call?(file) &&
|
|
76
|
+
call?(file.target) &&
|
|
77
|
+
class_name(file.target.target) == :"ActiveStorage::Filename"
|
|
78
|
+
end
|
|
79
|
+
|
|
74
80
|
def temp_file_method? exp
|
|
75
81
|
if call? exp
|
|
76
82
|
return true if exp.call_chain.include? :tempfile
|
|
@@ -25,7 +25,7 @@ class Brakeman::CheckHeaderDoS < Brakeman::BaseCheck
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def has_workaround?
|
|
28
|
-
tracker.
|
|
29
|
-
|
|
28
|
+
tracker.find_call(target: :ActiveSupport, method: :on_load).any? and
|
|
29
|
+
tracker.find_call(target: :"ActionView::LookupContext::DetailsKey", method: :class_eval).any?
|
|
30
30
|
end
|
|
31
31
|
end
|
|
@@ -41,8 +41,8 @@ class Brakeman::CheckI18nXSS < Brakeman::BaseCheck
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def has_workaround?
|
|
44
|
-
tracker.
|
|
45
|
-
match.
|
|
44
|
+
tracker.find_call(target: :I18n, method: :const_defined?, chained: true).any? do |match|
|
|
45
|
+
match[:call].first_arg == s(:lit, :MissingTranslation)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -20,8 +20,8 @@ class Brakeman::CheckJRubyXML < Brakeman::BaseCheck
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
#Check for workaround
|
|
23
|
-
tracker.
|
|
24
|
-
arg = result
|
|
23
|
+
tracker.find_call(target: :"ActiveSupport::XmlMini", method: :backend=, chained: true).each do |result|
|
|
24
|
+
arg = result[:call].first_arg
|
|
25
25
|
|
|
26
26
|
return if string? arg and arg.value == "REXML"
|
|
27
27
|
end
|
|
@@ -44,13 +44,13 @@ class Brakeman::CheckJSONParsing < Brakeman::BaseCheck
|
|
|
44
44
|
|
|
45
45
|
#Check for `ActiveSupport::JSON.backend = "JSONGem"`
|
|
46
46
|
def uses_gem_backend?
|
|
47
|
-
matches = tracker.
|
|
47
|
+
matches = tracker.find_call(target: :'ActiveSupport::JSON', method: :backend=, chained: true)
|
|
48
48
|
|
|
49
49
|
unless matches.empty?
|
|
50
50
|
json_gem = s(:str, "JSONGem")
|
|
51
51
|
|
|
52
52
|
matches.each do |result|
|
|
53
|
-
if result
|
|
53
|
+
if result[:call].first_arg == json_gem
|
|
54
54
|
return true
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -30,8 +30,8 @@ class Brakeman::CheckMimeTypeDoS < Brakeman::BaseCheck
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def has_workaround?
|
|
33
|
-
tracker.
|
|
34
|
-
arg = match
|
|
33
|
+
tracker.find_call(target: :Mime, method: :const_set).any? do |match|
|
|
34
|
+
arg = match[:call].first_arg
|
|
35
35
|
|
|
36
36
|
symbol? arg and arg.value == :LOOKUP
|
|
37
37
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Brakeman::CheckReverseTabnabbing < Brakeman::BaseCheck
|
|
4
|
+
Brakeman::Checks.add_optional self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for reverse tabnabbing cases on 'link_to' calls"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
calls = tracker.find_call :methods => :link_to
|
|
10
|
+
calls.each do |call|
|
|
11
|
+
process_result call
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def process_result result
|
|
16
|
+
return unless original? result and result[:call].last_arg
|
|
17
|
+
|
|
18
|
+
html_opts = result[:call].last_arg
|
|
19
|
+
return unless hash? html_opts
|
|
20
|
+
|
|
21
|
+
target = hash_access html_opts, :target
|
|
22
|
+
return unless target && string?(target) && target.value == "_blank"
|
|
23
|
+
|
|
24
|
+
target_url = result[:block] ? result[:call].first_arg : result[:call].second_arg
|
|
25
|
+
|
|
26
|
+
# `url_for` and `_path` calls lead to urls on to the same origin.
|
|
27
|
+
# That means that an adversary would need to run javascript on
|
|
28
|
+
# the victim application's domain. If that is the case, the adversary
|
|
29
|
+
# already has the ability to redirect the victim user anywhere.
|
|
30
|
+
# Also statically provided URLs (interpolated or otherwise) are also
|
|
31
|
+
# ignored as they produce many false positives.
|
|
32
|
+
return if !call?(target_url) || target_url.method.match(/^url_for$|_path$/)
|
|
33
|
+
|
|
34
|
+
rel = hash_access html_opts, :rel
|
|
35
|
+
confidence = :medium
|
|
36
|
+
|
|
37
|
+
if rel && string?(rel) then
|
|
38
|
+
rel_opt = rel.value
|
|
39
|
+
return if rel_opt.include?("noopener") && rel_opt.include?("noreferrer")
|
|
40
|
+
|
|
41
|
+
if rel_opt.include?("noopener") ^ rel_opt.include?("noreferrer") then
|
|
42
|
+
confidence = :weak
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
warn :result => result,
|
|
47
|
+
:warning_type => "Reverse Tabnabbing",
|
|
48
|
+
:warning_code => :reverse_tabnabbing,
|
|
49
|
+
:message => msg("When opening a link in a new tab without setting ", msg_code('rel: "noopener noreferr"'),
|
|
50
|
+
", the new tab can control the parent tab's location. For example, an attacker could redirect to a phishing page."),
|
|
51
|
+
:confidence => confidence,
|
|
52
|
+
:user_input => rel
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -70,7 +70,7 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
|
|
|
70
70
|
|
|
71
71
|
def check_cve_2018_8048
|
|
72
72
|
if loofah_vulnerable_cve_2018_8048?
|
|
73
|
-
message = msg(msg_version(tracker.config.gem_version(:loofah), "loofah gem"), " is vulnerable (CVE-2018-8048). Upgrade to 2.1
|
|
73
|
+
message = msg(msg_version(tracker.config.gem_version(:loofah), "loofah gem"), " is vulnerable (CVE-2018-8048). Upgrade to 2.2.1")
|
|
74
74
|
|
|
75
75
|
if tracker.find_call(:target => false, :method => :sanitize).any?
|
|
76
76
|
confidence = :high
|
|
@@ -90,7 +90,7 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
|
|
|
90
90
|
def loofah_vulnerable_cve_2018_8048?
|
|
91
91
|
loofah_version = tracker.config.gem_version(:loofah)
|
|
92
92
|
|
|
93
|
-
loofah_version and loofah_version < "2.1
|
|
93
|
+
loofah_version and loofah_version < "2.2.1"
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
def warn_sanitizer_cve cve, link, upgrade_version
|
|
@@ -21,8 +21,11 @@ class Brakeman::CheckSessionSettings < Brakeman::BaseCheck
|
|
|
21
21
|
|
|
22
22
|
check_for_issues settings, @app_tree.file_path("config/environment.rb")
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
session_store = @app_tree.file_path("config/initializers/session_store.rb")
|
|
25
|
+
secret_token = @app_tree.file_path("config/initializers/secret_token.rb")
|
|
26
|
+
|
|
27
|
+
[session_store, secret_token].each do |file|
|
|
28
|
+
if tracker.initializers[file] and not ignored? file.basename
|
|
26
29
|
process tracker.initializers[file]
|
|
27
30
|
end
|
|
28
31
|
end
|
|
@@ -34,8 +34,8 @@ class Brakeman::CheckXMLDoS < Brakeman::BaseCheck
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def has_workaround?
|
|
37
|
-
tracker.
|
|
38
|
-
arg = match
|
|
37
|
+
tracker.find_call(target: :"ActiveSupport::XmlMini", method: :backend=).any? do |match|
|
|
38
|
+
arg = match[:call].first_arg
|
|
39
39
|
if string? arg
|
|
40
40
|
value = arg.value
|
|
41
41
|
value == 'Nokogiri' or value == 'LibXML'
|
|
@@ -48,21 +48,17 @@ class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
|
|
|
48
48
|
def disabled_xml_parser?
|
|
49
49
|
if version_between? "0.0.0", "2.3.14"
|
|
50
50
|
#Look for ActionController::Base.param_parsers.delete(Mime::XML)
|
|
51
|
-
|
|
52
|
-
s(:colon2, s(:const, :ActionController), :Base),
|
|
53
|
-
:param_parsers)
|
|
54
|
-
|
|
55
|
-
matches = tracker.check_initializers(params_parser, :delete)
|
|
51
|
+
matches = tracker.find_call(target: :"ActionController::Base.param_parsers", method: :delete)
|
|
56
52
|
else
|
|
57
53
|
#Look for ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML)
|
|
58
|
-
matches = tracker.
|
|
54
|
+
matches = tracker.find_call(target: :"ActionDispatch::ParamsParser::DEFAULT_PARSERS", method: :delete)
|
|
59
55
|
end
|
|
60
56
|
|
|
61
57
|
unless matches.empty?
|
|
62
58
|
mime_xml = s(:colon2, s(:const, :Mime), :XML)
|
|
63
59
|
|
|
64
60
|
matches.each do |result|
|
|
65
|
-
if result
|
|
61
|
+
if result[:call].first_arg == mime_xml
|
|
66
62
|
return true
|
|
67
63
|
end
|
|
68
64
|
end
|
|
@@ -74,18 +70,14 @@ class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
|
|
|
74
70
|
#Look for ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
|
75
71
|
#in Rails 2.x apps
|
|
76
72
|
def enabled_yaml_parser?
|
|
77
|
-
|
|
78
|
-
s(:colon2, s(:const, :ActionController), :Base),
|
|
79
|
-
:param_parsers)
|
|
80
|
-
|
|
81
|
-
matches = tracker.check_initializers(param_parsers, :[]=)
|
|
73
|
+
matches = tracker.find_call(target: :'ActionController::Base.param_parsers', method: :[]=)
|
|
82
74
|
|
|
83
75
|
mime_yaml = s(:colon2, s(:const, :Mime), :YAML)
|
|
84
76
|
|
|
85
77
|
matches.each do |result|
|
|
86
|
-
if result
|
|
87
|
-
symbol? result
|
|
88
|
-
result
|
|
78
|
+
if result[:call].first_arg == mime_yaml and
|
|
79
|
+
symbol? result[:call].second_arg and
|
|
80
|
+
result[:call].second_arg.value == :yaml
|
|
89
81
|
|
|
90
82
|
return true
|
|
91
83
|
end
|
|
@@ -96,16 +88,16 @@ class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
|
|
|
96
88
|
|
|
97
89
|
def disabled_xml_dangerous_types?
|
|
98
90
|
if version_between? "0.0.0", "2.3.14"
|
|
99
|
-
matches = tracker.
|
|
91
|
+
matches = tracker.find_call(target: :"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", method: :delete)
|
|
100
92
|
else
|
|
101
|
-
matches = tracker.
|
|
93
|
+
matches = tracker.find_call(target: :"ActiveSupport::XmlMini::PARSING", method: :delete)
|
|
102
94
|
end
|
|
103
95
|
|
|
104
96
|
symbols_off = false
|
|
105
97
|
yaml_off = false
|
|
106
98
|
|
|
107
99
|
matches.each do |result|
|
|
108
|
-
arg = result
|
|
100
|
+
arg = result[:call].first_arg
|
|
109
101
|
|
|
110
102
|
if string? arg
|
|
111
103
|
if arg.value == "yaml"
|
data/lib/brakeman/file_parser.rb
CHANGED
|
@@ -33,17 +33,13 @@ module Brakeman
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
def parse_ruby input, path
|
|
36
|
+
def parse_ruby input, path
|
|
37
37
|
begin
|
|
38
38
|
Brakeman.debug "Parsing #{path}"
|
|
39
|
-
|
|
39
|
+
RubyParser.new.parse input, path, @timeout
|
|
40
40
|
rescue Racc::ParseError => e
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
else
|
|
44
|
-
@tracker.error e, "Could not parse #{path}"
|
|
45
|
-
nil
|
|
46
|
-
end
|
|
41
|
+
@tracker.error e, "Could not parse #{path}"
|
|
42
|
+
nil
|
|
47
43
|
rescue Timeout::Error => e
|
|
48
44
|
@tracker.error Exception.new("Parsing #{path} took too long (> #{@timeout} seconds). Try increasing the limit with --parser-timeout"), caller
|
|
49
45
|
nil
|
data/lib/brakeman/file_path.rb
CHANGED
|
@@ -35,6 +35,11 @@ module Brakeman
|
|
|
35
35
|
@relative = relative_path
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
# Just the file name, no path
|
|
39
|
+
def basename
|
|
40
|
+
@basename ||= File.basename(self.relative)
|
|
41
|
+
end
|
|
42
|
+
|
|
38
43
|
# Read file from absolute path.
|
|
39
44
|
def read
|
|
40
45
|
File.read self.absolute
|
|
@@ -67,5 +72,14 @@ module Brakeman
|
|
|
67
72
|
def to_s
|
|
68
73
|
self.to_str
|
|
69
74
|
end
|
|
75
|
+
|
|
76
|
+
def hash
|
|
77
|
+
@hash ||= [@absolute, @relative].hash
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def eql? rhs
|
|
81
|
+
@absolute == rhs.absolute and
|
|
82
|
+
@relative == rhs.relative
|
|
83
|
+
end
|
|
70
84
|
end
|
|
71
85
|
end
|
data/lib/brakeman/processor.rb
CHANGED
|
@@ -90,7 +90,7 @@ module Brakeman
|
|
|
90
90
|
def process_initializer file_name, src
|
|
91
91
|
res = BaseProcessor.new(@tracker).process_file src, file_name
|
|
92
92
|
res = AliasProcessor.new(@tracker).process_safely res, nil, file_name
|
|
93
|
-
@tracker.initializers[
|
|
93
|
+
@tracker.initializers[file_name] = res
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
#Process source for a library file
|
|
@@ -265,6 +265,10 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
|
265
265
|
unless target.nil?
|
|
266
266
|
exp = target
|
|
267
267
|
end
|
|
268
|
+
when :dup
|
|
269
|
+
unless target.nil?
|
|
270
|
+
exp = target
|
|
271
|
+
end
|
|
268
272
|
when :join
|
|
269
273
|
if array? target and target.length > 2 and (string? first_arg or first_arg.nil?)
|
|
270
274
|
exp = process_array_join(target, first_arg)
|
|
@@ -602,7 +606,7 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
|
|
|
602
606
|
if node_type? exp, :hash
|
|
603
607
|
if exp.any? { |e| node_type? e, :kwsplat and node_type? e.value, :hash }
|
|
604
608
|
kwsplats, rest = exp.partition { |e| node_type? e, :kwsplat and node_type? e.value, :hash }
|
|
605
|
-
exp = Sexp.new.concat(rest).line(exp)
|
|
609
|
+
exp = Sexp.new.concat(rest).line(exp.line)
|
|
606
610
|
|
|
607
611
|
kwsplats.each do |e|
|
|
608
612
|
exp = process_hash_merge! exp, e.value
|
|
@@ -192,8 +192,8 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
|
|
|
192
192
|
|
|
193
193
|
filter_name = ("fake_filter" + rand.to_s[/\d+$/]).to_sym
|
|
194
194
|
args = exp.block_call.arglist
|
|
195
|
-
args.insert(1, Sexp.new(:lit, filter_name))
|
|
196
|
-
before_filter_call = make_call(nil, :before_filter, args)
|
|
195
|
+
args.insert(1, Sexp.new(:lit, filter_name).line(exp.line))
|
|
196
|
+
before_filter_call = make_call(nil, :before_filter, args).line(exp.line)
|
|
197
197
|
|
|
198
198
|
if exp.block_args.length > 1
|
|
199
199
|
block_variable = exp.block_args[1]
|
|
@@ -210,9 +210,9 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
|
|
|
210
210
|
#Build Sexp for filter method
|
|
211
211
|
body = Sexp.new(:lasgn,
|
|
212
212
|
block_variable,
|
|
213
|
-
Sexp.new(:call, Sexp.new(:const, @current_class.name), :new))
|
|
213
|
+
Sexp.new(:call, Sexp.new(:const, @current_class.name).line(exp.line), :new).line(exp.line)).line(exp.line)
|
|
214
214
|
|
|
215
|
-
filter_method = Sexp.new(:defn, filter_name, Sexp.new(:args), body).concat(block_inner).line(exp.line)
|
|
215
|
+
filter_method = Sexp.new(:defn, filter_name, Sexp.new(:args).line(exp.line), body).concat(block_inner).line(exp.line)
|
|
216
216
|
|
|
217
217
|
vis = @visibility
|
|
218
218
|
@visibility = :private
|
|
@@ -29,6 +29,14 @@ class Brakeman::GemProcessor < Brakeman::BasicProcessor
|
|
|
29
29
|
@tracker.config.set_rails_version
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Known issue: Brakeman does not yet support `gem` calls with multiple
|
|
33
|
+
# "version requirements". Consider the following example from the ruby docs:
|
|
34
|
+
#
|
|
35
|
+
# gem 'rake', '>= 1.1.a', '< 2'
|
|
36
|
+
#
|
|
37
|
+
# We are assuming that `second_arg` (eg. '>= 1.1.a') is the only requirement.
|
|
38
|
+
# Perhaps we should instantiate an array of `::Gem::Requirement`s or even a
|
|
39
|
+
# `::Gem::Dependency` and pass that to `Tracker::Config#add_gem`?
|
|
32
40
|
def process_call exp
|
|
33
41
|
if exp.target == nil
|
|
34
42
|
if exp.method == :gem
|
|
@@ -51,8 +59,8 @@ class Brakeman::GemProcessor < Brakeman::BasicProcessor
|
|
|
51
59
|
end
|
|
52
60
|
end
|
|
53
61
|
elsif @inside_gemspec and exp.method == :add_dependency
|
|
54
|
-
if string? exp.first_arg and string? exp.
|
|
55
|
-
@tracker.config.add_gem exp.first_arg.value, exp.
|
|
62
|
+
if string? exp.first_arg and string? exp.second_arg
|
|
63
|
+
@tracker.config.add_gem exp.first_arg.value, exp.second_arg.value, @gemspec, exp.line
|
|
56
64
|
end
|
|
57
65
|
end
|
|
58
66
|
|
|
@@ -179,7 +179,7 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
|
179
179
|
clauses = [get_pushed_value(exp.then_clause), get_pushed_value(exp.else_clause)].compact
|
|
180
180
|
|
|
181
181
|
if clauses.length > 1
|
|
182
|
-
s(:or, *clauses)
|
|
182
|
+
s(:or, *clauses).line(exp.line)
|
|
183
183
|
else
|
|
184
184
|
clauses.first
|
|
185
185
|
end
|
|
@@ -5,9 +5,9 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
|
|
|
5
5
|
|
|
6
6
|
def initialize tracker
|
|
7
7
|
super
|
|
8
|
-
|
|
9
|
-
@current_method = nil
|
|
8
|
+
|
|
10
9
|
@in_target = false
|
|
10
|
+
@processing_class = false
|
|
11
11
|
@calls = []
|
|
12
12
|
@cache = {}
|
|
13
13
|
end
|
|
@@ -23,10 +23,33 @@ class Brakeman::FindAllCalls < Brakeman::BasicProcessor
|
|
|
23
23
|
process exp
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
#For whatever reason, originally the indexing of calls
|
|
27
|
+
#was performed on individual method bodies (see process_defn).
|
|
28
|
+
#This method explicitly indexes all calls everywhere given any
|
|
29
|
+
#source.
|
|
30
|
+
def process_all_source exp, opts
|
|
31
|
+
@processing_class = true
|
|
32
|
+
process_source exp, opts
|
|
33
|
+
ensure
|
|
34
|
+
@processing_class = false
|
|
35
|
+
end
|
|
36
|
+
|
|
26
37
|
#Process body of method
|
|
27
38
|
def process_defn exp
|
|
28
|
-
return exp unless @current_method
|
|
29
|
-
|
|
39
|
+
return exp unless @current_method or @processing_class
|
|
40
|
+
|
|
41
|
+
# 'Normal' processing assumes the method name was given
|
|
42
|
+
# as an option to `process_source` but for `process_all_source`
|
|
43
|
+
# we don't want to do that.
|
|
44
|
+
if @current_method.nil?
|
|
45
|
+
@current_method = exp.method_name
|
|
46
|
+
process_all exp.body
|
|
47
|
+
@current_method = nil
|
|
48
|
+
else
|
|
49
|
+
process_all exp.body
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
exp
|
|
30
53
|
end
|
|
31
54
|
|
|
32
55
|
alias process_defs process_defn
|
|
@@ -33,14 +33,13 @@ require 'brakeman/processors/lib/basic_processor'
|
|
|
33
33
|
# FindCall.new nil, /^g?sub!?$/
|
|
34
34
|
class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
35
35
|
|
|
36
|
-
def initialize targets, methods, tracker
|
|
36
|
+
def initialize targets, methods, tracker
|
|
37
37
|
super tracker
|
|
38
38
|
@calls = []
|
|
39
39
|
@find_targets = targets
|
|
40
40
|
@find_methods = methods
|
|
41
41
|
@current_class = nil
|
|
42
42
|
@current_method = nil
|
|
43
|
-
@in_depth = in_depth
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
#Returns a list of results.
|
|
@@ -48,10 +47,6 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
48
47
|
#A result looks like:
|
|
49
48
|
#
|
|
50
49
|
# s(:result, :ClassName, :method_name, s(:call, ...))
|
|
51
|
-
#
|
|
52
|
-
#or
|
|
53
|
-
#
|
|
54
|
-
# s(:result, :template_name, s(:call, ...))
|
|
55
50
|
def matches
|
|
56
51
|
@calls
|
|
57
52
|
end
|
|
@@ -60,10 +55,7 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
60
55
|
#or the template. These names are used when reporting results.
|
|
61
56
|
#
|
|
62
57
|
#Use FindCall#matches to retrieve results.
|
|
63
|
-
def process_source exp
|
|
64
|
-
@current_class = klass
|
|
65
|
-
@current_method = method
|
|
66
|
-
@current_template = template
|
|
58
|
+
def process_source exp
|
|
67
59
|
process exp
|
|
68
60
|
end
|
|
69
61
|
|
|
@@ -74,11 +66,6 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
74
66
|
|
|
75
67
|
alias :process_defs :process_defn
|
|
76
68
|
|
|
77
|
-
#Process body of block
|
|
78
|
-
def process_rlist exp
|
|
79
|
-
process_all exp
|
|
80
|
-
end
|
|
81
|
-
|
|
82
69
|
#Look for matching calls and add them to results
|
|
83
70
|
def process_call exp
|
|
84
71
|
target = get_target exp.target
|
|
@@ -87,25 +74,9 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
87
74
|
process_call_args exp
|
|
88
75
|
|
|
89
76
|
if match(@find_targets, target) and match(@find_methods, method)
|
|
90
|
-
|
|
91
|
-
if @current_template
|
|
92
|
-
@calls << Sexp.new(:result, @current_template, exp).line(exp.line)
|
|
93
|
-
else
|
|
94
|
-
@calls << Sexp.new(:result, @current_module, @current_class, @current_method, exp).line(exp.line)
|
|
95
|
-
end
|
|
96
|
-
|
|
77
|
+
@calls << Sexp.new(:result, @current_module, @current_class, @current_method, exp).line(exp.line)
|
|
97
78
|
end
|
|
98
79
|
|
|
99
|
-
#Normally FindCall won't match a method invocation that is the target of
|
|
100
|
-
#another call, such as:
|
|
101
|
-
#
|
|
102
|
-
# User.find(:first, :conditions => "user = '#{params['user']}').name
|
|
103
|
-
#
|
|
104
|
-
#A search for User.find will not match this unless @in_depth is true.
|
|
105
|
-
if @in_depth and call? exp.target
|
|
106
|
-
process exp.target
|
|
107
|
-
end
|
|
108
|
-
|
|
109
80
|
exp
|
|
110
81
|
end
|
|
111
82
|
|
|
@@ -123,8 +94,6 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
123
94
|
case exp.node_type
|
|
124
95
|
when :ivar, :lvar, :const, :lit
|
|
125
96
|
exp.value
|
|
126
|
-
when :true, :false
|
|
127
|
-
exp.node_type
|
|
128
97
|
when :colon2
|
|
129
98
|
class_name exp
|
|
130
99
|
else
|
|
@@ -141,43 +110,13 @@ class Brakeman::FindCall < Brakeman::BasicProcessor
|
|
|
141
110
|
when Symbol
|
|
142
111
|
if search_terms == item
|
|
143
112
|
true
|
|
144
|
-
elsif sexp? item
|
|
145
|
-
is_instance_of? item, search_terms
|
|
146
113
|
else
|
|
147
114
|
false
|
|
148
115
|
end
|
|
149
|
-
when Sexp
|
|
150
|
-
search_terms == item
|
|
151
116
|
when Enumerable
|
|
152
117
|
if search_terms.empty?
|
|
153
118
|
item == nil
|
|
154
|
-
else
|
|
155
|
-
search_terms.each do|term|
|
|
156
|
-
if match(term, item)
|
|
157
|
-
return true
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
false
|
|
161
119
|
end
|
|
162
|
-
when Regexp
|
|
163
|
-
search_terms.match item.to_s
|
|
164
|
-
when nil
|
|
165
|
-
true
|
|
166
|
-
else
|
|
167
|
-
raise "Cannot match #{search_terms} and #{item}"
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
#Checks if +item+ is an instance of +klass+ by looking for Klass.new
|
|
172
|
-
def is_instance_of? item, klass
|
|
173
|
-
if call? item
|
|
174
|
-
if sexp? item.target
|
|
175
|
-
item.method == :new and item.target.node_type == :const and item.target.value == klass
|
|
176
|
-
else
|
|
177
|
-
item.method == :new and item.target == klass
|
|
178
|
-
end
|
|
179
|
-
else
|
|
180
|
-
false
|
|
181
120
|
end
|
|
182
121
|
end
|
|
183
122
|
end
|
|
@@ -61,9 +61,9 @@ class Brakeman::TemplateProcessor < Brakeman::BaseProcessor
|
|
|
61
61
|
branches = [arg.then_clause, arg.else_clause].compact
|
|
62
62
|
|
|
63
63
|
if branches.empty?
|
|
64
|
-
s(:nil)
|
|
64
|
+
s(:nil).line(arg.line)
|
|
65
65
|
elsif branches.length == 2
|
|
66
|
-
Sexp.new(:or, *branches)
|
|
66
|
+
Sexp.new(:or, *branches).line(arg.line)
|
|
67
67
|
else
|
|
68
68
|
branches.first
|
|
69
69
|
end
|
|
@@ -77,9 +77,13 @@ class Brakeman::TemplateProcessor < Brakeman::BaseProcessor
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def add_output output, type = :output
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
if node_type? output, :or
|
|
81
|
+
Sexp.new(:or, add_output(output.lhs, type), add_output(output.rhs, type)).line(output.line)
|
|
82
|
+
else
|
|
83
|
+
s = Sexp.new(type, output)
|
|
84
|
+
s.line(output.line)
|
|
85
|
+
@current_template.add_output s
|
|
86
|
+
s
|
|
87
|
+
end
|
|
84
88
|
end
|
|
85
89
|
end
|
data/lib/brakeman/rescanner.rb
CHANGED
|
@@ -226,9 +226,13 @@ class Brakeman::Rescanner < Brakeman::Scanner
|
|
|
226
226
|
end
|
|
227
227
|
|
|
228
228
|
def rescan_initializer path
|
|
229
|
+
tracker.reset_initializer path
|
|
230
|
+
|
|
229
231
|
parse_ruby_files([path]).each do |astfile|
|
|
230
232
|
process_initializer astfile
|
|
231
233
|
end
|
|
234
|
+
|
|
235
|
+
@reindex << :initializers
|
|
232
236
|
end
|
|
233
237
|
|
|
234
238
|
#Handle rescanning when a file is deleted
|
data/lib/brakeman/tracker.rb
CHANGED
|
@@ -227,6 +227,10 @@ class Brakeman::Tracker
|
|
|
227
227
|
finder.process_source template.src, :template => template, :file => template.file
|
|
228
228
|
end
|
|
229
229
|
|
|
230
|
+
self.initializers.each do |file_name, src|
|
|
231
|
+
finder.process_all_source src, :file => file_name
|
|
232
|
+
end
|
|
233
|
+
|
|
230
234
|
@call_index = Brakeman::CallIndex.new finder.calls
|
|
231
235
|
end
|
|
232
236
|
|
|
@@ -237,8 +241,8 @@ class Brakeman::Tracker
|
|
|
237
241
|
#
|
|
238
242
|
#This will limit reindexing to the given sets
|
|
239
243
|
def reindex_call_sites locations
|
|
240
|
-
#If reindexing templates, models,
|
|
241
|
-
#everything
|
|
244
|
+
#If reindexing templates, models, controllers,
|
|
245
|
+
#just redo everything.
|
|
242
246
|
if locations.length == 3
|
|
243
247
|
return index_call_sites
|
|
244
248
|
end
|
|
@@ -260,6 +264,12 @@ class Brakeman::Tracker
|
|
|
260
264
|
method_sets << self.controllers
|
|
261
265
|
end
|
|
262
266
|
|
|
267
|
+
if locations.include? :initializers
|
|
268
|
+
self.initializers.each do |file_name, src|
|
|
269
|
+
@call_index.remove_indexes_by_file file_name
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
263
273
|
@call_index.remove_indexes_by_class classes_to_reindex
|
|
264
274
|
|
|
265
275
|
finder = Brakeman::FindAllCalls.new self
|
|
@@ -279,6 +289,12 @@ class Brakeman::Tracker
|
|
|
279
289
|
end
|
|
280
290
|
end
|
|
281
291
|
|
|
292
|
+
if locations.include? :initializers
|
|
293
|
+
self.initializers.each do |file_name, src|
|
|
294
|
+
finder.process_all_source src, :file => file_name
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
282
298
|
@call_index.index_calls finder.calls
|
|
283
299
|
end
|
|
284
300
|
|
|
@@ -363,4 +379,12 @@ class Brakeman::Tracker
|
|
|
363
379
|
def reset_routes
|
|
364
380
|
@routes = {}
|
|
365
381
|
end
|
|
382
|
+
|
|
383
|
+
def reset_initializer path
|
|
384
|
+
@initializers.delete_if do |file, src|
|
|
385
|
+
path.relative.include? file
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
@call_index.remove_indexes_by_file path
|
|
389
|
+
end
|
|
366
390
|
end
|
|
@@ -19,16 +19,9 @@ module Brakeman
|
|
|
19
19
|
@ruby_version = ""
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def allow_forgery_protection?
|
|
23
|
-
@rails[:action_controller] and
|
|
24
|
-
@rails[:action_controller][:allow_forgery_protection] == Sexp.new(:false)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
22
|
def default_protect_from_forgery?
|
|
28
23
|
if version_between? "5.2.0", "9.9.9"
|
|
29
|
-
if @rails
|
|
30
|
-
@rails[:action_controller][:default_protect_from_forgery] == Sexp.new(:false)
|
|
31
|
-
|
|
24
|
+
if @rails.dig(:action_controller, :default_protect_from_forgery) == Sexp.new(:false)
|
|
32
25
|
return false
|
|
33
26
|
else
|
|
34
27
|
return true
|
|
@@ -48,17 +41,15 @@ module Brakeman
|
|
|
48
41
|
|
|
49
42
|
def escape_html_entities_in_json?
|
|
50
43
|
#TODO add version-specific information here
|
|
51
|
-
@rails
|
|
52
|
-
true? @rails[:active_support][:escape_html_entities_in_json]
|
|
44
|
+
true? @rails.dig(:active_support, :escape_html_entities_in_json)
|
|
53
45
|
end
|
|
54
46
|
|
|
55
47
|
def whitelist_attributes?
|
|
56
|
-
@rails
|
|
57
|
-
@rails[:active_record][:whitelist_attributes] == Sexp.new(:true)
|
|
48
|
+
@rails.dig(:active_record, :whitelist_attributes) == Sexp.new(:true)
|
|
58
49
|
end
|
|
59
50
|
|
|
60
51
|
def gem_version name
|
|
61
|
-
@gems
|
|
52
|
+
@gems.dig(name, :version)
|
|
62
53
|
end
|
|
63
54
|
|
|
64
55
|
def add_gem name, version, file, line
|
|
@@ -111,6 +102,8 @@ module Brakeman
|
|
|
111
102
|
@escape_html = true
|
|
112
103
|
Brakeman.notify "[Notice] Escaping HTML by default"
|
|
113
104
|
end
|
|
105
|
+
|
|
106
|
+
check_haml_version
|
|
114
107
|
end
|
|
115
108
|
|
|
116
109
|
def set_ruby_version version
|
|
@@ -152,12 +145,26 @@ module Brakeman
|
|
|
152
145
|
end
|
|
153
146
|
|
|
154
147
|
def session_settings
|
|
155
|
-
@rails
|
|
156
|
-
@rails[:action_controller][:session]
|
|
148
|
+
@rails.dig(:action_controller, :session)
|
|
157
149
|
end
|
|
158
150
|
|
|
159
151
|
private
|
|
160
152
|
|
|
153
|
+
# Brakeman does not support Haml 5 yet.
|
|
154
|
+
# (https://github.com/presidentbeef/brakeman/issues/1370) Fortunately, Haml
|
|
155
|
+
# 5 doesn't add much new syntax, and brakeman (which uses the haml 4 parser)
|
|
156
|
+
# will parse most Haml 5 files without errors. Therefore, we only print a
|
|
157
|
+
# warning here, instead of raising an error.
|
|
158
|
+
def check_haml_version
|
|
159
|
+
return if supported_haml_version?
|
|
160
|
+
Brakeman.notify <<~EOS
|
|
161
|
+
Brakeman does not fully support Haml 5 yet. Your Gemfile (or gemspec)
|
|
162
|
+
allows Haml 5. Brakeman uses the Haml 4 parser and thus may produce
|
|
163
|
+
errors when parsing Haml 5 syntax. If Brakeman encounters such an error,
|
|
164
|
+
it will be unable to scan that file.
|
|
165
|
+
EOS
|
|
166
|
+
end
|
|
167
|
+
|
|
161
168
|
def convert_version_number value
|
|
162
169
|
if value.match(/\A\d+\z/)
|
|
163
170
|
value.to_i
|
|
@@ -174,6 +181,23 @@ module Brakeman
|
|
|
174
181
|
end
|
|
175
182
|
end
|
|
176
183
|
|
|
184
|
+
# Brakeman does not support Haml 5 yet. See `check_haml_version`.
|
|
185
|
+
#
|
|
186
|
+
# This method is messy because all we have is a version `Requirement` (like
|
|
187
|
+
# `~> 5.0.3`, or `> 4.99`) The only way I could think of was to loop over
|
|
188
|
+
# known Haml 5 versions and test whether the `Requirement` is satisfied. I
|
|
189
|
+
# included '5.99.99' in the list so that this method might stand a chance of
|
|
190
|
+
# working in the future.
|
|
191
|
+
def supported_haml_version?
|
|
192
|
+
haml_version = gem_version(:haml)
|
|
193
|
+
return true unless haml_version
|
|
194
|
+
|
|
195
|
+
requirement = Gem::Requirement.new(haml_version)
|
|
196
|
+
['5.99.99', '5.1.1', '5.1.0', '5.0.4', '5.0.3', '5.0.2', '5.0.1', '5.0.0'].none? do |v|
|
|
197
|
+
requirement.satisfied_by?(Gem::Version.new(v))
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
177
201
|
def higher? lhs, rhs
|
|
178
202
|
if lhs.class == rhs.class
|
|
179
203
|
lhs > rhs
|
|
@@ -49,7 +49,7 @@ module Brakeman
|
|
|
49
49
|
include Brakeman::Util
|
|
50
50
|
|
|
51
51
|
def initialize
|
|
52
|
-
@constants =
|
|
52
|
+
@constants = {}
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def size
|
|
@@ -103,6 +103,7 @@ module Brakeman
|
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
base_name = Constants.get_constant_base_name(name)
|
|
106
|
+
@constants[base_name] ||= []
|
|
106
107
|
@constants[base_name] << Constant.new(name, value, context)
|
|
107
108
|
end
|
|
108
109
|
|
data/lib/brakeman/version.rb
CHANGED
data/lib/brakeman/warning.rb
CHANGED
data/lib/ruby_parser/bm_sexp.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.6.0
|
|
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: 2019-
|
|
12
|
+
date: 2019-07-23 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: minitest
|
|
@@ -226,6 +226,7 @@ files:
|
|
|
226
226
|
- lib/brakeman/checks/check_basic_auth.rb
|
|
227
227
|
- lib/brakeman/checks/check_basic_auth_timing_attack.rb
|
|
228
228
|
- lib/brakeman/checks/check_content_tag.rb
|
|
229
|
+
- lib/brakeman/checks/check_cookie_serialization.rb
|
|
229
230
|
- lib/brakeman/checks/check_create_with.rb
|
|
230
231
|
- lib/brakeman/checks/check_cross_site_scripting.rb
|
|
231
232
|
- lib/brakeman/checks/check_default_routes.rb
|
|
@@ -266,6 +267,7 @@ files:
|
|
|
266
267
|
- lib/brakeman/checks/check_render_dos.rb
|
|
267
268
|
- lib/brakeman/checks/check_render_inline.rb
|
|
268
269
|
- lib/brakeman/checks/check_response_splitting.rb
|
|
270
|
+
- lib/brakeman/checks/check_reverse_tabnabbing.rb
|
|
269
271
|
- lib/brakeman/checks/check_route_dos.rb
|
|
270
272
|
- lib/brakeman/checks/check_safe_buffer_manipulation.rb
|
|
271
273
|
- lib/brakeman/checks/check_sanitize_methods.rb
|
|
@@ -403,8 +405,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
403
405
|
- !ruby/object:Gem::Version
|
|
404
406
|
version: '0'
|
|
405
407
|
requirements: []
|
|
406
|
-
|
|
407
|
-
rubygems_version: 2.7.8
|
|
408
|
+
rubygems_version: 3.0.3
|
|
408
409
|
signing_key:
|
|
409
410
|
specification_version: 4
|
|
410
411
|
summary: Security vulnerability scanner for Ruby on Rails.
|