brakeman 1.6.0 → 1.6.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.
- data/README.md +4 -0
- data/lib/brakeman/checks/base_check.rb +17 -2
- data/lib/brakeman/checks/check_basic_auth.rb +0 -1
- data/lib/brakeman/checks/check_cross_site_scripting.rb +0 -3
- data/lib/brakeman/checks/check_execute.rb +0 -2
- data/lib/brakeman/checks/check_file_access.rb +0 -1
- data/lib/brakeman/checks/check_mass_assignment.rb +1 -2
- data/lib/brakeman/checks/check_model_attributes.rb +1 -1
- data/lib/brakeman/checks/check_redirect.rb +0 -1
- data/lib/brakeman/checks/check_skip_before_filter.rb +0 -1
- data/lib/brakeman/checks/check_sql.rb +343 -58
- data/lib/brakeman/checks/check_without_protection.rb +1 -2
- data/lib/brakeman/processor.rb +7 -1
- data/lib/brakeman/processors/base_processor.rb +1 -1
- data/lib/brakeman/processors/controller_alias_processor.rb +34 -0
- data/lib/brakeman/processors/lib/render_helper.rb +1 -1
- data/lib/brakeman/report.rb +1 -1
- data/lib/brakeman/rescanner.rb +1 -1
- data/lib/brakeman/util.rb +19 -0
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +6 -2
- metadata +19 -5
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+

|
2
|
+
|
3
|
+

|
4
|
+
|
1
5
|
# Brakeman
|
2
6
|
|
3
7
|
Brakeman is a static analysis tool which checks Ruby on Rails applications for security vulnerabilities.
|
@@ -120,13 +120,28 @@ class Brakeman::BaseCheck < SexpProcessor
|
|
120
120
|
end
|
121
121
|
|
122
122
|
#Checks if the model inherits from parent,
|
123
|
-
def
|
123
|
+
def ancestor? model, parent
|
124
124
|
if model == nil
|
125
125
|
false
|
126
126
|
elsif model[:parent] == parent
|
127
127
|
true
|
128
128
|
elsif model[:parent]
|
129
|
-
|
129
|
+
ancestor? tracker.models[model[:parent]], parent
|
130
|
+
else
|
131
|
+
false
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def unprotected_model? model
|
136
|
+
model[:attr_accessible].nil? and !parent_classes_protected?(model) and ancestor?(model, :"ActiveRecord::Base")
|
137
|
+
end
|
138
|
+
|
139
|
+
# go up the chain of parent classes to see if any have attr_accessible
|
140
|
+
def parent_classes_protected? model
|
141
|
+
if model[:attr_accessible]
|
142
|
+
true
|
143
|
+
elsif parent = tracker.models[model[:parent]]
|
144
|
+
parent_classes_protected? parent
|
130
145
|
else
|
131
146
|
false
|
132
147
|
end
|
@@ -100,7 +100,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
100
100
|
warn :template => @current_template,
|
101
101
|
:warning_type => "Cross Site Scripting",
|
102
102
|
:message => message,
|
103
|
-
:line => input.match.line,
|
104
103
|
:code => input.match,
|
105
104
|
:confidence => CONFIDENCE[:high]
|
106
105
|
|
@@ -120,7 +119,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
120
119
|
warn :template => @current_template,
|
121
120
|
:warning_type => "Cross Site Scripting",
|
122
121
|
:message => "Unescaped model attribute",
|
123
|
-
:line => code.line,
|
124
122
|
:code => code,
|
125
123
|
:confidence => confidence
|
126
124
|
end
|
@@ -184,7 +182,6 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
|
|
184
182
|
warn :template => @current_template,
|
185
183
|
:warning_type => "Cross Site Scripting",
|
186
184
|
:message => message,
|
187
|
-
:line => exp.line,
|
188
185
|
:code => exp,
|
189
186
|
:user_input => @matched.match,
|
190
187
|
:confidence => confidence
|
@@ -52,7 +52,6 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
52
52
|
warn :result => result,
|
53
53
|
:warning_type => "Command Injection",
|
54
54
|
:message => "Possible command injection",
|
55
|
-
:line => call.line,
|
56
55
|
:code => call,
|
57
56
|
:user_input => failure.match,
|
58
57
|
:confidence => confidence
|
@@ -86,7 +85,6 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
|
|
86
85
|
|
87
86
|
warning = { :warning_type => "Command Injection",
|
88
87
|
:message => "Possible command injection",
|
89
|
-
:line => exp.line,
|
90
88
|
:code => exp,
|
91
89
|
:user_input => user_input,
|
92
90
|
:confidence => confidence }
|
@@ -14,7 +14,7 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
14
14
|
|
15
15
|
models = []
|
16
16
|
tracker.models.each do |name, m|
|
17
|
-
if
|
17
|
+
if unprotected_model? m
|
18
18
|
models << name
|
19
19
|
end
|
20
20
|
end
|
@@ -68,7 +68,6 @@ class Brakeman::CheckMassAssignment < Brakeman::BaseCheck
|
|
68
68
|
warn :result => res,
|
69
69
|
:warning_type => "Mass Assignment",
|
70
70
|
:message => "Unprotected mass assignment",
|
71
|
-
:line => call.line,
|
72
71
|
:code => call,
|
73
72
|
:user_input => user_input,
|
74
73
|
:confidence => confidence
|
@@ -62,7 +62,7 @@ class Brakeman::CheckModelAttributes < Brakeman::BaseCheck
|
|
62
62
|
|
63
63
|
def check_models
|
64
64
|
tracker.models.each do |name, model|
|
65
|
-
if
|
65
|
+
if unprotected_model? model
|
66
66
|
yield name, model
|
67
67
|
end
|
68
68
|
end
|
@@ -27,7 +27,6 @@ class Brakeman::CheckSkipBeforeFilter < Brakeman::BaseCheck
|
|
27
27
|
warn :class => controller[:name],
|
28
28
|
:warning_type => "Cross-Site Request Forgery",
|
29
29
|
:message => "Use whitelist (:only => [..]) when skipping CSRF check",
|
30
|
-
:line => filter.line,
|
31
30
|
:code => filter,
|
32
31
|
:confidence => CONFIDENCE[:med]
|
33
32
|
end
|
@@ -16,10 +16,11 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
16
16
|
def run_check
|
17
17
|
@rails_version = tracker.config[:rails_version]
|
18
18
|
|
19
|
+
@sql_targets = [:all, :average, :calculate, :count, :count_by_sql, :exists?,
|
20
|
+
:find, :find_by_sql, :first, :last, :maximum, :minimum, :sum]
|
21
|
+
|
19
22
|
if tracker.options[:rails3]
|
20
|
-
@sql_targets
|
21
|
-
else
|
22
|
-
@sql_targets = /^(find|find_by_sql|last|first|all|count|sum|average|minumum|maximum|count_by_sql)$/
|
23
|
+
@sql_targets.concat [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where]
|
23
24
|
end
|
24
25
|
|
25
26
|
Brakeman.debug "Finding possible SQL calls on models"
|
@@ -90,7 +91,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
90
91
|
find_calls.process_source block, model_name, scope_name
|
91
92
|
|
92
93
|
find_calls.calls.each do |call|
|
93
|
-
if call[:method]
|
94
|
+
if @sql_targets.include? call[:method]
|
94
95
|
process_result call
|
95
96
|
end
|
96
97
|
end
|
@@ -99,35 +100,82 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
99
100
|
end
|
100
101
|
end
|
101
102
|
|
102
|
-
#Process
|
103
|
+
#Process possible SQL injection sites:
|
104
|
+
#
|
105
|
+
# Model#find
|
106
|
+
#
|
107
|
+
# Model#(named_)scope
|
108
|
+
#
|
109
|
+
# Model#(find|count)_by_sql
|
110
|
+
#
|
111
|
+
# Model#all
|
112
|
+
#
|
113
|
+
### Rails 3
|
114
|
+
#
|
115
|
+
# Model#(where|having)
|
116
|
+
# Model#(order|group)
|
117
|
+
#
|
118
|
+
### Find Options Hash
|
119
|
+
#
|
120
|
+
# Dangerous keys that accept SQL:
|
121
|
+
#
|
122
|
+
# * conditions
|
123
|
+
# * order
|
124
|
+
# * having
|
125
|
+
# * joins
|
126
|
+
# * select
|
127
|
+
# * from
|
128
|
+
# * lock
|
129
|
+
#
|
103
130
|
def process_result result
|
104
|
-
|
105
|
-
#it is actually doing...
|
131
|
+
return if duplicate? result or result[:call].original_line
|
106
132
|
|
107
133
|
call = result[:call]
|
108
|
-
|
134
|
+
method = call[2]
|
109
135
|
args = call[3]
|
110
136
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
137
|
+
dangerous_value = case method
|
138
|
+
when :find
|
139
|
+
check_find_arguments args[2]
|
140
|
+
when :exists?
|
141
|
+
check_find_arguments args[1]
|
142
|
+
when :named_scope, :scope
|
143
|
+
check_scope_arguments args
|
144
|
+
when :find_by_sql, :count_by_sql
|
145
|
+
check_by_sql_arguments args[1]
|
146
|
+
when :calculate
|
147
|
+
check_find_arguments args[3]
|
148
|
+
when :last, :first, :all
|
149
|
+
check_find_arguments args[1]
|
150
|
+
when :average, :count, :maximum, :minimum, :sum
|
151
|
+
if args.length > 2
|
152
|
+
unsafe_sql?(args[1]) or check_find_arguments(args[-1])
|
153
|
+
else
|
154
|
+
check_find_arguments args[-1]
|
155
|
+
end
|
156
|
+
when :where, :having
|
157
|
+
check_query_arguments args
|
158
|
+
when :order, :group, :reorder
|
159
|
+
check_order_arguments args
|
160
|
+
when :joins
|
161
|
+
check_joins_arguments args[1]
|
162
|
+
when :from, :select
|
163
|
+
unsafe_sql? args[1]
|
164
|
+
when :lock
|
165
|
+
check_lock_arguments args[1]
|
166
|
+
else
|
167
|
+
Brakeman.debug "Unhandled SQL method: #{method}"
|
168
|
+
end
|
169
|
+
|
170
|
+
if dangerous_value
|
123
171
|
add_result result
|
124
172
|
|
125
|
-
if input = include_user_input?(
|
173
|
+
if input = include_user_input?(dangerous_value)
|
126
174
|
confidence = CONFIDENCE[:high]
|
127
175
|
user_input = input.match
|
128
176
|
else
|
129
177
|
confidence = CONFIDENCE[:med]
|
130
|
-
user_input =
|
178
|
+
user_input = dangerous_value
|
131
179
|
end
|
132
180
|
|
133
181
|
warn :result => result,
|
@@ -144,34 +192,137 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
144
192
|
confidence = CONFIDENCE[:low]
|
145
193
|
end
|
146
194
|
|
147
|
-
warn :result => result,
|
148
|
-
:warning_type => "SQL Injection",
|
195
|
+
warn :result => result,
|
196
|
+
:warning_type => "SQL Injection",
|
149
197
|
:message => "Upgrade to Rails >= 2.1.2 to escape :limit and :offset. Possible SQL injection",
|
150
198
|
:confidence => confidence
|
151
199
|
end
|
152
200
|
end
|
153
201
|
|
154
|
-
|
202
|
+
#The 'find' methods accept a number of different types of parameters:
|
203
|
+
#
|
204
|
+
# * The first argument might be :all, :first, or :last
|
205
|
+
# * The first argument might be an integer ID or an array of IDs
|
206
|
+
# * The second argument might be a hash of options, some of which are
|
207
|
+
# dangerous and some of which are not
|
208
|
+
# * The second argument might contain SQL fragments as values
|
209
|
+
# * The second argument might contain properly parameterized SQL fragments in arrays
|
210
|
+
# * The second argument might contain improperly parameterized SQL fragments in arrays
|
211
|
+
#
|
212
|
+
#This method should only be passed the second argument.
|
213
|
+
def check_find_arguments arg
|
214
|
+
if not sexp? arg or node_type? arg, :lit, :string, :str, :true, :false, :nil
|
215
|
+
return nil
|
216
|
+
end
|
155
217
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
218
|
+
unsafe_sql? arg
|
219
|
+
end
|
220
|
+
|
221
|
+
def check_scope_arguments args
|
222
|
+
return unless node_type? args, :arglist
|
223
|
+
|
224
|
+
if node_type? args[2], :iter
|
225
|
+
unsafe_sql? args[2][-1]
|
226
|
+
else
|
227
|
+
unsafe_sql? args[2]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def check_query_arguments arg
|
232
|
+
return unless sexp? arg
|
233
|
+
|
234
|
+
if node_type? arg, :arglist
|
235
|
+
if arg.length > 2 and node_type? arg[1], :string_interp, :dstr
|
236
|
+
# Model.where("blah = ?", blah)
|
237
|
+
return string_interp arg[1]
|
172
238
|
else
|
173
|
-
|
174
|
-
|
239
|
+
arg = arg[1]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
if request_value? arg
|
244
|
+
# Model.where(params[:where])
|
245
|
+
arg
|
246
|
+
elsif hash? arg
|
247
|
+
#This is generally going to be a hash of column names and values, which
|
248
|
+
#would escape the values. But the keys _could_ be user input.
|
249
|
+
check_hash_keys arg
|
250
|
+
elsif node_type? arg, :lit, :str
|
251
|
+
nil
|
252
|
+
else
|
253
|
+
#Hashes are safe...but we check above for hash, so...?
|
254
|
+
unsafe_sql? arg, :ignore_hash
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
#Checks each argument to order/reorder/group for possible SQL.
|
259
|
+
#Anything used with these methods is passed in verbatim.
|
260
|
+
def check_order_arguments args
|
261
|
+
return unless sexp? args
|
262
|
+
|
263
|
+
if node_type? args, :arglist
|
264
|
+
args.each do |arg|
|
265
|
+
if unsafe_arg = unsafe_sql?(arg)
|
266
|
+
return unsafe_arg
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
nil
|
271
|
+
else
|
272
|
+
unsafe_sql? args
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
#find_by_sql and count_by_sql can take either a straight SQL string
|
277
|
+
#or an array with values to bind.
|
278
|
+
def check_by_sql_arguments arg
|
279
|
+
return unless sexp? arg
|
280
|
+
|
281
|
+
#This is kind of necessary, because unsafe_sql? will handle an array
|
282
|
+
#correctly, but might be better to be explicit.
|
283
|
+
if array? arg
|
284
|
+
unsafe_sql? arg[1]
|
285
|
+
else
|
286
|
+
unsafe_sql? arg
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
#joins can take a string, hash of associations, or an array of both(?)
|
291
|
+
#We only care about the possible string values.
|
292
|
+
def check_joins_arguments arg
|
293
|
+
return unless sexp? arg and not node_type? arg, :hash, :string, :str
|
294
|
+
|
295
|
+
if array? arg
|
296
|
+
arg.each do |a|
|
297
|
+
if unsafe = check_joins_arguments(a)
|
298
|
+
return unsafe
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
nil
|
303
|
+
else
|
304
|
+
unsafe_sql? arg
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#Model#lock essentially only cares about strings. But those strings can be
|
309
|
+
#any SQL fragment. This does not apply to all databases. (For those who do not
|
310
|
+
#support it, the lock method does nothing).
|
311
|
+
def check_lock_arguments arg
|
312
|
+
return unless sexp? arg and not node_type? arg, :hash, :array, :string, :str
|
313
|
+
|
314
|
+
unsafe_sql? arg, :ignore_hash
|
315
|
+
end
|
316
|
+
|
317
|
+
|
318
|
+
#Check hash keys for user input.
|
319
|
+
#(Seems unlikely, but if a user can control the column names queried, that
|
320
|
+
#could be bad)
|
321
|
+
def check_hash_keys exp
|
322
|
+
hash_iterate(exp) do |key, value|
|
323
|
+
unless symbol? key
|
324
|
+
if unsafe_key = unsafe_sql?(value)
|
325
|
+
return unsafe_key
|
175
326
|
end
|
176
327
|
end
|
177
328
|
end
|
@@ -179,35 +330,169 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
179
330
|
false
|
180
331
|
end
|
181
332
|
|
333
|
+
#Check an interpolated string for dangerous values.
|
334
|
+
#
|
335
|
+
#This method assumes values interpolated into strings are unsafe by default,
|
336
|
+
#unless safe_value? explicitly returns true.
|
182
337
|
def check_string_interp arg
|
183
338
|
arg.each do |exp|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
339
|
+
if node_type?(exp, :string_eval, :evstr) and not safe_value? exp[1]
|
340
|
+
return exp[1]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
nil
|
345
|
+
end
|
346
|
+
|
347
|
+
#Checks the given expression for unsafe SQL values. If an unsafe value is
|
348
|
+
#found, returns that value (may be the given _exp_ or a subexpression).
|
349
|
+
#
|
350
|
+
#Otherwise, returns false/nil.
|
351
|
+
def unsafe_sql? exp, ignore_hash = false
|
352
|
+
return unless sexp? exp
|
353
|
+
|
354
|
+
dangerous_value = find_dangerous_value exp, ignore_hash
|
355
|
+
|
356
|
+
if safe_value? dangerous_value
|
357
|
+
false
|
358
|
+
else
|
359
|
+
dangerous_value
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
#Check _exp_ for dangerous values. Used by unsafe_sql?
|
364
|
+
def find_dangerous_value exp, ignore_hash
|
365
|
+
case exp.node_type
|
366
|
+
when :lit, :str, :const, :colon2, :true, :false, :nil
|
367
|
+
nil
|
368
|
+
when :array
|
369
|
+
#Assume this is an array like
|
370
|
+
#
|
371
|
+
# ["blah = ? AND thing = ?", ...]
|
372
|
+
#
|
373
|
+
#and check first value
|
374
|
+
|
375
|
+
unsafe_sql? exp[1]
|
376
|
+
when :string_interp, :dstr
|
377
|
+
check_string_interp exp
|
378
|
+
when :hash
|
379
|
+
check_hash_values exp unless ignore_hash
|
380
|
+
when :if
|
381
|
+
unsafe_sql? exp[2] or unsafe_sql? exp[3]
|
382
|
+
when :call
|
383
|
+
unless IGNORE_METHODS_IN_SQL.include? exp[2]
|
384
|
+
if has_immediate_user_input? exp or has_immediate_model? exp
|
385
|
+
exp
|
386
|
+
else
|
387
|
+
check_call exp
|
189
388
|
end
|
190
389
|
end
|
390
|
+
when :or
|
391
|
+
if unsafe = (unsafe_sql?(exp[1]) || unsafe_sql?(exp[2]))
|
392
|
+
return unsafe
|
393
|
+
else
|
394
|
+
nil
|
395
|
+
end
|
396
|
+
when :block, :rlist
|
397
|
+
unsafe_sql? exp[-1]
|
398
|
+
else
|
399
|
+
if has_immediate_user_input? exp or has_immediate_model? exp
|
400
|
+
exp
|
401
|
+
else
|
402
|
+
nil
|
403
|
+
end
|
191
404
|
end
|
192
405
|
end
|
193
406
|
|
194
|
-
#
|
195
|
-
|
407
|
+
#Checks hash values associated with these keys:
|
408
|
+
#
|
409
|
+
# * conditions
|
410
|
+
# * order
|
411
|
+
# * having
|
412
|
+
# * joins
|
413
|
+
# * select
|
414
|
+
# * from
|
415
|
+
# * lock
|
416
|
+
def check_hash_values exp
|
417
|
+
hash_iterate(exp) do |key, value|
|
418
|
+
if symbol? key
|
419
|
+
unsafe = case key[1]
|
420
|
+
when :conditions, :having, :select
|
421
|
+
check_query_arguments value
|
422
|
+
when :order, :group
|
423
|
+
check_order_arguments value
|
424
|
+
when :joins
|
425
|
+
check_joins_arguments value
|
426
|
+
when :lock
|
427
|
+
check_lock_arguments value
|
428
|
+
when :from
|
429
|
+
unsafe_sql? value
|
430
|
+
else
|
431
|
+
nil
|
432
|
+
end
|
433
|
+
|
434
|
+
return unsafe if unsafe
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
false
|
439
|
+
end
|
440
|
+
|
441
|
+
STRING_METHODS = Set[:<<, :+, :concat, :prepend]
|
442
|
+
|
443
|
+
def check_for_string_building exp
|
444
|
+
return unless call? exp
|
445
|
+
|
196
446
|
target = exp[1]
|
197
447
|
method = exp[2]
|
198
448
|
args = exp[3]
|
199
449
|
|
200
|
-
if
|
201
|
-
|
202
|
-
|
450
|
+
if string? target or string? args[1]
|
451
|
+
if STRING_METHODS.include? method
|
452
|
+
return exp
|
453
|
+
end
|
454
|
+
elsif STRING_METHODS.include? method and call? target
|
455
|
+
unsafe_sql? target
|
456
|
+
end
|
457
|
+
end
|
203
458
|
|
459
|
+
IGNORE_METHODS_IN_SQL = Set[:id, :merge_conditions, :table_name, :to_i, :to_f,
|
460
|
+
:sanitize_sql, :sanitize_sql_array, :sanitize_sql_for_assignment,
|
461
|
+
:sanitize_sql_for_conditions, :sanitize_sql_hash,
|
462
|
+
:sanitize_sql_hash_for_assignment, :sanitize_sql_hash_for_conditions]
|
463
|
+
|
464
|
+
def safe_value? exp
|
465
|
+
return true unless sexp? exp
|
466
|
+
|
467
|
+
case exp.node_type
|
468
|
+
when :str, :lit, :const, :colon2, :nil, :true, :false
|
204
469
|
true
|
470
|
+
when :call
|
471
|
+
IGNORE_METHODS_IN_SQL.include? exp[2]
|
472
|
+
when :if
|
473
|
+
safe_value? exp[2] and safe_value? exp[3]
|
474
|
+
when :block, :rlist
|
475
|
+
safe_value? exp[-1]
|
476
|
+
when :or
|
477
|
+
safe_value? exp[1] and safe_value? exp[2]
|
478
|
+
else
|
479
|
+
false
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
#Check call for string building
|
484
|
+
def check_call exp
|
485
|
+
return unless call? exp
|
486
|
+
|
487
|
+
target = exp[1]
|
488
|
+
args = exp[3]
|
489
|
+
|
490
|
+
if unsafe = check_for_string_building(exp)
|
491
|
+
unsafe
|
205
492
|
elsif call? target
|
206
493
|
check_call target
|
207
|
-
elsif target == nil and tracker.options[:rails3] and method.to_s.match(/^first|last|all|where|order|group|having$/)
|
208
|
-
check_arguments args
|
209
494
|
else
|
210
|
-
|
495
|
+
nil
|
211
496
|
end
|
212
497
|
end
|
213
498
|
|
@@ -218,7 +503,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
218
503
|
def check_for_limit_or_offset_vulnerability options
|
219
504
|
return false if @rails_version.nil? or @rails_version >= "2.1.1" or not hash? options
|
220
505
|
|
221
|
-
if hash_access(options, :limit) or hash_access(options
|
506
|
+
if hash_access(options, :limit) or hash_access(options, :offset)
|
222
507
|
return true
|
223
508
|
end
|
224
509
|
|
@@ -231,7 +516,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
|
|
231
516
|
#
|
232
517
|
# s(:call,
|
233
518
|
# s(:call,
|
234
|
-
# s(:call,
|
519
|
+
# s(:call,
|
235
520
|
# s(:call, nil, :params, s(:arglist)),
|
236
521
|
# :[],
|
237
522
|
# s(:arglist, s(:lit, :x))),
|
@@ -16,7 +16,7 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
|
|
16
16
|
|
17
17
|
models = []
|
18
18
|
tracker.models.each do |name, m|
|
19
|
-
if
|
19
|
+
if ancestor? m, :"ActiveRecord::Base"
|
20
20
|
models << name
|
21
21
|
end
|
22
22
|
end
|
@@ -59,7 +59,6 @@ class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
|
|
59
59
|
warn :result => res,
|
60
60
|
:warning_type => "Mass Assignment",
|
61
61
|
:message => "Unprotected mass assignment",
|
62
|
-
:line => call.line,
|
63
62
|
:code => call,
|
64
63
|
:user_input => user_input,
|
65
64
|
:confidence => confidence
|
data/lib/brakeman/processor.rb
CHANGED
@@ -10,6 +10,8 @@ module Brakeman
|
|
10
10
|
#The ControllerProcessor, TemplateProcessor, and ModelProcessor will
|
11
11
|
#update the Tracker with information about what is parsed.
|
12
12
|
class Processor
|
13
|
+
include Util
|
14
|
+
|
13
15
|
def initialize options
|
14
16
|
@tracker = Tracker.new self, options
|
15
17
|
end
|
@@ -35,7 +37,11 @@ module Brakeman
|
|
35
37
|
|
36
38
|
#Process controller source. +file_name+ is used for reporting
|
37
39
|
def process_controller src, file_name
|
38
|
-
|
40
|
+
if contains_class? src
|
41
|
+
ControllerProcessor.new(@tracker).process_controller src, file_name
|
42
|
+
else
|
43
|
+
LibraryProcessor.new(@tracker).process_library src, file_name
|
44
|
+
end
|
39
45
|
end
|
40
46
|
|
41
47
|
#Process variable aliasing in controller source and save it in the
|
@@ -251,7 +251,7 @@ class Brakeman::BaseProcessor < SexpProcessor
|
|
251
251
|
value = args[1]
|
252
252
|
end
|
253
253
|
|
254
|
-
types_in_hash = Set[:action, :file, :inline, :js, :json, :nothing, :partial, :text, :update, :xml]
|
254
|
+
types_in_hash = Set[:action, :file, :inline, :js, :json, :nothing, :partial, :template, :text, :update, :xml]
|
255
255
|
|
256
256
|
#render :layout => "blah" means something else when in a template
|
257
257
|
if in_view
|
@@ -24,7 +24,41 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
24
24
|
return
|
25
25
|
else
|
26
26
|
@current_class = name
|
27
|
+
|
27
28
|
process_default src
|
29
|
+
|
30
|
+
process_mixins
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
#Process modules mixed into the controller, in case they contain actions.
|
35
|
+
def process_mixins
|
36
|
+
controller = @tracker.controllers[@current_class]
|
37
|
+
|
38
|
+
controller[:includes].each do |i|
|
39
|
+
mixin = @tracker.libs[i]
|
40
|
+
|
41
|
+
next unless mixin
|
42
|
+
|
43
|
+
#Process methods in alphabetical order for consistency
|
44
|
+
methods = mixin[:public].keys.map { |n| n.to_s }.sort.map { |n| n.to_sym }
|
45
|
+
|
46
|
+
methods.each do |name|
|
47
|
+
#Need to process the method like it was in a controller in order
|
48
|
+
#to get the renders set
|
49
|
+
processor = Brakeman::ControllerProcessor.new(@tracker)
|
50
|
+
method = mixin[:public][name]
|
51
|
+
|
52
|
+
if node_type? method, :methdef
|
53
|
+
method = processor.process_defn method
|
54
|
+
else
|
55
|
+
#Should be a methdef, but this will catch other cases
|
56
|
+
method = processor.process method
|
57
|
+
end
|
58
|
+
|
59
|
+
#Then process it like any other method in the controller
|
60
|
+
process method
|
61
|
+
end
|
28
62
|
end
|
29
63
|
end
|
30
64
|
|
data/lib/brakeman/report.rb
CHANGED
@@ -505,7 +505,7 @@ class Brakeman::Report
|
|
505
505
|
message = CGI.escapeHTML(message)
|
506
506
|
|
507
507
|
if @highlight_user_input and warning.user_input
|
508
|
-
user_input = warning.format_user_input
|
508
|
+
user_input = CGI.escapeHTML(warning.format_user_input)
|
509
509
|
|
510
510
|
message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
|
511
511
|
end
|
data/lib/brakeman/rescanner.rb
CHANGED
@@ -127,7 +127,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
|
|
127
127
|
end
|
128
128
|
|
129
129
|
def rescan_template path
|
130
|
-
return unless path.match KNOWN_TEMPLATE_EXTENSIONS
|
130
|
+
return unless path.match KNOWN_TEMPLATE_EXTENSIONS and File.exist? path
|
131
131
|
|
132
132
|
template_name = template_path_to_name(path)
|
133
133
|
|
data/lib/brakeman/util.rb
CHANGED
@@ -219,6 +219,25 @@ module Brakeman::Util
|
|
219
219
|
exp.is_a? Sexp and types.include? exp.node_type
|
220
220
|
end
|
221
221
|
|
222
|
+
#Returns true if the given _exp_ contains a :class node.
|
223
|
+
#
|
224
|
+
#Useful for checking if a module is just a module or if it is a namespace.
|
225
|
+
def contains_class? exp
|
226
|
+
todo = [exp]
|
227
|
+
|
228
|
+
until todo.empty?
|
229
|
+
current = todo.shift
|
230
|
+
|
231
|
+
if node_type? current, :class
|
232
|
+
return true
|
233
|
+
elsif sexp? current
|
234
|
+
todo = current[1..-1].concat todo
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
false
|
239
|
+
end
|
240
|
+
|
222
241
|
#Return file name related to given warning. Uses +warning.file+ if it exists
|
223
242
|
def file_for warning, tracker = nil
|
224
243
|
if tracker.nil?
|
data/lib/brakeman/version.rb
CHANGED
data/lib/brakeman/warning.rb
CHANGED
@@ -29,8 +29,12 @@ class Brakeman::Warning
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
-
if
|
33
|
-
@
|
32
|
+
if not @line
|
33
|
+
if @user_input and @user_input.respond_to? :line
|
34
|
+
@line = @user_input.line
|
35
|
+
elsif @code and @code.respond_to? :line
|
36
|
+
@line = @code.line
|
37
|
+
end
|
34
38
|
end
|
35
39
|
|
36
40
|
unless @warning_set
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brakeman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 13
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 6
|
9
|
-
-
|
10
|
-
version: 1.6.
|
9
|
+
- 1
|
10
|
+
version: 1.6.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Justin Collins
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-05-23 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: activesupport
|
@@ -166,6 +166,20 @@ dependencies:
|
|
166
166
|
version: "3.0"
|
167
167
|
type: :runtime
|
168
168
|
version_requirements: *id010
|
169
|
+
- !ruby/object:Gem::Dependency
|
170
|
+
name: json_pure
|
171
|
+
prerelease: false
|
172
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
173
|
+
none: false
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
hash: 3
|
178
|
+
segments:
|
179
|
+
- 0
|
180
|
+
version: "0"
|
181
|
+
type: :runtime
|
182
|
+
version_requirements: *id011
|
169
183
|
description: Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis.
|
170
184
|
email:
|
171
185
|
executables:
|
@@ -298,7 +312,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
298
312
|
requirements: []
|
299
313
|
|
300
314
|
rubyforge_project:
|
301
|
-
rubygems_version: 1.8.
|
315
|
+
rubygems_version: 1.8.23
|
302
316
|
signing_key:
|
303
317
|
specification_version: 3
|
304
318
|
summary: Security vulnerability scanner for Ruby on Rails.
|