brakeman 1.9.5 → 2.0.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES +27 -0
- data/README.md +5 -2
- data/bin/brakeman +20 -15
- data/lib/brakeman.rb +106 -80
- data/lib/brakeman/app_tree.rb +22 -11
- data/lib/brakeman/call_index.rb +4 -4
- data/lib/brakeman/checks/base_check.rb +33 -5
- data/lib/brakeman/checks/check_basic_auth.rb +2 -1
- data/lib/brakeman/checks/check_content_tag.rb +8 -29
- data/lib/brakeman/checks/check_cross_site_scripting.rb +10 -19
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_execute.rb +3 -11
- data/lib/brakeman/checks/check_file_access.rb +1 -14
- data/lib/brakeman/checks/check_forgery_setting.rb +5 -4
- data/lib/brakeman/checks/check_link_to.rb +4 -15
- data/lib/brakeman/checks/check_link_to_href.rb +1 -8
- data/lib/brakeman/checks/check_mass_assignment.rb +6 -2
- data/lib/brakeman/checks/check_model_attributes.rb +1 -0
- data/lib/brakeman/checks/check_model_serialize.rb +2 -1
- data/lib/brakeman/checks/check_render.rb +2 -15
- data/lib/brakeman/checks/check_select_tag.rb +1 -1
- data/lib/brakeman/checks/check_select_vulnerability.rb +2 -2
- data/lib/brakeman/checks/check_send.rb +3 -0
- data/lib/brakeman/checks/check_session_settings.rb +2 -3
- data/lib/brakeman/checks/check_skip_before_filter.rb +5 -2
- data/lib/brakeman/checks/check_sql.rb +59 -50
- data/lib/brakeman/checks/check_symbol_dos.rb +5 -14
- data/lib/brakeman/checks/check_unsafe_reflection.rb +2 -15
- data/lib/brakeman/options.rb +14 -9
- data/lib/brakeman/processors/controller_alias_processor.rb +4 -10
- data/lib/brakeman/processors/controller_processor.rb +53 -14
- data/lib/brakeman/processors/lib/find_all_calls.rb +40 -40
- data/lib/brakeman/processors/lib/processor_helper.rb +5 -1
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +8 -0
- data/lib/brakeman/processors/output_processor.rb +23 -1
- data/lib/brakeman/report.rb +28 -14
- data/lib/brakeman/scanner.rb +5 -3
- data/lib/brakeman/tracker.rb +7 -7
- data/lib/brakeman/util.rb +5 -3
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +12 -9
- data/lib/ruby_parser/bm_sexp.rb +5 -1
- data/lib/tasks/brakeman.rake +10 -0
- metadata +11 -9
- data/lib/brakeman/checks/check_yaml_load.rb +0 -55
@@ -13,10 +13,11 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
13
13
|
|
14
14
|
#Process the given source. Provide either class and method being searched
|
15
15
|
#or the template. These names are used when reporting results.
|
16
|
-
def process_source exp,
|
17
|
-
@current_class =
|
18
|
-
@current_method = method
|
19
|
-
@current_template = template
|
16
|
+
def process_source exp, opts
|
17
|
+
@current_class = opts[:class]
|
18
|
+
@current_method = opts[:method]
|
19
|
+
@current_template = opts[:template]
|
20
|
+
@current_file = opts[:file]
|
20
21
|
process exp
|
21
22
|
end
|
22
23
|
|
@@ -48,15 +49,12 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
48
49
|
method = exp.method
|
49
50
|
process_call_args exp
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
@calls << call
|
52
|
+
@calls << { :target => target,
|
53
|
+
:method => method,
|
54
|
+
:call => exp,
|
55
|
+
:nested => @in_target,
|
56
|
+
:chain => get_chain(exp),
|
57
|
+
:location => make_location }
|
60
58
|
|
61
59
|
exp
|
62
60
|
end
|
@@ -66,15 +64,11 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
66
64
|
def process_render exp
|
67
65
|
process exp.last if sexp? exp.last
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
call[:location] = [:class, @current_class, @current_method]
|
75
|
-
end
|
76
|
-
|
77
|
-
@calls << call
|
67
|
+
@calls << { :target => nil,
|
68
|
+
:method => :render,
|
69
|
+
:call => exp,
|
70
|
+
:nested => false,
|
71
|
+
:location => make_location }
|
78
72
|
|
79
73
|
exp
|
80
74
|
end
|
@@ -84,15 +78,11 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
84
78
|
def process_dxstr exp
|
85
79
|
process exp.last if sexp? exp.last
|
86
80
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
call[:location] = [:class, @current_class, @current_method]
|
93
|
-
end
|
94
|
-
|
95
|
-
@calls << call
|
81
|
+
@calls << { :target => nil,
|
82
|
+
:method => :`,
|
83
|
+
:call => exp,
|
84
|
+
:nested => false,
|
85
|
+
:location => make_location }
|
96
86
|
|
97
87
|
exp
|
98
88
|
end
|
@@ -101,15 +91,11 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
101
91
|
def process_dsym exp
|
102
92
|
exp.each { |arg| process arg if sexp? arg }
|
103
93
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
call[:location] = [:class, @current_class, @current_method]
|
110
|
-
end
|
111
|
-
|
112
|
-
@calls << call
|
94
|
+
@calls << { :target => nil,
|
95
|
+
:method => :literal_to_sym,
|
96
|
+
:call => exp,
|
97
|
+
:nested => false,
|
98
|
+
:location => make_location }
|
113
99
|
|
114
100
|
exp
|
115
101
|
end
|
@@ -155,4 +141,18 @@ class Brakeman::FindAllCalls < Brakeman::BaseProcessor
|
|
155
141
|
[get_target(call)]
|
156
142
|
end
|
157
143
|
end
|
144
|
+
|
145
|
+
def make_location
|
146
|
+
if @current_template
|
147
|
+
{ :type => :template,
|
148
|
+
:template => @current_template,
|
149
|
+
:file => @current_file }
|
150
|
+
else
|
151
|
+
{ :type => :class,
|
152
|
+
:class => @current_class,
|
153
|
+
:method => @current_method,
|
154
|
+
:file => @current_file }
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
158
|
end
|
@@ -72,6 +72,14 @@ class Brakeman::Rails3ConfigProcessor < Brakeman::BaseProcessor
|
|
72
72
|
level = @tracker.config[:rails]
|
73
73
|
options[0..-2].each do |o|
|
74
74
|
level[o] ||= {}
|
75
|
+
|
76
|
+
option = level[o]
|
77
|
+
|
78
|
+
if not option.is_a? Hash
|
79
|
+
Brakeman.debug "[Notice] Skipping config setting: #{options.map(&:to_s).join(".")}"
|
80
|
+
return exp
|
81
|
+
end
|
82
|
+
|
75
83
|
level = level[o]
|
76
84
|
end
|
77
85
|
|
@@ -14,7 +14,6 @@ class Brakeman::OutputProcessor < Ruby2Ruby
|
|
14
14
|
end
|
15
15
|
|
16
16
|
alias process_safely format
|
17
|
-
alias process_methdef process_defn
|
18
17
|
|
19
18
|
def process exp
|
20
19
|
begin
|
@@ -99,6 +98,29 @@ class Brakeman::OutputProcessor < Ruby2Ruby
|
|
99
98
|
out
|
100
99
|
end
|
101
100
|
|
101
|
+
def process_defn exp
|
102
|
+
# Copied from Ruby2Ruby except without the whole
|
103
|
+
# "convert methods to attr_*" stuff
|
104
|
+
name = exp.shift
|
105
|
+
args = process exp.shift
|
106
|
+
args = "" if args == "()"
|
107
|
+
|
108
|
+
exp.shift if exp == s(s(:nil)) # empty it out of a default nil expression
|
109
|
+
|
110
|
+
body = []
|
111
|
+
until exp.empty? do
|
112
|
+
body << indent(process(exp.shift))
|
113
|
+
end
|
114
|
+
|
115
|
+
body << indent("# do nothing") if body.empty?
|
116
|
+
|
117
|
+
body = body.join("\n")
|
118
|
+
|
119
|
+
return "def #{name}#{args}\n#{body}\nend".gsub(/\n\s*\n+/, "\n")
|
120
|
+
end
|
121
|
+
|
122
|
+
alias process_methdef process_defn
|
123
|
+
|
102
124
|
def process_call_with_block exp
|
103
125
|
call = process exp[0]
|
104
126
|
block = process_rlist exp[2..-1]
|
data/lib/brakeman/report.rb
CHANGED
@@ -3,7 +3,7 @@ require 'set'
|
|
3
3
|
require 'brakeman/processors/output_processor'
|
4
4
|
require 'brakeman/util'
|
5
5
|
require 'terminal-table'
|
6
|
-
require 'highline
|
6
|
+
require 'highline'
|
7
7
|
require "csv"
|
8
8
|
require 'multi_json'
|
9
9
|
require 'brakeman/version'
|
@@ -283,6 +283,26 @@ class Brakeman::Report
|
|
283
283
|
end
|
284
284
|
end
|
285
285
|
|
286
|
+
# format output from filename or format
|
287
|
+
def format(filename_or_format)
|
288
|
+
case filename_or_format
|
289
|
+
when /\.html/, :to_html
|
290
|
+
to_html
|
291
|
+
when /\.pdf/, :to_pdf
|
292
|
+
to_pdf
|
293
|
+
when /\.csv/, :to_csv
|
294
|
+
to_csv
|
295
|
+
when /\.json/, :to_json
|
296
|
+
to_json
|
297
|
+
when /\.tabs/, :to_tabs
|
298
|
+
to_tabs
|
299
|
+
when :to_test
|
300
|
+
to_test
|
301
|
+
else
|
302
|
+
to_s
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
286
306
|
#Generate HTML output
|
287
307
|
def to_html
|
288
308
|
out = html_header <<
|
@@ -504,7 +524,7 @@ HEADER
|
|
504
524
|
message
|
505
525
|
end <<
|
506
526
|
"<table id='#{code_id}' class='context' style='display:none'>" <<
|
507
|
-
"<caption>#{warning_file(warning
|
527
|
+
"<caption>#{warning_file(warning) || ''}</caption>"
|
508
528
|
|
509
529
|
unless context.empty?
|
510
530
|
if warning.line - 1 == 1 or warning.line + 1 == 1
|
@@ -567,7 +587,7 @@ HEADER
|
|
567
587
|
checks.send(meth).map do |w|
|
568
588
|
line = w.line || 0
|
569
589
|
w.warning_type.gsub!(/[^\w\s]/, ' ')
|
570
|
-
"#{warning_file
|
590
|
+
"#{warning_file(w, :absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
|
571
591
|
end.join "\n"
|
572
592
|
|
573
593
|
end.join "\n"
|
@@ -584,11 +604,6 @@ HEADER
|
|
584
604
|
report[meth] = @checks.send(meth)
|
585
605
|
report[meth].each do |w|
|
586
606
|
w.message = w.format_message
|
587
|
-
if w.code
|
588
|
-
w.code = w.format_code
|
589
|
-
else
|
590
|
-
w.code = ""
|
591
|
-
end
|
592
607
|
w.context = context_for(@app_tree, w).join("\n")
|
593
608
|
end
|
594
609
|
end
|
@@ -614,10 +629,9 @@ HEADER
|
|
614
629
|
:security_warnings => all_warnings.length,
|
615
630
|
:start_time => tracker.start_time.to_s,
|
616
631
|
:end_time => tracker.end_time.to_s,
|
617
|
-
:timestamp => tracker.end_time.to_s,
|
618
632
|
:duration => tracker.duration,
|
619
633
|
:checks_performed => checks.checks_run.sort,
|
620
|
-
:number_of_controllers =>tracker.controllers.length,
|
634
|
+
:number_of_controllers => tracker.controllers.length,
|
621
635
|
# ignore the "fake" model
|
622
636
|
:number_of_models => tracker.models.length - 1,
|
623
637
|
:number_of_templates => number_of_templates(@tracker),
|
@@ -642,13 +656,13 @@ HEADER
|
|
642
656
|
Set.new(tracker.templates.map {|k,v| v[:name].to_s[/[^.]+/]}).length
|
643
657
|
end
|
644
658
|
|
645
|
-
def warning_file warning,
|
659
|
+
def warning_file warning, absolute = @tracker.options[:absolute_paths]
|
646
660
|
return nil if warning.file.nil?
|
647
661
|
|
648
|
-
if
|
649
|
-
relative_path warning.file
|
650
|
-
else
|
662
|
+
if absolute
|
651
663
|
warning.file
|
664
|
+
else
|
665
|
+
relative_path warning.file
|
652
666
|
end
|
653
667
|
end
|
654
668
|
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -33,7 +33,7 @@ class Brakeman::Scanner
|
|
33
33
|
@app_tree = Brakeman::AppTree.from_options(options)
|
34
34
|
|
35
35
|
if !@app_tree.root || !@app_tree.exists?("app")
|
36
|
-
|
36
|
+
raise NoApplication, "Please supply the path to a Rails application."
|
37
37
|
end
|
38
38
|
|
39
39
|
if @app_tree.exists?("script/rails")
|
@@ -104,8 +104,8 @@ class Brakeman::Scanner
|
|
104
104
|
end
|
105
105
|
|
106
106
|
rescue Exception => e
|
107
|
-
Brakeman.notify "[Notice] Error while processing
|
108
|
-
tracker.error e.exception(e.message + "\nwhile processing
|
107
|
+
Brakeman.notify "[Notice] Error while processing #{path}"
|
108
|
+
tracker.error e.exception(e.message + "\nwhile processing #{path}"), e.backtrace
|
109
109
|
end
|
110
110
|
|
111
111
|
private :process_config_file
|
@@ -355,4 +355,6 @@ class Brakeman::Scanner
|
|
355
355
|
def parse_ruby input
|
356
356
|
@ruby_parser.new.parse input
|
357
357
|
end
|
358
|
+
|
359
|
+
class NoApplication < RuntimeError; end
|
358
360
|
end
|
data/lib/brakeman/tracker.rb
CHANGED
@@ -25,7 +25,7 @@ class Brakeman::Tracker
|
|
25
25
|
@processor = processor
|
26
26
|
@options = options
|
27
27
|
|
28
|
-
@config = {}
|
28
|
+
@config = { :rails => {} }
|
29
29
|
@templates = {}
|
30
30
|
@controllers = {}
|
31
31
|
#Initialize models with the unknown model so
|
@@ -86,7 +86,7 @@ class Brakeman::Tracker
|
|
86
86
|
method_name = "#{definition[1]}.#{method_name}"
|
87
87
|
end
|
88
88
|
|
89
|
-
yield definition, set_name, method_name
|
89
|
+
yield definition, set_name, method_name, info[:file]
|
90
90
|
|
91
91
|
end
|
92
92
|
end
|
@@ -155,12 +155,12 @@ class Brakeman::Tracker
|
|
155
155
|
def index_call_sites
|
156
156
|
finder = Brakeman::FindAllCalls.new self
|
157
157
|
|
158
|
-
self.each_method do |definition, set_name, method_name|
|
159
|
-
finder.process_source definition, set_name, method_name
|
158
|
+
self.each_method do |definition, set_name, method_name, file|
|
159
|
+
finder.process_source definition, :class => set_name, :method => method_name, :file => file
|
160
160
|
end
|
161
161
|
|
162
162
|
self.each_template do |name, template|
|
163
|
-
finder.process_source template[:src],
|
163
|
+
finder.process_source template[:src], :template => template, :file => template[:file]
|
164
164
|
end
|
165
165
|
|
166
166
|
@call_index = Brakeman::CallIndex.new finder.calls
|
@@ -208,7 +208,7 @@ class Brakeman::Tracker
|
|
208
208
|
method_name = "#{definition[1]}.#{method_name}"
|
209
209
|
end
|
210
210
|
|
211
|
-
finder.process_source definition, set_name, method_name
|
211
|
+
finder.process_source definition, :class => set_name, :method => method_name, :file => info[:file]
|
212
212
|
|
213
213
|
end
|
214
214
|
end
|
@@ -217,7 +217,7 @@ class Brakeman::Tracker
|
|
217
217
|
|
218
218
|
if locations.include? :templates
|
219
219
|
self.each_template do |name, template|
|
220
|
-
finder.process_source template[:src],
|
220
|
+
finder.process_source template[:src], :template => template, :file => template[:file]
|
221
221
|
end
|
222
222
|
end
|
223
223
|
|
data/lib/brakeman/util.rb
CHANGED
@@ -275,6 +275,8 @@ module Brakeman::Util
|
|
275
275
|
|
276
276
|
if warning.file
|
277
277
|
File.expand_path warning.file, tracker.options[:app_path]
|
278
|
+
elsif warning.template.is_a? Hash and warning.template[:file]
|
279
|
+
warning.template[:file]
|
278
280
|
else
|
279
281
|
case warning.warning_set
|
280
282
|
when :controller
|
@@ -305,7 +307,7 @@ module Brakeman::Util
|
|
305
307
|
unless type
|
306
308
|
if string_name =~ /Controller$/
|
307
309
|
type = :controller
|
308
|
-
elsif camelize(string_name) == string_name
|
310
|
+
elsif camelize(string_name) == string_name # This is not always true
|
309
311
|
type = :model
|
310
312
|
else
|
311
313
|
type = :template
|
@@ -325,7 +327,7 @@ module Brakeman::Util
|
|
325
327
|
if tracker.models[name] and tracker.models[name][:file]
|
326
328
|
path = tracker.models[name][:file]
|
327
329
|
else
|
328
|
-
path += "/app/
|
330
|
+
path += "/app/models/#{underscore(string_name)}.rb"
|
329
331
|
end
|
330
332
|
when :template
|
331
333
|
if tracker.templates[name] and tracker.templates[name][:file]
|
@@ -383,7 +385,7 @@ module Brakeman::Util
|
|
383
385
|
|
384
386
|
def truncate_table str
|
385
387
|
@terminal_width ||= if $stdin && $stdin.tty?
|
386
|
-
::HighLine
|
388
|
+
::HighLine.new.terminal_size[0]
|
387
389
|
else
|
388
390
|
80
|
389
391
|
end
|
data/lib/brakeman/version.rb
CHANGED
data/lib/brakeman/warning.rb
CHANGED
@@ -5,9 +5,10 @@ require 'brakeman/warning_codes'
|
|
5
5
|
#The Warning class stores information about warnings
|
6
6
|
class Brakeman::Warning
|
7
7
|
attr_reader :called_from, :check, :class, :confidence, :controller,
|
8
|
-
:line, :method, :model, :template, :user_input, :
|
8
|
+
:line, :method, :model, :template, :user_input, :warning_code, :warning_set,
|
9
|
+
:warning_type
|
9
10
|
|
10
|
-
attr_accessor :code, :context, :file, :message
|
11
|
+
attr_accessor :code, :context, :file, :message, :relative_path
|
11
12
|
|
12
13
|
TEXT_CONFIDENCE = [ "High", "Medium", "Weak" ]
|
13
14
|
|
@@ -23,13 +24,14 @@ class Brakeman::Warning
|
|
23
24
|
|
24
25
|
result = options[:result]
|
25
26
|
if result
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
@code ||= result[:call]
|
28
|
+
@file ||= result[:location][:file]
|
29
|
+
|
30
|
+
if result[:location][:type] == :template #template result
|
31
|
+
@template ||= result[:location][:template]
|
29
32
|
else
|
30
|
-
@class ||= result[:location][
|
31
|
-
@method ||= result[:location][
|
32
|
-
@code ||= result[:call]
|
33
|
+
@class ||= result[:location][:class]
|
34
|
+
@method ||= result[:location][:method]
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
@@ -160,9 +162,10 @@ class Brakeman::Warning
|
|
160
162
|
|
161
163
|
def fingerprint
|
162
164
|
loc = self.location
|
163
|
-
location_string = loc && loc.sort_by { |k, v| k.to_s }.
|
165
|
+
location_string = loc && loc.sort_by { |k, v| k.to_s }.inspect
|
164
166
|
warning_code_string = sprintf("%03d", @warning_code)
|
165
167
|
code_string = @code.inspect
|
168
|
+
|
166
169
|
Digest::SHA2.new(256).update("#{warning_code_string}#{code_string}#{location_string}#{@relative_path}#{self.confidence}").to_s
|
167
170
|
end
|
168
171
|
|
data/lib/ruby_parser/bm_sexp.rb
CHANGED
@@ -371,7 +371,11 @@ class Sexp
|
|
371
371
|
# s(:call, nil, :p, s(:arglist, s(:lvar, :y))))
|
372
372
|
def block_args
|
373
373
|
expect :iter, :call_with_block
|
374
|
-
self[2]
|
374
|
+
if self[2] == 0 # ?! See https://github.com/presidentbeef/brakeman/issues/331
|
375
|
+
return Sexp.new(:args)
|
376
|
+
else
|
377
|
+
self[2]
|
378
|
+
end
|
375
379
|
end
|
376
380
|
|
377
381
|
def first_param
|