brakeman 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/brakeman +21 -16
- data/lib/checks/check_cross_site_scripting.rb +129 -21
- data/lib/processors/controller_alias_processor.rb +15 -0
- data/lib/processors/controller_processor.rb +27 -0
- data/lib/processors/haml_template_processor.rb +16 -0
- data/lib/processors/lib/render_helper.rb +19 -0
- data/lib/processors/library_processor.rb +2 -1
- data/lib/report.rb +11 -0
- metadata +33 -33
data/bin/brakeman
CHANGED
@@ -3,27 +3,14 @@ require "optparse"
|
|
3
3
|
require 'set'
|
4
4
|
require 'yaml'
|
5
5
|
|
6
|
-
|
6
|
+
|
7
|
+
Version = "0.1.0"
|
7
8
|
|
8
9
|
trap("INT") do
|
9
10
|
$stderr.puts "\nInterrupted - exiting."
|
10
11
|
exit!
|
11
12
|
end
|
12
13
|
|
13
|
-
#Load scanner
|
14
|
-
begin
|
15
|
-
require 'scanner'
|
16
|
-
rescue LoadError
|
17
|
-
#Try to find lib directory locally
|
18
|
-
$: << "#{File.expand_path(File.dirname(__FILE__))}/../lib"
|
19
|
-
|
20
|
-
begin
|
21
|
-
require 'scanner'
|
22
|
-
rescue LoadError
|
23
|
-
abort "Cannot find lib/ directory."
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
14
|
#Parse command line options
|
28
15
|
options = {}
|
29
16
|
|
@@ -86,7 +73,7 @@ OptionParser.new do |opts|
|
|
86
73
|
|
87
74
|
opts.on "-f",
|
88
75
|
"--format TYPE",
|
89
|
-
[:pdf, :text, :html, :csv],
|
76
|
+
[:pdf, :text, :html, :csv, :tabs],
|
90
77
|
"Specify output format. Default is text" do |type|
|
91
78
|
|
92
79
|
type = "s" if type == :text
|
@@ -187,6 +174,8 @@ if OPTIONS[:output_format]
|
|
187
174
|
OPTIONS[:output_format] = :to_csv
|
188
175
|
when :pdf, :to_pdf
|
189
176
|
OPTIONS[:output_format] = :to_pdf
|
177
|
+
when :tabs, :to_tabs
|
178
|
+
OPTIONS[:output_format] = :to_tabs
|
190
179
|
else
|
191
180
|
OPTIONS[:output_format] = :to_s
|
192
181
|
end
|
@@ -198,6 +187,8 @@ else
|
|
198
187
|
OPTIONS[:output_format] = :to_csv
|
199
188
|
when /\.pdf$/i
|
200
189
|
OPTIONS[:output_format] = :to_pdf
|
190
|
+
when /\.tabs$/i
|
191
|
+
OPTIONS[:output_format] = :to_tabs
|
201
192
|
else
|
202
193
|
OPTIONS[:output_format] = :to_s
|
203
194
|
end
|
@@ -247,6 +238,20 @@ abort("Please supply the path to a Rails application.") unless app_path and File
|
|
247
238
|
|
248
239
|
warn "[Notice] Using Ruby #{RUBY_VERSION}. Please make sure this matches the one used to run your Rails application."
|
249
240
|
|
241
|
+
#Load scanner
|
242
|
+
begin
|
243
|
+
require 'scanner'
|
244
|
+
rescue LoadError
|
245
|
+
#Try to find lib directory locally
|
246
|
+
$:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
|
247
|
+
|
248
|
+
begin
|
249
|
+
require 'scanner'
|
250
|
+
rescue LoadError
|
251
|
+
abort "Cannot find lib/ directory."
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
250
255
|
#Start scanning
|
251
256
|
scanner = Scanner.new app_path
|
252
257
|
|
@@ -26,7 +26,7 @@ class CheckCrossSiteScripting < BaseCheck
|
|
26
26
|
|
27
27
|
MODEL_METHODS = Set.new([:all, :find, :first, :last, :new])
|
28
28
|
|
29
|
-
IGNORE_LIKE = /^link_to_|_path|_tag|_url$/
|
29
|
+
IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
|
30
30
|
|
31
31
|
HAML_HELPERS = Sexp.new(:colon2, Sexp.new(:const, :Haml), :Helpers)
|
32
32
|
|
@@ -42,34 +42,31 @@ class CheckCrossSiteScripting < BaseCheck
|
|
42
42
|
@models = tracker.models.keys
|
43
43
|
@inspect_arguments = OPTIONS[:check_arguments]
|
44
44
|
|
45
|
+
CheckLinkTo.new(checks, tracker).run_check
|
46
|
+
|
45
47
|
tracker.each_template do |name, template|
|
46
48
|
@current_template = template
|
47
49
|
|
48
50
|
template[:outputs].each do |out|
|
49
51
|
type, match = has_immediate_user_input?(out[1])
|
50
|
-
if type
|
51
|
-
unless duplicate? out
|
52
|
+
if type and not duplicate? out
|
52
53
|
add_result out
|
53
54
|
case type
|
54
55
|
when :params
|
55
|
-
|
56
|
-
warn :template => @current_template,
|
57
|
-
:warning_type => "Cross Site Scripting",
|
58
|
-
:message => "Unescaped parameter value",
|
59
|
-
:line => match.line,
|
60
|
-
:code => match,
|
61
|
-
:confidence => CONFIDENCE[:high]
|
62
|
-
|
56
|
+
message = "Unescaped parameter value"
|
63
57
|
when :cookies
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
:message => "Unescaped cookie value",
|
68
|
-
:line => match.line,
|
69
|
-
:code => match,
|
70
|
-
:confidence => CONFIDENCE[:high]
|
58
|
+
message = "Unescaped cookie value"
|
59
|
+
else
|
60
|
+
message = "Unescaped user input value"
|
71
61
|
end
|
72
|
-
|
62
|
+
|
63
|
+
warn :template => @current_template,
|
64
|
+
:warning_type => "Cross Site Scripting",
|
65
|
+
:message => message,
|
66
|
+
:line => match.line,
|
67
|
+
:code => match,
|
68
|
+
:confidence => CONFIDENCE[:high]
|
69
|
+
|
73
70
|
elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(out[1])
|
74
71
|
method = match[2]
|
75
72
|
|
@@ -168,8 +165,8 @@ class CheckCrossSiteScripting < BaseCheck
|
|
168
165
|
elsif @inspect_arguments and (ALL_PARAMETERS.include?(exp) or params? exp)
|
169
166
|
|
170
167
|
@matched = :params
|
171
|
-
|
172
|
-
process args
|
168
|
+
elsif @inspect_arguments
|
169
|
+
process args
|
173
170
|
end
|
174
171
|
end
|
175
172
|
|
@@ -212,5 +209,116 @@ class CheckCrossSiteScripting < BaseCheck
|
|
212
209
|
end
|
213
210
|
exp
|
214
211
|
end
|
212
|
+
end
|
213
|
+
|
214
|
+
#This _only_ checks calls to link_to
|
215
|
+
class CheckLinkTo < CheckCrossSiteScripting
|
216
|
+
IGNORE_METHODS = IGNORE_METHODS - [:link_to]
|
217
|
+
|
218
|
+
def run_check
|
219
|
+
#Ideally, I think this should also check to see if people are setting
|
220
|
+
#:escape => false
|
221
|
+
methods = tracker.find_call [], :link_to
|
222
|
+
|
223
|
+
@models = tracker.models.keys
|
224
|
+
@inspect_arguments = OPTIONS[:check_arguments]
|
225
|
+
|
226
|
+
methods.each do |call|
|
227
|
+
process_result call
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def process_result result
|
232
|
+
#Have to make a copy of this, otherwise it will be changed to
|
233
|
+
#an ignored method call by the code above.
|
234
|
+
call = result[-1] = result[-1].dup
|
235
|
+
|
236
|
+
@matched = false
|
237
|
+
|
238
|
+
return if call[3][1].nil?
|
239
|
+
|
240
|
+
#Only check first argument for +link_to+, as the second
|
241
|
+
#will *usually* be a record or escaped.
|
242
|
+
first_arg = process call[3][1]
|
243
|
+
|
244
|
+
type, match = has_immediate_user_input? first_arg
|
245
|
+
|
246
|
+
if type
|
247
|
+
case type
|
248
|
+
when :params
|
249
|
+
message = "Unescaped parameter value in link_to"
|
250
|
+
when :cookies
|
251
|
+
message = "Unescaped cookie value in link_to"
|
252
|
+
else
|
253
|
+
message = "Unescaped user input value in link_to"
|
254
|
+
end
|
255
|
+
|
256
|
+
unless duplicate? result
|
257
|
+
add_result result
|
258
|
+
|
259
|
+
warn :result => result,
|
260
|
+
:warning_type => "Cross Site Scripting",
|
261
|
+
:message => message,
|
262
|
+
:confidence => CONFIDENCE[:high]
|
263
|
+
end
|
264
|
+
|
265
|
+
elsif not OPTIONS[:ignore_model_output] and match = has_immediate_model?(first_arg)
|
266
|
+
method = match[2]
|
267
|
+
|
268
|
+
unless duplicate? result or IGNORE_MODEL_METHODS.include? method
|
269
|
+
add_result result
|
270
|
+
|
271
|
+
if MODEL_METHODS.include? method or method.to_s =~ /^find_by/
|
272
|
+
confidence = CONFIDENCE[:high]
|
273
|
+
else
|
274
|
+
confidence = CONFIDENCE[:med]
|
275
|
+
end
|
276
|
+
|
277
|
+
warn :result => result,
|
278
|
+
:warning_type => "Cross Site Scripting",
|
279
|
+
:message => "Unescaped model attribute in link_to",
|
280
|
+
:confidence => confidence
|
281
|
+
end
|
282
|
+
|
283
|
+
elsif @matched
|
284
|
+
if @matched == :model and not OPTIONS[:ignore_model_output]
|
285
|
+
message = "Unescaped model attribute in link_to"
|
286
|
+
elsif @matched == :params
|
287
|
+
message = "Unescaped parameter value in link_to"
|
288
|
+
end
|
289
|
+
|
290
|
+
if message and not duplicate? result
|
291
|
+
add_result result
|
292
|
+
|
293
|
+
warn :result => result,
|
294
|
+
:warning_type => "Cross Site Scripting",
|
295
|
+
:message => message,
|
296
|
+
:confidence => CONFIDENCE[:med]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def process_call exp
|
302
|
+
@mark = true
|
303
|
+
actually_process_call exp
|
304
|
+
exp
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
def actually_process_call exp
|
309
|
+
return if @matched
|
310
|
+
|
311
|
+
target = exp[1]
|
312
|
+
if sexp? target
|
313
|
+
target = process target.dup
|
314
|
+
end
|
315
|
+
|
316
|
+
#Bare records create links to the model resource,
|
317
|
+
#not a string that could have injection
|
318
|
+
if model_name? target and context == [:call, :arglist]
|
319
|
+
return exp
|
320
|
+
end
|
215
321
|
|
322
|
+
super
|
323
|
+
end
|
216
324
|
end
|
@@ -93,6 +93,7 @@ class ControllerAliasProcessor < AliasProcessor
|
|
93
93
|
|
94
94
|
#Processes the default template for the current action
|
95
95
|
def process_default_render exp
|
96
|
+
process_layout
|
96
97
|
process_template template_name, nil
|
97
98
|
end
|
98
99
|
|
@@ -114,6 +115,20 @@ class ControllerAliasProcessor < AliasProcessor
|
|
114
115
|
end
|
115
116
|
end
|
116
117
|
|
118
|
+
#Determines default layout name
|
119
|
+
def layout_name
|
120
|
+
controller = @tracker.controllers[@current_class]
|
121
|
+
|
122
|
+
return controller[:layout] if controller[:layout]
|
123
|
+
return false if controller[:layout] == false
|
124
|
+
|
125
|
+
app_controller = @tracker.controllers[:ApplicationController]
|
126
|
+
|
127
|
+
return app_controller[:layout] if app_controller and app_controller[:layout]
|
128
|
+
|
129
|
+
nil
|
130
|
+
end
|
131
|
+
|
117
132
|
#Returns true if the given method name is also a route
|
118
133
|
def route? method
|
119
134
|
return true if @tracker.routes[:allow_all_actions]
|
@@ -42,6 +42,7 @@ class ControllerProcessor < BaseProcessor
|
|
42
42
|
:file => @file_name }
|
43
43
|
@tracker.controllers[@controller[:name]] = @controller
|
44
44
|
exp[3] = process exp[3]
|
45
|
+
set_layout_name
|
45
46
|
@controller = nil
|
46
47
|
exp
|
47
48
|
end
|
@@ -75,6 +76,20 @@ class ControllerProcessor < BaseProcessor
|
|
75
76
|
when :before_filter
|
76
77
|
@controller[:options][:before_filters] ||= []
|
77
78
|
@controller[:options][:before_filters] << args[1..-1]
|
79
|
+
when :layout
|
80
|
+
if string? args[-1]
|
81
|
+
#layout "some_layout"
|
82
|
+
|
83
|
+
name = args[-1][1].to_s
|
84
|
+
unless Dir.glob("#{OPTIONS[:app_path]}/app/views/layouts/#{name}.html.{erb,haml}").empty?
|
85
|
+
@controller[:layout] = "layouts/#{name}"
|
86
|
+
else
|
87
|
+
warn "[Notice] Layout not found: #{name}" if OPTIONS[:debug]
|
88
|
+
end
|
89
|
+
elsif sexp? args[-1] and (args[-1][0] == :nil or args[-1][0] == :false)
|
90
|
+
#layout :false or layout nil
|
91
|
+
@controller[:layout] = false
|
92
|
+
end
|
78
93
|
end
|
79
94
|
end
|
80
95
|
ignore
|
@@ -135,6 +150,18 @@ class ControllerProcessor < BaseProcessor
|
|
135
150
|
end
|
136
151
|
end
|
137
152
|
|
153
|
+
#Sets default layout for renders inside Controller
|
154
|
+
def set_layout_name
|
155
|
+
return if @controller[:layout]
|
156
|
+
|
157
|
+
name = underscore(@controller[:name].to_s.split("::")[-1].gsub("Controller", ''))
|
158
|
+
|
159
|
+
#There is a layout for this Controller
|
160
|
+
unless Dir.glob("#{OPTIONS[:app_path]}/app/views/layouts/#{name}.html.{erb,haml}").empty?
|
161
|
+
@controller[:layout] = "layouts/#{name}"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
138
165
|
#This is to handle before_filter do |controller| ... end
|
139
166
|
#
|
140
167
|
#We build a new method and process that the same way as usual
|
@@ -3,6 +3,22 @@ require 'processors/template_processor'
|
|
3
3
|
#Processes HAML templates.
|
4
4
|
class HamlTemplateProcessor < TemplateProcessor
|
5
5
|
HAML_FORMAT_METHOD = /format_script_(true|false)_(true|false)_(true|false)_(true|false)_(true|false)_(true|false)_(true|false)/
|
6
|
+
|
7
|
+
def initialize *args
|
8
|
+
super
|
9
|
+
|
10
|
+
@tracker.libs.each do |name, lib|
|
11
|
+
if name.to_s =~ /^Haml::Filters/
|
12
|
+
begin
|
13
|
+
require lib[:file]
|
14
|
+
rescue Exception => e
|
15
|
+
if OPTIONS[:debug]
|
16
|
+
raise e
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
6
22
|
|
7
23
|
#Processes call, looking for template output
|
8
24
|
def process_call exp
|
@@ -19,6 +19,14 @@ module RenderHelper
|
|
19
19
|
exp
|
20
20
|
end
|
21
21
|
|
22
|
+
#Processes layout
|
23
|
+
def process_layout name = nil
|
24
|
+
layout = name || layout_name
|
25
|
+
return unless layout
|
26
|
+
|
27
|
+
process_template layout, nil
|
28
|
+
end
|
29
|
+
|
22
30
|
#Determines file name for partial and then processes it
|
23
31
|
def process_partial name, args
|
24
32
|
if name == "" or !(string? name or symbol? name)
|
@@ -61,6 +69,17 @@ module RenderHelper
|
|
61
69
|
|
62
70
|
options = get_options args
|
63
71
|
|
72
|
+
#Process layout
|
73
|
+
if string? options[:layout]
|
74
|
+
process_template "layouts/#{options[:layout][1]}", nil
|
75
|
+
elsif sexp? options[:layout] and options[:layout][0] == :false
|
76
|
+
#nothing
|
77
|
+
elsif not template[:name].to_s.match(/[^\/_][^\/]+$/)
|
78
|
+
#Don't do this for partials
|
79
|
+
|
80
|
+
process_layout
|
81
|
+
end
|
82
|
+
|
64
83
|
if hash? options[:locals]
|
65
84
|
hash_iterate options[:locals] do |key, value|
|
66
85
|
template_env[Sexp.new(:call, nil, key[1], Sexp.new(:arglist))] = value
|
data/lib/report.rb
CHANGED
@@ -625,4 +625,15 @@ class Report
|
|
625
625
|
|
626
626
|
output << "</table></div>"
|
627
627
|
end
|
628
|
+
|
629
|
+
def to_tabs
|
630
|
+
[[:warnings, "General"], [:controller_warnings, "Controller"],
|
631
|
+
[:model_warnings, "Model"], [:template_warnings, "Template"]].map do |meth, category|
|
632
|
+
|
633
|
+
checks.send(meth).map do |w|
|
634
|
+
"#{file_for w}\t#{w.line}\t#{w.warning_type}\t#{category}\t#{w.message}\t#{TEXT_CONFIDENCE[w.confidence]}"
|
635
|
+
end.join "\n"
|
636
|
+
|
637
|
+
end.join "\n"
|
638
|
+
end
|
628
639
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: brakeman
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 0.0.3
|
10
|
+
version: 0.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Justin Collins
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-01-18 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -111,47 +111,47 @@ files:
|
|
111
111
|
- WARNING_TYPES
|
112
112
|
- FEATURES
|
113
113
|
- README.md
|
114
|
-
- lib/warning.rb
|
115
|
-
- lib/processors/params_processor.rb
|
116
|
-
- lib/processors/controller_alias_processor.rb
|
117
114
|
- lib/processors/base_processor.rb
|
118
|
-
- lib/processors/
|
119
|
-
- lib/processors/library_processor.rb
|
120
|
-
- lib/processors/erb_template_processor.rb
|
115
|
+
- lib/processors/alias_processor.rb
|
121
116
|
- lib/processors/haml_template_processor.rb
|
122
|
-
- lib/processors/
|
123
|
-
- lib/processors/
|
124
|
-
- lib/processors/
|
125
|
-
- lib/processors/
|
117
|
+
- lib/processors/output_processor.rb
|
118
|
+
- lib/processors/params_processor.rb
|
119
|
+
- lib/processors/erubis_template_processor.rb
|
120
|
+
- lib/processors/controller_alias_processor.rb
|
126
121
|
- lib/processors/lib/processor_helper.rb
|
127
|
-
- lib/processors/lib/find_model_call.rb
|
128
122
|
- lib/processors/lib/render_helper.rb
|
129
|
-
- lib/processors/
|
130
|
-
- lib/processors/
|
123
|
+
- lib/processors/lib/find_model_call.rb
|
124
|
+
- lib/processors/lib/find_call.rb
|
125
|
+
- lib/processors/route_processor.rb
|
126
|
+
- lib/processors/model_processor.rb
|
127
|
+
- lib/processors/erb_template_processor.rb
|
128
|
+
- lib/processors/template_alias_processor.rb
|
131
129
|
- lib/processors/config_processor.rb
|
132
|
-
- lib/processors/erubis_template_processor.rb
|
133
130
|
- lib/processors/template_processor.rb
|
131
|
+
- lib/processors/controller_processor.rb
|
132
|
+
- lib/processors/library_processor.rb
|
133
|
+
- lib/report.rb
|
134
|
+
- lib/util.rb
|
134
135
|
- lib/checks/check_send_file.rb
|
135
|
-
- lib/checks/
|
136
|
-
- lib/checks/
|
136
|
+
- lib/checks/check_default_routes.rb
|
137
|
+
- lib/checks/check_render.rb
|
138
|
+
- lib/checks/check_execute.rb
|
137
139
|
- lib/checks/check_mass_assignment.rb
|
140
|
+
- lib/checks/check_sql.rb
|
141
|
+
- lib/checks/check_validation_regex.rb
|
138
142
|
- lib/checks/check_cross_site_scripting.rb
|
143
|
+
- lib/checks/check_redirect.rb
|
144
|
+
- lib/checks/check_session_settings.rb
|
145
|
+
- lib/checks/check_forgery_setting.rb
|
146
|
+
- lib/checks/base_check.rb
|
139
147
|
- lib/checks/check_model_attributes.rb
|
140
|
-
- lib/checks/check_default_routes.rb
|
141
148
|
- lib/checks/check_evaluation.rb
|
142
|
-
- lib/checks/check_validation_regex.rb
|
143
|
-
- lib/checks/check_execute.rb
|
144
|
-
- lib/checks/base_check.rb
|
145
149
|
- lib/checks/check_file_access.rb
|
146
|
-
- lib/
|
147
|
-
- lib/checks/check_forgery_setting.rb
|
148
|
-
- lib/checks/check_render.rb
|
149
|
-
- lib/tracker.rb
|
150
|
-
- lib/util.rb
|
151
|
-
- lib/report.rb
|
150
|
+
- lib/processor.rb
|
152
151
|
- lib/scanner.rb
|
152
|
+
- lib/tracker.rb
|
153
153
|
- lib/checks.rb
|
154
|
-
- lib/
|
154
|
+
- lib/warning.rb
|
155
155
|
- lib/format/style.css
|
156
156
|
has_rdoc: true
|
157
157
|
homepage: http://github.com/presidentbeef/brakeman
|
@@ -183,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
183
|
requirements: []
|
184
184
|
|
185
185
|
rubyforge_project:
|
186
|
-
rubygems_version: 1.
|
186
|
+
rubygems_version: 1.4.1
|
187
187
|
signing_key:
|
188
188
|
specification_version: 3
|
189
189
|
summary: Security vulnerability scanner for Ruby on Rails.
|