brakeman 1.6.0 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Brakeman Logo](http://brakemanscanner.org/images/logo_medium.png)
|
2
|
+
|
3
|
+
![Travis CI Status](https://secure.travis-ci.org/presidentbeef/brakeman.png)
|
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.
|