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
data/lib/scanner.rb ADDED
@@ -0,0 +1,215 @@
1
+ require 'rubygems'
2
+ begin
3
+ require 'ruby_parser'
4
+ require 'erb'
5
+ require 'processor'
6
+ rescue LoadError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Please install the appropriate dependency."
9
+ exit
10
+ end
11
+
12
+ #Scans the Rails application.
13
+ class Scanner
14
+
15
+ #Pass in path to the root of the Rails application
16
+ def initialize path
17
+ @path = path
18
+ @app_path = path + "/app/"
19
+ @processor = Processor.new
20
+ end
21
+
22
+ #Returns the Tracker generated from the scan
23
+ def tracker
24
+ @processor.tracked_events
25
+ end
26
+
27
+ #Process everything in the Rails application
28
+ def process
29
+ warn "Processing configuration..."
30
+ process_config
31
+ warn "Processing initializers..."
32
+ process_initializers
33
+ warn "Processing libs..."
34
+ process_libs
35
+ warn "Processing routes..."
36
+ process_routes
37
+ warn "Processing templates..."
38
+ process_templates
39
+ warn "Processing models..."
40
+ process_models
41
+ warn "Processing controllers..."
42
+ process_controllers
43
+ tracker
44
+ end
45
+
46
+ #Process config/environment.rb and config/gems.rb
47
+ #
48
+ #Stores parsed information in tracker.config
49
+ def process_config
50
+ @processor.process_config(RubyParser.new.parse(File.read("#@path/config/environment.rb")))
51
+
52
+ if File.exists? "#@path/config/gems.rb"
53
+ @processor.process_config(RubyParser.new.parse(File.read("#@path/config/gems.rb")))
54
+ end
55
+
56
+ if File.exists? "#@path/vendor/plugins/rails_xss"
57
+ tracker.config[:escape_html] = true
58
+ warn "[Notice] Escaping HTML by default"
59
+ end
60
+ end
61
+
62
+ #Process all the .rb files in config/initializers/
63
+ #
64
+ #Adds parsed information to tracker.initializers
65
+ def process_initializers
66
+ Dir.glob(@path + "/config/initializers/**/*.rb").sort.each do |f|
67
+ begin
68
+ @processor.process_initializer(f, RubyParser.new.parse(File.read(f)))
69
+ rescue Racc::ParseError => e
70
+ tracker.error e, "could not parse #{f}"
71
+ rescue Exception => e
72
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
73
+ end
74
+ end
75
+ end
76
+
77
+ #Process all .rb in lib/
78
+ #
79
+ #Adds parsed information to tracker.libs.
80
+ def process_libs
81
+ Dir.glob(@path + "/lib/**/*.rb").sort.each do |f|
82
+ begin
83
+ @processor.process_lib RubyParser.new.parse(File.read(f)), f
84
+ rescue Racc::ParseError => e
85
+ tracker.error e, "could not parse #{f}"
86
+ rescue Exception => e
87
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
88
+ end
89
+ end
90
+ end
91
+
92
+ #Process config/routes.rb
93
+ #
94
+ #Adds parsed information to tracker.routes
95
+ def process_routes
96
+ if File.exists? "#@path/config/routes.rb"
97
+ @processor.process_routes RubyParser.new.parse(File.read("#@path/config/routes.rb"))
98
+ end
99
+ end
100
+
101
+ #Process all .rb files in controllers/
102
+ #
103
+ #Adds processed controllers to tracker.controllers
104
+ def process_controllers
105
+ Dir.glob(@app_path + "/controllers/**/*.rb").sort.each do |f|
106
+ begin
107
+ @processor.process_controller(RubyParser.new.parse(File.read(f)), f)
108
+ rescue Racc::ParseError => e
109
+ tracker.error e, "could not parse #{f}"
110
+ rescue Exception => e
111
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
112
+ end
113
+ end
114
+
115
+ tracker.controllers.each do |name, controller|
116
+ @processor.process_controller_alias controller[:src]
117
+ end
118
+ end
119
+
120
+ #Process all views and partials in views/
121
+ #
122
+ #Adds processed views to tracker.views
123
+ def process_templates
124
+
125
+ views_path = @app_path + "/views/**/*.{html.erb,html.haml,rhtml,js.erb}"
126
+ $stdout.sync = true
127
+ count = 0
128
+
129
+ @initialized_haml = false
130
+ @initialize_erubis = false
131
+
132
+ Dir.glob(views_path).sort.each do |f|
133
+ count += 1
134
+ type = f.match(/.*\.(erb|haml|rhtml)$/)[1].to_sym
135
+ type = :erb if type == :rhtml
136
+ name = template_path_to_name f
137
+
138
+ begin
139
+ if type == :erb
140
+ if tracker.config[:escape_html]
141
+ initialize_erubis unless @initialized_erubis
142
+ src = RailsXSSErubis.new(File.read(f)).src
143
+ elsif tracker.config[:erubis]
144
+ initialize_erubis unless @initialized_erubis
145
+ src = ScannerErubis.new(File.read(f)).src
146
+ else
147
+ src = ERB.new(File.read(f), nil, "-").src
148
+ end
149
+ parsed = RubyParser.new.parse src
150
+ elsif type == :haml
151
+ initialize_haml unless @initialized_haml
152
+ src = Haml::Engine.new(File.read(f),
153
+ :escape_html => !!tracker.config[:escape_html]).precompiled
154
+ parsed = RubyParser.new.parse src
155
+ else
156
+ tracker.error "Unkown template type in #{f}"
157
+ end
158
+
159
+ @processor.process_template(name, parsed, type, nil, f)
160
+
161
+ rescue Racc::ParseError => e
162
+ tracker.error e, "could not parse #{f}"
163
+ rescue Haml::Error => e
164
+ tracker.error e, ["While compiling HAML in #{f}"] << e.backtrace
165
+ rescue Exception => e
166
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
167
+ end
168
+ end
169
+
170
+ tracker.templates.keys.dup.each do |name|
171
+ @processor.process_template_alias tracker.templates[name]
172
+ end
173
+
174
+ end
175
+
176
+ #Convert path/filename to view name
177
+ #
178
+ # views/test/something.html.erb -> test/something
179
+ def template_path_to_name path
180
+ names = path.split("/")
181
+ names.last.gsub!(/(\.(html|js)\..*|\.rhtml)$/, '')
182
+ names[(names.index("views") + 1)..-1].join("/").to_sym
183
+ end
184
+
185
+ #Process all the .rb files in models/
186
+ #
187
+ #Adds the processed models to tracker.models
188
+ def process_models
189
+ Dir.glob(@app_path + "/models/*.rb").sort.each do |f|
190
+ begin
191
+ @processor.process_model(RubyParser.new.parse(File.read(f)), f)
192
+ rescue Racc::ParseError => e
193
+ tracker.error e, "could not parse #{f}"
194
+ rescue Exception => e
195
+ tracker.error e.exception(e.message + "\nWhile processing #{f}"), e.backtrace
196
+ end
197
+ end
198
+ end
199
+
200
+ private
201
+
202
+ def initialize_haml
203
+ require 'haml'
204
+ @initialized_haml = true
205
+ rescue LoadError => e
206
+ $stderr.puts e.message
207
+ $stderr.puts "Please install Haml."
208
+ exit!
209
+ end
210
+
211
+ def initialize_erubis
212
+ require 'scanner_erubis'
213
+ @initialized_erubis = true
214
+ end
215
+ end
@@ -0,0 +1,43 @@
1
+ begin
2
+ require 'erubis'
3
+ rescue LoadError => e
4
+ $stderr.puts e.message
5
+ $stderr.puts "Please install Erubis."
6
+ exit!
7
+ end
8
+
9
+ #This is from the rails_xss plugin,
10
+ #except we don't care about plain text.
11
+ class RailsXSSErubis < ::Erubis::Eruby
12
+ include Erubis::NoTextEnhancer
13
+
14
+ #Initializes output buffer.
15
+ def add_preamble(src)
16
+ src << "@output_buffer = ActionView::SafeBuffer.new;\n"
17
+ end
18
+
19
+ #This does nothing.
20
+ def add_text(src, text)
21
+ # src << "@output_buffer << ('" << escape_text(text) << "'.html_safe!);"
22
+ end
23
+
24
+ #Add an expression to the output buffer _without_ escaping.
25
+ def add_expr_literal(src, code)
26
+ src << '@output_buffer << ((' << code << ').to_s);'
27
+ end
28
+
29
+ #Add an expression to the output buffer after escaping it.
30
+ def add_expr_escaped(src, code)
31
+ src << '@output_buffer << ' << escaped_expr(code) << ';'
32
+ end
33
+
34
+ #Add code to output buffer.
35
+ def add_postamble(src)
36
+ src << '@output_buffer.to_s'
37
+ end
38
+ end
39
+
40
+ #Erubis processor which ignores any output which is plain text.
41
+ class ScannerErubis < Erubis::Eruby
42
+ include Erubis::NoTextEnhancer
43
+ end
data/lib/tracker.rb ADDED
@@ -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
data/lib/util.rb ADDED
@@ -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