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.
- data/FEATURES +16 -0
- data/README.md +112 -0
- data/WARNING_TYPES +69 -0
- data/bin/brakeman +266 -0
- data/lib/checks.rb +67 -0
- data/lib/checks/base_check.rb +338 -0
- data/lib/checks/check_cross_site_scripting.rb +216 -0
- data/lib/checks/check_default_routes.rb +29 -0
- data/lib/checks/check_evaluation.rb +29 -0
- data/lib/checks/check_execute.rb +110 -0
- data/lib/checks/check_file_access.rb +46 -0
- data/lib/checks/check_forgery_setting.rb +25 -0
- data/lib/checks/check_mass_assignment.rb +72 -0
- data/lib/checks/check_model_attributes.rb +36 -0
- data/lib/checks/check_redirect.rb +98 -0
- data/lib/checks/check_render.rb +65 -0
- data/lib/checks/check_send_file.rb +15 -0
- data/lib/checks/check_session_settings.rb +36 -0
- data/lib/checks/check_sql.rb +124 -0
- data/lib/checks/check_validation_regex.rb +60 -0
- data/lib/format/style.css +105 -0
- data/lib/processor.rb +83 -0
- data/lib/processors/alias_processor.rb +384 -0
- data/lib/processors/base_processor.rb +235 -0
- data/lib/processors/config_processor.rb +146 -0
- data/lib/processors/controller_alias_processor.rb +222 -0
- data/lib/processors/controller_processor.rb +175 -0
- data/lib/processors/erb_template_processor.rb +84 -0
- data/lib/processors/erubis_template_processor.rb +62 -0
- data/lib/processors/haml_template_processor.rb +115 -0
- data/lib/processors/lib/find_call.rb +176 -0
- data/lib/processors/lib/find_model_call.rb +39 -0
- data/lib/processors/lib/processor_helper.rb +36 -0
- data/lib/processors/lib/render_helper.rb +118 -0
- data/lib/processors/library_processor.rb +117 -0
- data/lib/processors/model_processor.rb +125 -0
- data/lib/processors/output_processor.rb +204 -0
- data/lib/processors/params_processor.rb +77 -0
- data/lib/processors/route_processor.rb +338 -0
- data/lib/processors/template_alias_processor.rb +86 -0
- data/lib/processors/template_processor.rb +55 -0
- data/lib/report.rb +628 -0
- data/lib/scanner.rb +232 -0
- data/lib/tracker.rb +144 -0
- data/lib/util.rb +141 -0
- data/lib/warning.rb +97 -0
- metadata +191 -0
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'processors/base_processor'
|
2
|
+
require 'processors/alias_processor'
|
3
|
+
|
4
|
+
#Replace block variable in
|
5
|
+
#
|
6
|
+
# Rails::Initializer.run |config|
|
7
|
+
#
|
8
|
+
#with this value so we can keep track of it.
|
9
|
+
RAILS_CONFIG = Sexp.new(:const, :"!BRAKEMAN_RAILS_CONFIG")
|
10
|
+
|
11
|
+
#Processes configuration. Results are put in tracker.config.
|
12
|
+
#
|
13
|
+
#Configuration of Rails via Rails::Initializer are stored in tracker.config[:rails].
|
14
|
+
#For example:
|
15
|
+
#
|
16
|
+
# Rails::Initializer.run |config|
|
17
|
+
# config.action_controller.session_store = :cookie_store
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
#will be stored in
|
21
|
+
#
|
22
|
+
# tracker.config[:rails][:action_controller][:session_store]
|
23
|
+
#
|
24
|
+
#Values for tracker.config[:rails] will still be Sexps.
|
25
|
+
class ConfigProcessor < BaseProcessor
|
26
|
+
def initialize *args
|
27
|
+
super
|
28
|
+
@tracker.config[:rails] ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
#Use this method to process configuration file
|
32
|
+
def process_config src
|
33
|
+
res = ConfigAliasProcessor.new.process_safely(src)
|
34
|
+
process res
|
35
|
+
end
|
36
|
+
|
37
|
+
#Check if config is set to use Erubis
|
38
|
+
def process_call exp
|
39
|
+
target = exp[1]
|
40
|
+
target = process target if sexp? target
|
41
|
+
|
42
|
+
if exp[2] == :gem and exp[3][1][1] == "erubis"
|
43
|
+
warn "[Notice] Using Erubis for ERB templates"
|
44
|
+
@tracker.config[:erubis] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
exp
|
48
|
+
end
|
49
|
+
|
50
|
+
#Look for configuration settings
|
51
|
+
def process_attrasgn exp
|
52
|
+
if exp[1] == RAILS_CONFIG
|
53
|
+
#Get rid of '=' at end
|
54
|
+
attribute = exp[2].to_s[0..-2].to_sym
|
55
|
+
if exp[3].length > 2
|
56
|
+
#Multiple arguments?...not sure if this will ever happen
|
57
|
+
@tracker.config[:rails][exp[2]] = exp[3][1..-1]
|
58
|
+
else
|
59
|
+
@tracker.config[:rails][exp[2]] = exp[3][1]
|
60
|
+
end
|
61
|
+
elsif include_rails_config? exp
|
62
|
+
options = get_rails_config exp
|
63
|
+
level = @tracker.config[:rails]
|
64
|
+
options[0..-2].each do |o|
|
65
|
+
level[o] ||= {}
|
66
|
+
level = level[o]
|
67
|
+
end
|
68
|
+
|
69
|
+
level[options.last] = exp[3][1]
|
70
|
+
end
|
71
|
+
|
72
|
+
exp
|
73
|
+
end
|
74
|
+
|
75
|
+
#Check for Rails version
|
76
|
+
def process_cdecl exp
|
77
|
+
#Set Rails version required
|
78
|
+
if exp[1] == :RAILS_GEM_VERSION
|
79
|
+
@tracker.config[:rails_version] = exp[2][1]
|
80
|
+
end
|
81
|
+
|
82
|
+
exp
|
83
|
+
end
|
84
|
+
|
85
|
+
#Check if an expression includes a call to set Rails config
|
86
|
+
def include_rails_config? exp
|
87
|
+
target = exp[1]
|
88
|
+
if call? target
|
89
|
+
if target[1] == RAILS_CONFIG
|
90
|
+
true
|
91
|
+
else
|
92
|
+
include_rails_config? target
|
93
|
+
end
|
94
|
+
elsif target == RAILS_CONFIG
|
95
|
+
true
|
96
|
+
else
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
#Returns an array of symbols for each 'level' in the config
|
102
|
+
#
|
103
|
+
# config.action_controller.session_store = :cookie
|
104
|
+
#
|
105
|
+
#becomes
|
106
|
+
#
|
107
|
+
# [:action_controller, :session_store]
|
108
|
+
def get_rails_config exp
|
109
|
+
if sexp? exp and exp.node_type == :attrasgn
|
110
|
+
attribute = exp[2].to_s[0..-2].to_sym
|
111
|
+
get_rails_config(exp[1]) << attribute
|
112
|
+
elsif call? exp
|
113
|
+
if exp[1] == RAILS_CONFIG
|
114
|
+
[exp[2]]
|
115
|
+
else
|
116
|
+
get_rails_config(exp[1]) << exp[2]
|
117
|
+
end
|
118
|
+
else
|
119
|
+
raise "WHAT"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#This is necessary to replace block variable so we can track config settings
|
125
|
+
class ConfigAliasProcessor < AliasProcessor
|
126
|
+
|
127
|
+
RAILS_INIT = Sexp.new(:colon2, Sexp.new(:const, :Rails), :Initializer)
|
128
|
+
|
129
|
+
#Look for a call to
|
130
|
+
#
|
131
|
+
# Rails::Initializer.run do |config|
|
132
|
+
# ...
|
133
|
+
# end
|
134
|
+
#
|
135
|
+
#and replace config with RAILS_CONFIG
|
136
|
+
def process_iter exp
|
137
|
+
target = exp[1][1]
|
138
|
+
method = exp[1][2]
|
139
|
+
|
140
|
+
if sexp? target and target == RAILS_INIT and method == :run
|
141
|
+
exp[2][2] = RAILS_CONFIG
|
142
|
+
end
|
143
|
+
|
144
|
+
process_default exp
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'processors/alias_processor'
|
2
|
+
require 'processors/lib/render_helper'
|
3
|
+
|
4
|
+
#Processes aliasing in controllers, but includes following
|
5
|
+
#renders in routes and putting variables into templates
|
6
|
+
class ControllerAliasProcessor < AliasProcessor
|
7
|
+
include RenderHelper
|
8
|
+
|
9
|
+
def initialize tracker
|
10
|
+
super()
|
11
|
+
@tracker = tracker
|
12
|
+
@rendered = false
|
13
|
+
@current_class = @current_module = @current_method = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
#Processes a class which is probably a controller.
|
17
|
+
def process_class exp
|
18
|
+
@current_class = class_name(exp[1])
|
19
|
+
if @current_module
|
20
|
+
@current_class = (@current_module + "::" + @current_class.to_s).to_sym
|
21
|
+
end
|
22
|
+
|
23
|
+
process_default exp
|
24
|
+
end
|
25
|
+
|
26
|
+
#Processes a method definition, which may include
|
27
|
+
#processing any rendered templates.
|
28
|
+
def process_methdef exp
|
29
|
+
set_env_defaults
|
30
|
+
is_route = route? exp[1]
|
31
|
+
other_method = @current_method
|
32
|
+
@current_method = exp[1]
|
33
|
+
@rendered = false if is_route
|
34
|
+
|
35
|
+
env.scope do
|
36
|
+
|
37
|
+
if is_route
|
38
|
+
before_filter_list(@current_method, @current_class).each do |f|
|
39
|
+
process_before_filter f
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
process exp[3]
|
44
|
+
|
45
|
+
if is_route and not @rendered
|
46
|
+
process_default_render exp
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@current_method = other_method
|
51
|
+
exp
|
52
|
+
end
|
53
|
+
|
54
|
+
#Look for calls to head()
|
55
|
+
def process_call exp
|
56
|
+
exp = super
|
57
|
+
|
58
|
+
if exp[2] == :head
|
59
|
+
@rendered = true
|
60
|
+
end
|
61
|
+
exp
|
62
|
+
end
|
63
|
+
|
64
|
+
#Check for +respond_to+
|
65
|
+
def process_call_with_block exp
|
66
|
+
process_default exp
|
67
|
+
|
68
|
+
if exp[1][2] == :respond_to
|
69
|
+
@rendered = true
|
70
|
+
end
|
71
|
+
|
72
|
+
exp
|
73
|
+
end
|
74
|
+
|
75
|
+
#Processes a call to a before filter.
|
76
|
+
#Basically, adds any instance variable assignments to the environment.
|
77
|
+
#TODO: method arguments?
|
78
|
+
def process_before_filter name
|
79
|
+
method = find_method name, @current_class
|
80
|
+
|
81
|
+
if method.nil?
|
82
|
+
warn "[Notice] Could not find filter #{name}" if OPTIONS[:debug]
|
83
|
+
return
|
84
|
+
end
|
85
|
+
|
86
|
+
processor = AliasProcessor.new
|
87
|
+
processor.process_safely(method[3])
|
88
|
+
|
89
|
+
processor.only_ivars.all.each do |variable, value|
|
90
|
+
env[variable] = value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#Processes the default template for the current action
|
95
|
+
def process_default_render exp
|
96
|
+
process_template template_name, nil
|
97
|
+
end
|
98
|
+
|
99
|
+
#Process template and add the current class and method name as called_from info
|
100
|
+
def process_template name, args
|
101
|
+
super name, args, "#@current_class##@current_method"
|
102
|
+
end
|
103
|
+
|
104
|
+
#Turns a method name into a template name
|
105
|
+
def template_name name = nil
|
106
|
+
name ||= @current_method
|
107
|
+
name = name.to_s
|
108
|
+
if name.include? "/"
|
109
|
+
name
|
110
|
+
else
|
111
|
+
controller = @current_class.to_s.gsub("Controller", "")
|
112
|
+
controller.gsub!("::", "/")
|
113
|
+
underscore(controller + "/" + name.to_s)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
#Returns true if the given method name is also a route
|
118
|
+
def route? method
|
119
|
+
return true if @tracker.routes[:allow_all_actions]
|
120
|
+
routes = @tracker.routes[@current_class]
|
121
|
+
routes and (routes == :allow_all_actions or routes.include? method)
|
122
|
+
end
|
123
|
+
|
124
|
+
#Get list of filters, including those that are inherited
|
125
|
+
def before_filter_list method, klass
|
126
|
+
controller = @tracker.controllers[klass]
|
127
|
+
filters = []
|
128
|
+
|
129
|
+
while controller
|
130
|
+
filters = get_before_filters(method, controller) + filters
|
131
|
+
|
132
|
+
controller = @tracker.controllers[controller[:parent]]
|
133
|
+
end
|
134
|
+
|
135
|
+
filters
|
136
|
+
end
|
137
|
+
|
138
|
+
#Returns an array of filter names
|
139
|
+
def get_before_filters method, controller
|
140
|
+
filters = []
|
141
|
+
return filters unless controller[:options]
|
142
|
+
filter_list = controller[:options][:before_filters]
|
143
|
+
return filters unless filter_list
|
144
|
+
|
145
|
+
filter_list.each do |filter|
|
146
|
+
f = before_filter_to_hash filter
|
147
|
+
if f[:all] or
|
148
|
+
(f[:only] == method) or
|
149
|
+
(f[:only].is_a? Array and f[:only].include? method) or
|
150
|
+
(f[:except] == method) or
|
151
|
+
(f[:except].is_a? Array and not f[:except].include? method)
|
152
|
+
|
153
|
+
filters.concat f[:methods]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
filters
|
158
|
+
end
|
159
|
+
|
160
|
+
#Returns a before filter as a hash table
|
161
|
+
def before_filter_to_hash args
|
162
|
+
filter = {}
|
163
|
+
|
164
|
+
#Process args for the uncommon but possible situation
|
165
|
+
#in which some variables are used in the filter.
|
166
|
+
args.each do |a|
|
167
|
+
if sexp? a
|
168
|
+
a = process_default a
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
filter[:methods] = [args[0][1]]
|
173
|
+
|
174
|
+
args[1..-1].each do |a|
|
175
|
+
filter[:methods] << a[1] unless a.node_type == :hash
|
176
|
+
end
|
177
|
+
|
178
|
+
if args[-1].node_type == :hash
|
179
|
+
option = args[-1][1][1]
|
180
|
+
value = args[-1][2]
|
181
|
+
case value.node_type
|
182
|
+
when :array
|
183
|
+
filter[option] = value[1..-1].map {|v| v[1] }
|
184
|
+
when :lit, :str
|
185
|
+
filter[option] = value[1]
|
186
|
+
else
|
187
|
+
warn "[Notice] Unknown before_filter value: #{option} => #{value}" if OPTIONS[:debug]
|
188
|
+
end
|
189
|
+
else
|
190
|
+
filter[:all] = true
|
191
|
+
end
|
192
|
+
|
193
|
+
filter
|
194
|
+
end
|
195
|
+
|
196
|
+
#Finds a method in the given class or a parent class
|
197
|
+
def find_method method_name, klass
|
198
|
+
return nil if sexp? method_name
|
199
|
+
method_name = method_name.to_sym
|
200
|
+
controller = @tracker.controllers[klass]
|
201
|
+
controller ||= @tracker.libs[klass]
|
202
|
+
|
203
|
+
if klass and controller
|
204
|
+
method = controller[:public][method_name]
|
205
|
+
method ||= controller[:private][method_name]
|
206
|
+
method ||= controller[:protected][method_name]
|
207
|
+
|
208
|
+
if method.nil?
|
209
|
+
controller[:includes].each do |included|
|
210
|
+
method = find_method method_name, included
|
211
|
+
return method if method
|
212
|
+
end
|
213
|
+
|
214
|
+
find_method method_name, controller[:parent]
|
215
|
+
else
|
216
|
+
method
|
217
|
+
end
|
218
|
+
else
|
219
|
+
nil
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'ruby_parser'
|
2
|
+
require 'processors/base_processor'
|
3
|
+
|
4
|
+
#Processes controller. Results are put in tracker.controllers
|
5
|
+
class ControllerProcessor < BaseProcessor
|
6
|
+
FORMAT_HTML = Sexp.new(:call, Sexp.new(:lvar, :format), :html, Sexp.new(:arglist))
|
7
|
+
|
8
|
+
def initialize tracker
|
9
|
+
super
|
10
|
+
@controller = nil
|
11
|
+
@current_method = nil
|
12
|
+
@current_module = nil
|
13
|
+
@visibility = :public
|
14
|
+
@file_name = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
#Use this method to process a Controller
|
18
|
+
def process_controller src, file_name = nil
|
19
|
+
@file_name = file_name
|
20
|
+
process src
|
21
|
+
end
|
22
|
+
|
23
|
+
#s(:class, NAME, PARENT, s(:scope ...))
|
24
|
+
def process_class exp
|
25
|
+
if @controller
|
26
|
+
warn "[Notice] Skipping inner class: #{class_name exp[1]}" if OPTIONS[:debug]
|
27
|
+
return ignore
|
28
|
+
end
|
29
|
+
|
30
|
+
name = class_name(exp[1])
|
31
|
+
if @current_module
|
32
|
+
name = (@current_module + "::" + name.to_s).to_sym
|
33
|
+
end
|
34
|
+
@controller = { :name => name,
|
35
|
+
:parent => class_name(exp[2]),
|
36
|
+
:includes => [],
|
37
|
+
:public => {},
|
38
|
+
:private => {},
|
39
|
+
:protected => {},
|
40
|
+
:options => {},
|
41
|
+
:src => exp,
|
42
|
+
:file => @file_name }
|
43
|
+
@tracker.controllers[@controller[:name]] = @controller
|
44
|
+
exp[3] = process exp[3]
|
45
|
+
@controller = nil
|
46
|
+
exp
|
47
|
+
end
|
48
|
+
|
49
|
+
#Look for specific calls inside the controller
|
50
|
+
def process_call exp
|
51
|
+
target = exp[1]
|
52
|
+
if sexp? target
|
53
|
+
target = process target
|
54
|
+
end
|
55
|
+
|
56
|
+
method = exp[2]
|
57
|
+
args = exp[3]
|
58
|
+
|
59
|
+
#Methods called inside class definition
|
60
|
+
#like attr_* and other settings
|
61
|
+
if @current_method.nil? and target.nil?
|
62
|
+
if args.length == 1 #actually, empty
|
63
|
+
case method
|
64
|
+
when :private, :protected, :public
|
65
|
+
@visibility = method
|
66
|
+
when :protect_from_forgery
|
67
|
+
@controller[:options][:protect_from_forgery] = true
|
68
|
+
else
|
69
|
+
#??
|
70
|
+
end
|
71
|
+
else
|
72
|
+
case method
|
73
|
+
when :include
|
74
|
+
@controller[:includes] << class_name(args[1]) if @controller
|
75
|
+
when :before_filter
|
76
|
+
@controller[:options][:before_filters] ||= []
|
77
|
+
@controller[:options][:before_filters] << args[1..-1]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
ignore
|
81
|
+
elsif target == nil and method == :render
|
82
|
+
make_render exp
|
83
|
+
elsif exp == FORMAT_HTML and context[1] != :iter
|
84
|
+
#This is an empty call to
|
85
|
+
# format.html
|
86
|
+
#Which renders the default template if no arguments
|
87
|
+
#Need to make more generic, though.
|
88
|
+
call = Sexp.new :render, :default, @current_method
|
89
|
+
call.line(exp.line)
|
90
|
+
call
|
91
|
+
else
|
92
|
+
call = Sexp.new :call, target, method, process(args)
|
93
|
+
call.line(exp.line)
|
94
|
+
call
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
#Process method definition and store in Tracker
|
99
|
+
def process_defn exp
|
100
|
+
name = exp[1]
|
101
|
+
@current_method = name
|
102
|
+
res = Sexp.new :methdef, name, process(exp[2]), process(exp[3][1])
|
103
|
+
res.line(exp.line)
|
104
|
+
@current_method = nil
|
105
|
+
@controller[@visibility][name] = res unless @controller.nil?
|
106
|
+
|
107
|
+
res
|
108
|
+
end
|
109
|
+
|
110
|
+
#Process self.method definition and store in Tracker
|
111
|
+
def process_defs exp
|
112
|
+
name = exp[2]
|
113
|
+
|
114
|
+
if exp[1].node_type == :self
|
115
|
+
target = @controller[:name]
|
116
|
+
else
|
117
|
+
target = class_name exp[1]
|
118
|
+
end
|
119
|
+
|
120
|
+
@current_method = name
|
121
|
+
res = Sexp.new :selfdef, target, name, process(exp[3]), process(exp[4][1])
|
122
|
+
res.line(exp.line)
|
123
|
+
@current_method = nil
|
124
|
+
@controller[@visibility][name] = res unless @controller.nil?
|
125
|
+
|
126
|
+
res
|
127
|
+
end
|
128
|
+
|
129
|
+
#Look for before_filters and add fake ones if necessary
|
130
|
+
def process_iter exp
|
131
|
+
if exp[1][2] == :before_filter
|
132
|
+
add_fake_filter exp
|
133
|
+
else
|
134
|
+
super
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
#This is to handle before_filter do |controller| ... end
|
139
|
+
#
|
140
|
+
#We build a new method and process that the same way as usual
|
141
|
+
#methods and filters.
|
142
|
+
def add_fake_filter exp
|
143
|
+
filter_name = ("fake_filter" + rand.to_s[/\d+$/]).to_sym
|
144
|
+
args = exp[1][3]
|
145
|
+
args.insert(1, Sexp.new(:lit, filter_name))
|
146
|
+
before_filter_call = Sexp.new(:call, nil, :before_filter, args)
|
147
|
+
|
148
|
+
if exp[2]
|
149
|
+
block_variable = exp[2][1]
|
150
|
+
else
|
151
|
+
block_variable = :temp
|
152
|
+
end
|
153
|
+
|
154
|
+
if sexp? exp[3] and exp[3].node_type == :block
|
155
|
+
block_inner = exp[3][1..-1]
|
156
|
+
else
|
157
|
+
block_inner = [exp[3]]
|
158
|
+
end
|
159
|
+
|
160
|
+
#Build Sexp for filter method
|
161
|
+
body = Sexp.new(:scope,
|
162
|
+
Sexp.new(:block,
|
163
|
+
Sexp.new(:lasgn, block_variable,
|
164
|
+
Sexp.new(:call, Sexp.new(:const, @controller[:name]), :new, Sexp.new(:arglist)))).concat(block_inner))
|
165
|
+
|
166
|
+
filter_method = Sexp.new(:defn, filter_name, Sexp.new(:args), body).line(exp.line)
|
167
|
+
|
168
|
+
vis = @visibility
|
169
|
+
@visibility = :private
|
170
|
+
process_defn filter_method
|
171
|
+
@visibility = vis
|
172
|
+
process before_filter_call
|
173
|
+
exp
|
174
|
+
end
|
175
|
+
end
|