brakeman 0.0.2

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.
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