brakeman 0.0.3 → 0.1.0
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/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.
|