brakeman-min 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/FEATURES +16 -0
  2. data/README.md +118 -0
  3. data/WARNING_TYPES +69 -0
  4. data/bin/brakeman +269 -0
  5. data/lib/checks.rb +67 -0
  6. data/lib/checks/base_check.rb +353 -0
  7. data/lib/checks/check_cross_site_scripting.rb +324 -0
  8. data/lib/checks/check_default_routes.rb +29 -0
  9. data/lib/checks/check_evaluation.rb +27 -0
  10. data/lib/checks/check_execute.rb +110 -0
  11. data/lib/checks/check_file_access.rb +46 -0
  12. data/lib/checks/check_forgery_setting.rb +42 -0
  13. data/lib/checks/check_mail_to.rb +48 -0
  14. data/lib/checks/check_mass_assignment.rb +72 -0
  15. data/lib/checks/check_model_attributes.rb +36 -0
  16. data/lib/checks/check_nested_attributes.rb +34 -0
  17. data/lib/checks/check_redirect.rb +98 -0
  18. data/lib/checks/check_render.rb +65 -0
  19. data/lib/checks/check_send_file.rb +15 -0
  20. data/lib/checks/check_session_settings.rb +36 -0
  21. data/lib/checks/check_sql.rb +124 -0
  22. data/lib/checks/check_validation_regex.rb +60 -0
  23. data/lib/format/style.css +105 -0
  24. data/lib/processor.rb +83 -0
  25. data/lib/processors/alias_processor.rb +384 -0
  26. data/lib/processors/base_processor.rb +237 -0
  27. data/lib/processors/config_processor.rb +146 -0
  28. data/lib/processors/controller_alias_processor.rb +237 -0
  29. data/lib/processors/controller_processor.rb +202 -0
  30. data/lib/processors/erb_template_processor.rb +84 -0
  31. data/lib/processors/erubis_template_processor.rb +62 -0
  32. data/lib/processors/haml_template_processor.rb +131 -0
  33. data/lib/processors/lib/find_call.rb +176 -0
  34. data/lib/processors/lib/find_model_call.rb +39 -0
  35. data/lib/processors/lib/processor_helper.rb +36 -0
  36. data/lib/processors/lib/render_helper.rb +137 -0
  37. data/lib/processors/library_processor.rb +118 -0
  38. data/lib/processors/model_processor.rb +125 -0
  39. data/lib/processors/output_processor.rb +233 -0
  40. data/lib/processors/params_processor.rb +77 -0
  41. data/lib/processors/route_processor.rb +338 -0
  42. data/lib/processors/template_alias_processor.rb +86 -0
  43. data/lib/processors/template_processor.rb +55 -0
  44. data/lib/report.rb +651 -0
  45. data/lib/scanner.rb +215 -0
  46. data/lib/scanner_erubis.rb +43 -0
  47. data/lib/tracker.rb +144 -0
  48. data/lib/util.rb +141 -0
  49. data/lib/version.rb +1 -0
  50. data/lib/warning.rb +97 -0
  51. metadata +141 -0
@@ -0,0 +1,125 @@
1
+ require 'processors/base_processor'
2
+
3
+ #Processes models. Puts results in tracker.models
4
+ class ModelProcessor < BaseProcessor
5
+ def initialize tracker
6
+ super
7
+ @model = nil
8
+ @current_method = nil
9
+ @visibility = :public
10
+ @file_name = nil
11
+ end
12
+
13
+ #Process model source
14
+ def process_model src, file_name = nil
15
+ @file_name = file_name
16
+ process src
17
+ end
18
+
19
+ #s(:class, NAME, PARENT, s(:scope ...))
20
+ def process_class exp
21
+ if @model
22
+ warn "[Notice] Skipping inner class: #{class_name exp[1]}" if OPTIONS[:debug]
23
+ ignore
24
+ else
25
+ @model = { :name => class_name(exp[1]),
26
+ :parent => class_name(exp[2]),
27
+ :includes => [],
28
+ :public => {},
29
+ :private => {},
30
+ :protected => {},
31
+ :options => {},
32
+ :file => @file_name }
33
+ @tracker.models[@model[:name]] = @model
34
+ res = process exp[3]
35
+ @model = nil
36
+ res
37
+ end
38
+ end
39
+
40
+ #Handle calls outside of methods,
41
+ #such as include, attr_accessible, private, etc.
42
+ def process_call exp
43
+ return exp unless @model
44
+ target = exp[1]
45
+ if sexp? target
46
+ target = process target
47
+ end
48
+
49
+ method = exp[2]
50
+ args = exp[3]
51
+
52
+ #Methods called inside class definition
53
+ #like attr_* and other settings
54
+ if @current_method.nil? and target.nil?
55
+ if args.length == 1 #actually, empty
56
+ case method
57
+ when :private, :protected, :public
58
+ @visibility = method
59
+ else
60
+ #??
61
+ end
62
+ else
63
+ case method
64
+ when :include
65
+ @model[:includes] << class_name(args[1]) if @model
66
+ when :attr_accessible
67
+ @model[:attr_accessible] ||= []
68
+ args = args[1..-1].map do |e|
69
+ e[1]
70
+ end
71
+
72
+ @model[:attr_accessible].concat args
73
+ else
74
+ if @model
75
+ @model[:options][method] ||= []
76
+ @model[:options][method] << process(args)
77
+ end
78
+ end
79
+ end
80
+ ignore
81
+ else
82
+ call = Sexp.new :call, target, method, process(args)
83
+ call.line(exp.line)
84
+ call
85
+ end
86
+ end
87
+
88
+ #Add method definition to tracker
89
+ def process_defn exp
90
+ return exp unless @model
91
+ name = exp[1]
92
+
93
+ @current_method = name
94
+ res = Sexp.new :methdef, name, process(exp[2]), process(exp[3][1])
95
+ res.line(exp.line)
96
+ @current_method = nil
97
+ if @model
98
+ list = @model[@visibility]
99
+ list[name] = res
100
+ end
101
+ res
102
+ end
103
+
104
+ #Add method definition to tracker
105
+ def process_defs exp
106
+ return exp unless @model
107
+ name = exp[2]
108
+
109
+ if exp[1].node_type == :self
110
+ target = @model[:name]
111
+ else
112
+ target = class_name exp[1]
113
+ end
114
+
115
+ @current_method = name
116
+ res = Sexp.new :selfdef, target, name, process(exp[3]), process(exp[4][1])
117
+ res.line(exp.line)
118
+ @current_method = nil
119
+ if @model
120
+ @model[@visibility][name] = res unless @model.nil?
121
+ end
122
+ res
123
+ end
124
+
125
+ end
@@ -0,0 +1,233 @@
1
+ require 'rubygems'
2
+ require 'ruby2ruby'
3
+ require 'util'
4
+
5
+ #Produces formatted output strings from Sexps.
6
+ #Recommended usage is
7
+ #
8
+ # OutputProcessor.new.format(Sexp.new(:str, "hello"))
9
+ class OutputProcessor < Ruby2Ruby
10
+ include Util
11
+
12
+ #Copies +exp+ and then formats it.
13
+ def format exp
14
+ process exp.deep_clone
15
+ end
16
+
17
+ alias process_safely format
18
+
19
+ def process exp
20
+ begin
21
+ super exp if sexp? exp and not exp.empty?
22
+ rescue Exception => e
23
+ warn "While formatting #{exp}: #{e}\n#{e.backtrace.join("\n")}" if OPTIONS[:debug]
24
+ end
25
+ end
26
+
27
+ def process_call exp
28
+ if exp[0].is_a? Symbol
29
+ target = exp[0]
30
+
31
+ method = exp[1]
32
+
33
+ args = process exp[2]
34
+
35
+ out = nil
36
+
37
+ if method == :[]
38
+ if target
39
+ out = "#{target}[#{args}]"
40
+ else
41
+ raise Exception.new("Not sure what to do with access and no target: #{exp}")
42
+ end
43
+ else
44
+ if target
45
+ out = "#{target}.#{method}(#{args})"
46
+ else
47
+ out = "#{method}(#{args})"
48
+ end
49
+ end
50
+ exp.clear
51
+ out
52
+ else
53
+ super exp
54
+ end
55
+ end
56
+
57
+ def process_lvar exp
58
+ out = "(local #{exp[0]})"
59
+ exp.clear
60
+ out
61
+ end
62
+
63
+ def process_ignore exp
64
+ exp.clear
65
+ "[ignored]"
66
+ end
67
+
68
+ def process_params exp
69
+ exp.clear
70
+ "params"
71
+ end
72
+
73
+ def process_session exp
74
+ exp.clear
75
+ "session"
76
+ end
77
+
78
+ def process_cookies exp
79
+ exp.clear
80
+ "cookies"
81
+ end
82
+
83
+ def process_string_interp exp
84
+ out = '"'
85
+ exp.each do |e|
86
+ if e.is_a? String
87
+ out << e
88
+ else
89
+ res = process e
90
+ out << res unless res == ""
91
+ end
92
+ end
93
+ out << '"'
94
+ exp.clear
95
+ out
96
+ end
97
+
98
+ def process_string_eval exp
99
+ out = "\#{#{process(exp[0])}}"
100
+ exp.clear
101
+ out
102
+ end
103
+
104
+ def process_dxstr exp
105
+ out = "`"
106
+ out << exp.map! do |e|
107
+ if e.is_a? String
108
+ e
109
+ elsif string? e
110
+ e[1]
111
+ else
112
+ process e
113
+ end
114
+ end.join
115
+ exp.clear
116
+ out << "`"
117
+ end
118
+
119
+ def process_rlist exp
120
+ out = exp.map do |e|
121
+ res = process e
122
+ if res == ""
123
+ nil
124
+ else
125
+ res
126
+ end
127
+ end.compact.join("\n")
128
+ exp.clear
129
+ out
130
+ end
131
+
132
+ def process_call_with_block exp
133
+ call = process exp[0]
134
+ block = process exp[1] if exp[1]
135
+ out = "#{call} do\n #{block}\n end"
136
+ exp.clear
137
+ out
138
+ end
139
+
140
+ def process_output exp
141
+ out = if exp[0].node_type == :str
142
+ ""
143
+ else
144
+ res = process exp[0]
145
+
146
+ if res == ""
147
+ ""
148
+ else
149
+ "[Output] #{res}"
150
+ end
151
+ end
152
+ exp.clear
153
+ out
154
+ end
155
+
156
+ def process_format exp
157
+ out = if exp[0].node_type == :str or exp[0].node_type == :ignore
158
+ ""
159
+ else
160
+ res = process exp[0]
161
+
162
+ if res == ""
163
+ ""
164
+ else
165
+ "[Format] #{res}"
166
+ end
167
+ end
168
+ exp.clear
169
+ out
170
+ end
171
+
172
+ def process_format_escaped exp
173
+ out = if exp[0].node_type == :str or exp[0].node_type == :ignore
174
+ ""
175
+ else
176
+ res = process exp[0]
177
+
178
+ if res == ""
179
+ ""
180
+ else
181
+ "[Escaped] #{res}"
182
+ end
183
+ end
184
+ exp.clear
185
+ out
186
+ end
187
+
188
+ def process_const exp
189
+ if exp[0] == Tracker::UNKNOWN_MODEL
190
+ exp.clear
191
+ "(Unresolved Model)"
192
+ else
193
+ super exp
194
+ end
195
+ end
196
+
197
+ def process_render exp
198
+ exp[1] = process exp[1] if sexp? exp[1]
199
+ exp[2] = process exp[2] if sexp? exp[2]
200
+ out = "render(#{exp[0]} => #{exp[1]}, #{exp[2]})"
201
+ exp.clear
202
+ out
203
+ end
204
+
205
+ #This is copied from Ruby2Ruby, except the :string_eval type has been added
206
+ def util_dthing(type, exp)
207
+ s = []
208
+
209
+ # first item in sexp is a string literal
210
+ s << dthing_escape(type, exp.shift)
211
+
212
+ until exp.empty?
213
+ pt = exp.shift
214
+ case pt
215
+ when Sexp then
216
+ case pt.first
217
+ when :str then
218
+ s << dthing_escape(type, pt.last)
219
+ when :evstr, :string_eval then
220
+ s << '#{' << process(pt) << '}' # do not use interpolation here
221
+ else
222
+ raise "unknown type: #{pt.inspect}"
223
+ end
224
+ else
225
+ # HACK: raise "huh?: #{pt.inspect}" -- hitting # constants in regexps
226
+ # do nothing for now
227
+ end
228
+ end
229
+
230
+ s.join
231
+ end
232
+
233
+ end
@@ -0,0 +1,77 @@
1
+ require 'rubygems'
2
+ require 'sexp_processor'
3
+ require 'set'
4
+
5
+ #Looks for request parameters. Not used currently.
6
+ class ParamsProcessor < SexpProcessor
7
+ attr_reader :result
8
+
9
+ def initialize
10
+ super()
11
+ self.strict = false
12
+ self.auto_shift_type = false
13
+ self.require_empty = false
14
+ self.default_method = :process_default
15
+ self.warn_on_default = false
16
+ @result = []
17
+ @matched = false
18
+ @mark = false
19
+ @watch_nodes = Set.new([:call, :iasgn, :lasgn, :gasgn, :cvasgn, :return, :attrasgn])
20
+ @params = Sexp.new(:call, nil, :params, Sexp.new(:arglist))
21
+ end
22
+
23
+ def process_default exp
24
+ if @watch_nodes.include?(exp.node_type) and not @mark
25
+ @mark = true
26
+ @matched = false
27
+ process_these exp[1..-1]
28
+ if @matched
29
+ @result << exp
30
+ @matched = false
31
+ end
32
+ @mark = false
33
+ else
34
+ process_these exp[1..-1]
35
+ end
36
+
37
+ exp
38
+ end
39
+
40
+ def process_these exp
41
+ exp.each do |e|
42
+ if sexp? e and not e.empty?
43
+ process e
44
+ end
45
+ end
46
+ end
47
+
48
+ def process_call exp
49
+ if @mark
50
+ actually_process_call exp
51
+ else
52
+ @mark = true
53
+ actually_process_call exp
54
+ if @matched
55
+ @result << exp
56
+ end
57
+ @mark = @matched = false
58
+ end
59
+
60
+ exp
61
+ end
62
+
63
+ def actually_process_call exp
64
+ process exp[1]
65
+ process exp[3]
66
+ if exp[1] == @params or exp == @params
67
+ @matched = true
68
+ end
69
+ end
70
+
71
+ #Don't really care about condition
72
+ def process_if exp
73
+ process_these exp[2..-1]
74
+ exp
75
+ end
76
+
77
+ end
@@ -0,0 +1,338 @@
1
+ require 'processors/base_processor'
2
+ require 'processors/alias_processor'
3
+ require 'util'
4
+ require 'set'
5
+
6
+ #Processes the Sexp from routes.rb. Stores results in tracker.routes.
7
+ #
8
+ #Note that it is only interested in determining what methods on which
9
+ #controllers are used as routes, not the generated URLs for routes.
10
+ class RoutesProcessor < BaseProcessor
11
+ attr_reader :map, :nested, :current_controller
12
+
13
+ def initialize tracker
14
+ super
15
+ @map = Sexp.new(:lvar, :map)
16
+ @nested = nil #used for identifying nested targets
17
+ @prefix = [] #Controller name prefix (a module name, usually)
18
+ @current_controller = nil
19
+ @with_options = nil #For use inside map.with_options
20
+ end
21
+
22
+ #Call this with parsed route file information.
23
+ #
24
+ #This method first calls RouteAliasProcessor#process_safely on the +exp+,
25
+ #so it does not modify the +exp+.
26
+ def process_routes exp
27
+ process RouteAliasProcessor.new.process_safely(exp)
28
+ end
29
+
30
+ #Looking for mapping of routes
31
+ def process_call exp
32
+ target = exp[1]
33
+
34
+ if target == map or target == nested
35
+ process_map exp
36
+
37
+ else
38
+ process_default exp
39
+ end
40
+
41
+ exp
42
+ end
43
+
44
+ #Process a map.something call
45
+ #based on the method used
46
+ def process_map exp
47
+ args = exp[3][1..-1]
48
+
49
+ case exp[2]
50
+ when :resource
51
+ process_resource args
52
+ when :resources
53
+ process_resources args
54
+ when :connect, :root
55
+ process_connect args
56
+ else
57
+ process_named_route args
58
+ end
59
+
60
+ exp
61
+ end
62
+
63
+ #Look for map calls that take a block.
64
+ #Otherwise, just do the default processing.
65
+ def process_iter exp
66
+ if exp[1][1] == map or exp[1][1] == nested
67
+ method = exp[1][2]
68
+ case method
69
+ when :namespace
70
+ process_namespace exp
71
+ when :resources, :resource
72
+ process_resources exp[1][3][1..-1]
73
+ process_default exp[3]
74
+ when :with_options
75
+ process_with_options exp
76
+ end
77
+ exp
78
+ else
79
+ super
80
+ end
81
+ end
82
+
83
+ #Process
84
+ # map.resources :x, :controller => :y, :member => ...
85
+ #etc.
86
+ def process_resources exp
87
+ controller = check_for_controller_name exp
88
+ if controller
89
+ self.current_controller = controller
90
+ process_resource_options exp[-1]
91
+ else
92
+ exp.each do |argument|
93
+ if sexp? argument and argument.node_type == :lit
94
+ self.current_controller = exp[0][1]
95
+ add_resources_routes
96
+ process_resource_options exp[-1]
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ #Add default routes
103
+ def add_resources_routes
104
+ @tracker.routes[@current_controller].merge [:index, :new, :create, :show, :edit, :update, :destroy]
105
+ end
106
+
107
+ #Process all the options that might be in the hash passed to
108
+ #map.resource, et al.
109
+ def process_resource_options exp
110
+ if exp.nil? and @with_options
111
+ exp = @with_options
112
+ elsif @with_options
113
+ exp = exp.concat @with_options[1..-1]
114
+ end
115
+ return unless exp.node_type == :hash
116
+
117
+ hash_iterate(exp) do |option, value|
118
+ case option[1]
119
+ when :controller, :requirements, :singular, :path_prefix, :as,
120
+ :path_names, :shallow, :name_prefix
121
+ #should be able to skip
122
+ when :collection, :member, :new
123
+ process_collection value
124
+ when :has_one
125
+ save_controller = current_controller
126
+ process_resource value[1..-1]
127
+ self.current_controller = save_controller
128
+ when :has_many
129
+ save_controller = current_controller
130
+ process_resources value[1..-1]
131
+ self.current_controller = save_controller
132
+ when :only
133
+ process_option_only value
134
+ when :except
135
+ process_option_except value
136
+ else
137
+ raise "Unhandled resource option: #{option}"
138
+ end
139
+ end
140
+ end
141
+
142
+ #Process route option :only => ...
143
+ def process_option_only exp
144
+ routes = @tracker.routes[@current_controller]
145
+ [:index, :new, :create, :show, :edit, :update, :destroy].each do |r|
146
+ routes.delete r
147
+ end
148
+
149
+ if exp.node_type == :array
150
+ exp[1..-1].each do |e|
151
+ routes << e[1]
152
+ end
153
+ end
154
+ end
155
+
156
+ #Process route option :except => ...
157
+ def process_option_except exp
158
+ return unless exp.node_type == :array
159
+ routes = @tracker.routes[@current_controller]
160
+
161
+ exp[1..-1].each do |e|
162
+ routes.delete e[1]
163
+ end
164
+ end
165
+
166
+ # map.resource :x, ..
167
+ def process_resource exp
168
+ controller = check_for_controller_name exp
169
+ if controller
170
+ self.current_controller = controller
171
+ process_resource_options exp[-1]
172
+ else
173
+ exp.each do |argument|
174
+ if argument.node_type == :lit
175
+ self.current_controller = pluralize(exp[0][1].to_s)
176
+ add_resource_routes
177
+ process_resource_options exp[-1]
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ #Add default routes minus :index
184
+ def add_resource_routes
185
+ @tracker.routes[@current_controller].merge [:new, :create, :show, :edit, :update, :destroy]
186
+ end
187
+
188
+ #Process
189
+ # map.connect '/something', :controller => 'blah', :action => 'whatever'
190
+ def process_connect exp
191
+ controller = check_for_controller_name exp
192
+ self.current_controller = controller if controller
193
+
194
+ #Check for default route
195
+ if string? exp[0]
196
+ if exp[0][1] == ":controller/:action/:id"
197
+ @tracker.routes[:allow_all_actions] = exp[0]
198
+ elsif exp[0][1].include? ":action"
199
+ @tracker.routes[@current_controller] = :allow_all_actions
200
+ return
201
+ end
202
+ end
203
+
204
+ #This -seems- redundant, but people might connect actions
205
+ #to a controller which already allows them all
206
+ return if @tracker.routes[@current_controller] == :allow_all_actions
207
+
208
+ exp[-1].each_with_index do |e,i|
209
+ if symbol? e and e[1] == :action
210
+ @tracker.routes[@current_controller] << exp[-1][i + 1][1].to_sym
211
+ return
212
+ end
213
+ end
214
+ end
215
+
216
+ # map.with_options :controller => 'something' do |something|
217
+ # something.resources :blah
218
+ # end
219
+ def process_with_options exp
220
+ @with_options = exp[1][3][-1]
221
+ @nested = Sexp.new(:lvar, exp[2][1])
222
+
223
+ self.current_controller = check_for_controller_name exp[1][3]
224
+
225
+ #process block
226
+ process exp[3]
227
+
228
+ @with_options = nil
229
+ @nested = nil
230
+ end
231
+
232
+ # map.namespace :something do |something|
233
+ # something.resources :blah
234
+ # end
235
+ def process_namespace exp
236
+ call = exp[1]
237
+ formal_args = exp[2]
238
+ block = exp[3]
239
+
240
+ @prefix << camelize(call[3][1][1])
241
+
242
+ @nested = Sexp.new(:lvar, formal_args[1])
243
+
244
+ process block
245
+
246
+ @prefix.pop
247
+ end
248
+
249
+ # map.something_abnormal '/blah', :controller => 'something', :action => 'wohoo'
250
+ def process_named_route exp
251
+ process_connect exp
252
+ end
253
+
254
+ #Process collection option
255
+ # :collection => { :some_action => :http_actions }
256
+ def process_collection exp
257
+ return unless exp.node_type == :hash
258
+ routes = @tracker.routes[@current_controller]
259
+
260
+ hash_iterate(exp) do |action, type|
261
+ routes << action[1]
262
+ end
263
+ end
264
+
265
+ #Manage Controller prefixes
266
+ #@prefix is an Array, but this method returns a string
267
+ #suitable for prefixing onto a controller name.
268
+ def prefix
269
+ if @prefix.length > 0
270
+ @prefix.join("::") << "::"
271
+ else
272
+ ''
273
+ end
274
+ end
275
+
276
+ #Sets the controller name to a proper class name.
277
+ #For example
278
+ # self.current_controller = :session
279
+ # @controller == :SessionController #true
280
+ #
281
+ #Also prepends the prefix if there is one set.
282
+ def current_controller= name
283
+ @current_controller = (prefix + camelize(name) + "Controller").to_sym
284
+ @tracker.routes[@current_controller] ||= Set.new
285
+ end
286
+
287
+ private
288
+
289
+ #Checks an argument list for a hash that has a key :controller.
290
+ #If it does, returns the value.
291
+ #
292
+ #Otherwise, returns nil.
293
+ def check_for_controller_name args
294
+ args.each do |a|
295
+ if hash? a
296
+ hash_iterate(a) do |k, v|
297
+ if k[1] == :controller
298
+ return v[1]
299
+ end
300
+ end
301
+ end
302
+ end
303
+
304
+ nil
305
+ end
306
+ end
307
+
308
+ #This is for a really specific case where a hash is used as arguments
309
+ #to one of the map methods.
310
+ class RouteAliasProcessor < AliasProcessor
311
+
312
+ #This replaces
313
+ # { :some => :hash }.keys
314
+ #with
315
+ # [:some]
316
+ def process_call exp
317
+ process_default exp
318
+
319
+ if hash? exp[1] and exp[2] == :keys
320
+ keys = get_keys exp[1]
321
+ exp.clear
322
+ keys.each_with_index do |e,i|
323
+ exp[i] = e
324
+ end
325
+ end
326
+ exp
327
+ end
328
+
329
+ #Returns an array Sexp containing the keys from the hash
330
+ def get_keys hash
331
+ keys = Sexp.new(:array)
332
+ hash_iterate(hash) do |key, value|
333
+ keys << key
334
+ end
335
+
336
+ keys
337
+ end
338
+ end