brakeman 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/FEATURES +16 -0
  2. data/README.md +112 -0
  3. data/WARNING_TYPES +69 -0
  4. data/bin/brakeman +266 -0
  5. data/lib/checks.rb +67 -0
  6. data/lib/checks/base_check.rb +338 -0
  7. data/lib/checks/check_cross_site_scripting.rb +216 -0
  8. data/lib/checks/check_default_routes.rb +29 -0
  9. data/lib/checks/check_evaluation.rb +29 -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 +25 -0
  13. data/lib/checks/check_mass_assignment.rb +72 -0
  14. data/lib/checks/check_model_attributes.rb +36 -0
  15. data/lib/checks/check_redirect.rb +98 -0
  16. data/lib/checks/check_render.rb +65 -0
  17. data/lib/checks/check_send_file.rb +15 -0
  18. data/lib/checks/check_session_settings.rb +36 -0
  19. data/lib/checks/check_sql.rb +124 -0
  20. data/lib/checks/check_validation_regex.rb +60 -0
  21. data/lib/format/style.css +105 -0
  22. data/lib/processor.rb +83 -0
  23. data/lib/processors/alias_processor.rb +384 -0
  24. data/lib/processors/base_processor.rb +235 -0
  25. data/lib/processors/config_processor.rb +146 -0
  26. data/lib/processors/controller_alias_processor.rb +222 -0
  27. data/lib/processors/controller_processor.rb +175 -0
  28. data/lib/processors/erb_template_processor.rb +84 -0
  29. data/lib/processors/erubis_template_processor.rb +62 -0
  30. data/lib/processors/haml_template_processor.rb +115 -0
  31. data/lib/processors/lib/find_call.rb +176 -0
  32. data/lib/processors/lib/find_model_call.rb +39 -0
  33. data/lib/processors/lib/processor_helper.rb +36 -0
  34. data/lib/processors/lib/render_helper.rb +118 -0
  35. data/lib/processors/library_processor.rb +117 -0
  36. data/lib/processors/model_processor.rb +125 -0
  37. data/lib/processors/output_processor.rb +204 -0
  38. data/lib/processors/params_processor.rb +77 -0
  39. data/lib/processors/route_processor.rb +338 -0
  40. data/lib/processors/template_alias_processor.rb +86 -0
  41. data/lib/processors/template_processor.rb +55 -0
  42. data/lib/report.rb +628 -0
  43. data/lib/scanner.rb +232 -0
  44. data/lib/tracker.rb +144 -0
  45. data/lib/util.rb +141 -0
  46. data/lib/warning.rb +97 -0
  47. metadata +191 -0
@@ -0,0 +1,232 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'ruby_parser'
4
+ require 'haml'
5
+ require 'erb'
6
+ require 'erubis'
7
+ require 'processor'
8
+ rescue LoadError => e
9
+ $stderr.puts e.message
10
+ $stderr.puts "Please install the appropriate dependency."
11
+ exit
12
+ end
13
+
14
+ #Erubis processor which ignores any output which is plain text.
15
+ class ScannerErubis < Erubis::Eruby
16
+ include Erubis::NoTextEnhancer
17
+ end
18
+
19
+ #Scans the Rails application.
20
+ class Scanner
21
+
22
+ #Pass in path to the root of the Rails application
23
+ def initialize path
24
+ @path = path
25
+ @app_path = path + "/app/"
26
+ @processor = Processor.new
27
+ end
28
+
29
+ #Returns the Tracker generated from the scan
30
+ def tracker
31
+ @processor.tracked_events
32
+ end
33
+
34
+ #Process everything in the Rails application
35
+ def process
36
+ warn "Processing configuration..."
37
+ process_config
38
+ warn "Processing initializers..."
39
+ process_initializers
40
+ warn "Processing libs..."
41
+ process_libs
42
+ warn "Processing routes..."
43
+ process_routes
44
+ warn "Processing templates..."
45
+ process_templates
46
+ warn "Processing models..."
47
+ process_models
48
+ warn "Processing controllers..."
49
+ process_controllers
50
+ tracker
51
+ end
52
+
53
+ #Process config/environment.rb and config/gems.rb
54
+ #
55
+ #Stores parsed information in tracker.config
56
+ def process_config
57
+ @processor.process_config(RubyParser.new.parse(File.read("#@path/config/environment.rb")))
58
+
59
+ if File.exists? "#@path/config/gems.rb"
60
+ @processor.process_config(RubyParser.new.parse(File.read("#@path/config/gems.rb")))
61
+ end
62
+
63
+ if File.exists? "#@path/vendor/plugins/rails_xss"
64
+ tracker.config[:escape_html] = true
65
+ warn "[Notice] Escaping HTML by default"
66
+ end
67
+ end
68
+
69
+ #Process all the .rb files in config/initializers/
70
+ #
71
+ #Adds parsed information to tracker.initializers
72
+ def process_initializers
73
+ Dir.glob(@path + "/config/initializers/**/*.rb").sort.each do |f|
74
+ begin
75
+ @processor.process_initializer(f, RubyParser.new.parse(File.read(f)))
76
+ rescue Racc::ParseError => e
77
+ tracker.error e, "could not parse #{f}"
78
+ rescue Exception => e
79
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
80
+ end
81
+ end
82
+ end
83
+
84
+ #Process all .rb in lib/
85
+ #
86
+ #Adds parsed information to tracker.libs.
87
+ def process_libs
88
+ Dir.glob(@path + "/lib/**/*.rb").sort.each do |f|
89
+ begin
90
+ @processor.process_lib RubyParser.new.parse(File.read(f)), f
91
+ rescue Racc::ParseError => e
92
+ tracker.error e, "could not parse #{f}"
93
+ rescue Exception => e
94
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
95
+ end
96
+ end
97
+ end
98
+
99
+ #Process config/routes.rb
100
+ #
101
+ #Adds parsed information to tracker.routes
102
+ def process_routes
103
+ if File.exists? "#@path/config/routes.rb"
104
+ @processor.process_routes RubyParser.new.parse(File.read("#@path/config/routes.rb"))
105
+ end
106
+ end
107
+
108
+ #Process all .rb files in controllers/
109
+ #
110
+ #Adds processed controllers to tracker.controllers
111
+ def process_controllers
112
+ Dir.glob(@app_path + "/controllers/**/*.rb").sort.each do |f|
113
+ begin
114
+ @processor.process_controller(RubyParser.new.parse(File.read(f)), f)
115
+ rescue Racc::ParseError => e
116
+ tracker.error e, "could not parse #{f}"
117
+ rescue Exception => e
118
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
119
+ end
120
+ end
121
+
122
+ tracker.controllers.each do |name, controller|
123
+ @processor.process_controller_alias controller[:src]
124
+ end
125
+ end
126
+
127
+ #Process all views and partials in views/
128
+ #
129
+ #Adds processed views to tracker.views
130
+ def process_templates
131
+
132
+ views_path = @app_path + "/views/**/*.{html.erb,html.haml,rhtml,js.erb}"
133
+ $stdout.sync = true
134
+ count = 0
135
+
136
+ Dir.glob(views_path).sort.each do |f|
137
+ count += 1
138
+ type = f.match(/.*\.(erb|haml|rhtml)$/)[1].to_sym
139
+ type = :erb if type == :rhtml
140
+ name = template_path_to_name f
141
+
142
+ begin
143
+ if type == :erb
144
+ if tracker.config[:escape_html]
145
+ src = RailsXSSErubis.new(File.read(f)).src
146
+ elsif tracker.config[:erubis]
147
+ src = ScannerErubis.new(File.read(f)).src
148
+ else
149
+ src = ERB.new(File.read(f), nil, "-").src
150
+ end
151
+ parsed = RubyParser.new.parse src
152
+ elsif type == :haml
153
+ src = Haml::Engine.new(File.read(f),
154
+ :escape_html => !!tracker.config[:escape_html]).precompiled
155
+ parsed = RubyParser.new.parse src
156
+ else
157
+ tracker.error "Unkown template type in #{f}"
158
+ end
159
+
160
+ @processor.process_template(name, parsed, type, nil, f)
161
+
162
+ rescue Racc::ParseError => e
163
+ tracker.error e, "could not parse #{f}"
164
+ rescue Haml::Error => e
165
+ tracker.error e, ["While compiling HAML in #{f}"] << e.backtrace
166
+ rescue Exception => e
167
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
168
+ end
169
+ end
170
+
171
+ tracker.templates.keys.dup.each do |name|
172
+ @processor.process_template_alias tracker.templates[name]
173
+ end
174
+
175
+ end
176
+
177
+ #Convert path/filename to view name
178
+ #
179
+ # views/test/something.html.erb -> test/something
180
+ def template_path_to_name path
181
+ names = path.split("/")
182
+ names.last.gsub!(/(\.(html|js)\..*|\.rhtml)$/, '')
183
+ names[(names.index("views") + 1)..-1].join("/").to_sym
184
+ end
185
+
186
+ #Process all the .rb files in models/
187
+ #
188
+ #Adds the processed models to tracker.models
189
+ def process_models
190
+ Dir.glob(@app_path + "/models/*.rb").sort.each do |f|
191
+ begin
192
+ @processor.process_model(RubyParser.new.parse(File.read(f)), f)
193
+ rescue Racc::ParseError => e
194
+ tracker.error e, "could not parse #{f}"
195
+ rescue Exception => e
196
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ #This is from the rails_xss plugin,
203
+ #except we don't care about plain text.
204
+ class RailsXSSErubis < ::Erubis::Eruby
205
+ include Erubis::NoTextEnhancer
206
+
207
+ #Initializes output buffer.
208
+ def add_preamble(src)
209
+ src << "@output_buffer = ActionView::SafeBuffer.new;\n"
210
+ end
211
+
212
+ #This does nothing.
213
+ def add_text(src, text)
214
+ # src << "@output_buffer << ('" << escape_text(text) << "'.html_safe!);"
215
+ end
216
+
217
+ #Add an expression to the output buffer _without_ escaping.
218
+ def add_expr_literal(src, code)
219
+ src << '@output_buffer << ((' << code << ').to_s);'
220
+ end
221
+
222
+ #Add an expression to the output buffer after escaping it.
223
+ def add_expr_escaped(src, code)
224
+ src << '@output_buffer << ' << escaped_expr(code) << ';'
225
+ end
226
+
227
+ #Add code to output buffer.
228
+ def add_postamble(src)
229
+ src << '@output_buffer.to_s'
230
+ end
231
+ end
232
+
@@ -0,0 +1,144 @@
1
+ require 'set'
2
+ require 'checks'
3
+ require 'report'
4
+ require 'processors/lib/find_call'
5
+ require 'processors/lib/find_model_call'
6
+
7
+ #The Tracker keeps track of all the processed information.
8
+ class Tracker
9
+ attr_accessor :controllers, :templates, :models, :errors,
10
+ :checks, :initializers, :config, :routes, :processor, :libs,
11
+ :template_cache
12
+
13
+ #Place holder when there should be a model, but it is not
14
+ #clear what model it will be.
15
+ UNKNOWN_MODEL = :BrakemanUnresolvedModel
16
+
17
+ #Creates a new Tracker.
18
+ #
19
+ #The Processor argument is only used by other Processors
20
+ #that might need to access it.
21
+ def initialize processor = nil
22
+ @processor = processor
23
+ @config = {}
24
+ @templates = {}
25
+ @controllers = {}
26
+ #Initialize models with the unknown model so
27
+ #we can match models later without knowing precisely what
28
+ #class they are.
29
+ @models = { UNKNOWN_MODEL => { :name => UNKNOWN_MODEL,
30
+ :parent => nil,
31
+ :includes => [],
32
+ :public => {},
33
+ :private => {},
34
+ :protected => {},
35
+ :options => {} } }
36
+ @routes = {}
37
+ @initializers = {}
38
+ @errors = []
39
+ @libs = {}
40
+ @checks = nil
41
+ @template_cache = Set.new
42
+ end
43
+
44
+ #Add an error to the list. If no backtrace is given,
45
+ #the one from the exception will be used.
46
+ def error exception, backtrace = nil
47
+ backtrace ||= exception.backtrace
48
+ unless backtrace.is_a? Array
49
+ backtrace = [ backtrace ]
50
+ end
51
+
52
+ @errors << { :error => exception.to_s.gsub("\n", " "), :backtrace => backtrace }
53
+ end
54
+
55
+ #Run a set of checks on the current information. Results will be stored
56
+ #in Tracker#checks.
57
+ def run_checks
58
+ @checks = Checks.run_checks(self)
59
+ end
60
+
61
+ #Iterate over all methods in controllers and models.
62
+ def each_method
63
+ [self.controllers, self.models].each do |set|
64
+ set.each do |set_name, info|
65
+ [:private, :public, :protected].each do |visibility|
66
+ info[visibility].each do |method_name, definition|
67
+ if definition.node_type == :selfdef
68
+ method_name = "#{definition[1]}.#{method_name}"
69
+ end
70
+
71
+ yield definition, set_name, method_name
72
+
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ #Iterates over each template, yielding the name and the template.
80
+ #Prioritizes templates which have been rendered.
81
+ def each_template
82
+ if @processed.nil?
83
+ @processed, @rest = templates.keys.partition { |k| k.to_s.include? "." }
84
+ end
85
+
86
+ @processed.each do |k|
87
+ yield k, templates[k]
88
+ end
89
+
90
+ @rest.each do |k|
91
+ yield k, templates[k]
92
+ end
93
+ end
94
+
95
+ #Find a method call.
96
+ #
97
+ #See FindCall for details on arguments.
98
+ def find_call target, method
99
+ finder = FindCall.new target, method
100
+
101
+ self.each_method do |definition, set_name, method_name|
102
+ finder.process_source definition, set_name, method_name
103
+ end
104
+
105
+ self.each_template do |name, template|
106
+ finder.process_source template[:src], nil, nil, template
107
+ end
108
+
109
+ finder.matches
110
+ end
111
+
112
+ #Finds method call on models.
113
+ #
114
+ #See FindCall for details on arguments.
115
+ def find_model_find target
116
+ finder = FindModelCall.new target
117
+
118
+ self.each_method do |definition, set_name, method_name|
119
+ finder.process_source definition, set_name, method_name
120
+ end
121
+
122
+ self.each_template do |name, template|
123
+ finder.process_source template[:src], nil, nil, template
124
+ end
125
+
126
+ finder.matches
127
+ end
128
+
129
+ #Similar to Tracker#find_call, but searches the initializers
130
+ def check_initializers target, method
131
+ finder = FindCall.new target, method
132
+
133
+ initializers.each do |name, initializer|
134
+ finder.process_source initializer
135
+ end
136
+
137
+ finder.matches
138
+ end
139
+
140
+ #Returns a Report with this Tracker's information
141
+ def report
142
+ Report.new(self)
143
+ end
144
+ end
@@ -0,0 +1,141 @@
1
+ require 'sexp_processor'
2
+ require 'set'
3
+ require 'active_support/inflector'
4
+
5
+ #This is a mixin containing utility methods.
6
+ module Util
7
+
8
+ QUERY_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request, Sexp.new(:arglist)), :query_parameters, Sexp.new(:arglist))
9
+
10
+ PATH_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request, Sexp.new(:arglist)), :path_parameters, Sexp.new(:arglist))
11
+
12
+ REQUEST_PARAMETERS = Sexp.new(:call, Sexp.new(:call, nil, :request, Sexp.new(:arglist)), :request_parameters, Sexp.new(:arglist))
13
+
14
+ PARAMETERS = Sexp.new(:call, nil, :params, Sexp.new(:arglist))
15
+
16
+ COOKIES = Sexp.new(:call, nil, :cookies, Sexp.new(:arglist))
17
+
18
+ SESSION = Sexp.new(:call, nil, :session, Sexp.new(:arglist))
19
+
20
+ ALL_PARAMETERS = Set.new([PARAMETERS, QUERY_PARAMETERS, PATH_PARAMETERS, REQUEST_PARAMETERS])
21
+
22
+ #Convert a string from "something_like_this" to "SomethingLikeThis"
23
+ #
24
+ #Taken from ActiveSupport.
25
+ def camelize lower_case_and_underscored_word
26
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
27
+ end
28
+
29
+ #Convert a string from "Something::LikeThis" to "something/like_this"
30
+ #
31
+ #Taken from ActiveSupport.
32
+ def underscore camel_cased_word
33
+ camel_cased_word.to_s.gsub(/::/, '/').
34
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
35
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
36
+ tr("-", "_").
37
+ downcase
38
+ end
39
+
40
+ #Use ActiveSupport::Inflector to pluralize a word.
41
+ def pluralize word
42
+ ActiveSupport::Inflector.pluralize word
43
+ end
44
+
45
+ #Takes an Sexp like
46
+ # (:hash, (:lit, :key), (:str, "value"))
47
+ #and yields the key and value pairs to the given block.
48
+ #
49
+ #For example:
50
+ #
51
+ # h = Sexp.new(:hash, (:lit, :name), (:str, "bob"), (:lit, :name), (:str, "jane"))
52
+ # names = []
53
+ # hash_iterate(h) do |key, value|
54
+ # if symbol? key and key[1] == :name
55
+ # names << value[1]
56
+ # end
57
+ # end
58
+ # names #["bob"]
59
+ def hash_iterate hash
60
+ 1.step(hash.length - 1, 2) do |i|
61
+ yield hash[i], hash[i + 1]
62
+ end
63
+ end
64
+
65
+ #Insert value into Hash Sexp
66
+ def hash_insert hash, key, value
67
+ index = 0
68
+ hash_iterate hash.dup do |k,v|
69
+ index += 1
70
+ if k == key and index % 2 == 1
71
+ hash[index + 1] = value
72
+ return hash
73
+ end
74
+ end
75
+
76
+ hash << key << value
77
+
78
+ hash
79
+ end
80
+
81
+ #Adds params, session, and cookies to environment
82
+ #so they can be replaced by their respective Sexps.
83
+ def set_env_defaults
84
+ @env[PARAMETERS] = Sexp.new(:params)
85
+ @env[SESSION] = Sexp.new(:session)
86
+ @env[COOKIES] = Sexp.new(:cookies)
87
+ end
88
+
89
+ #Check if _exp_ represents a hash: s(:hash, {...})
90
+ #This also includes pseudo hashes params, session, and cookies.
91
+ def hash? exp
92
+ exp.is_a? Sexp and (exp.node_type == :hash or
93
+ exp.node_type == :params or
94
+ exp.node_type == :session or
95
+ exp.node_type == :cookies)
96
+ end
97
+
98
+ #Check if _exp_ represents an array: s(:array, [...])
99
+ def array? exp
100
+ exp.is_a? Sexp and exp.node_type == :array
101
+ end
102
+
103
+ #Check if _exp_ represents a String: s(:str, "...")
104
+ def string? exp
105
+ exp.is_a? Sexp and exp.node_type == :str
106
+ end
107
+
108
+ #Check if _exp_ represents a Symbol: s(:lit, :...)
109
+ def symbol? exp
110
+ exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Symbol
111
+ end
112
+
113
+ #Check if _exp_ represents a method call: s(:call, ...)
114
+ def call? exp
115
+ exp.is_a? Sexp and exp.node_type == :call
116
+ end
117
+
118
+ #Check if _exp_ represents a Regexp: s(:lit, /.../)
119
+ def regexp? exp
120
+ exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Regexp
121
+ end
122
+
123
+ #Check if _exp_ represents an Integer: s(:lit, ...)
124
+ def integer? exp
125
+ exp.is_a? Sexp and exp.node_type == :lit and exp[1].is_a? Integer
126
+ end
127
+
128
+ #Check if _exp_ is a params hash
129
+ def params? exp
130
+ exp.is_a? Sexp and exp.node_type == :params
131
+ end
132
+
133
+ def cookies? exp
134
+ exp.is_a? Sexp and exp.node_type == :cookies
135
+ end
136
+
137
+ #Check if _exp_ is a Sexp.
138
+ def sexp? exp
139
+ exp.is_a? Sexp
140
+ end
141
+ end