brakeman 1.5.3 → 1.6.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/brakeman +20 -6
- data/lib/brakeman.rb +21 -1
- data/lib/brakeman/checks.rb +2 -7
- data/lib/brakeman/checks/base_check.rb +31 -26
- data/lib/brakeman/checks/check_basic_auth.rb +1 -7
- data/lib/brakeman/checks/check_cross_site_scripting.rb +38 -33
- data/lib/brakeman/checks/check_evaluation.rb +2 -1
- data/lib/brakeman/checks/check_execute.rb +5 -1
- data/lib/brakeman/checks/check_file_access.rb +6 -4
- data/lib/brakeman/checks/check_link_to.rb +8 -5
- data/lib/brakeman/checks/check_link_to_href.rb +6 -5
- data/lib/brakeman/checks/check_mail_to.rb +2 -4
- data/lib/brakeman/checks/check_mass_assignment.rb +12 -6
- data/lib/brakeman/checks/check_redirect.rb +17 -14
- data/lib/brakeman/checks/check_render.rb +4 -4
- data/lib/brakeman/checks/check_send.rb +4 -2
- data/lib/brakeman/checks/check_session_settings.rb +16 -21
- data/lib/brakeman/checks/check_skip_before_filter.rb +2 -4
- data/lib/brakeman/checks/check_sql.rb +8 -7
- data/lib/brakeman/checks/check_validation_regex.rb +2 -4
- data/lib/brakeman/checks/check_without_protection.rb +8 -9
- data/lib/brakeman/differ.rb +61 -0
- data/lib/brakeman/format/style.css +4 -0
- data/lib/brakeman/options.rb +8 -0
- data/lib/brakeman/processors/alias_processor.rb +5 -7
- data/lib/brakeman/processors/controller_alias_processor.rb +10 -3
- data/lib/brakeman/processors/erb_template_processor.rb +2 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +2 -0
- data/lib/brakeman/processors/gem_processor.rb +8 -0
- data/lib/brakeman/processors/haml_template_processor.rb +2 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +2 -6
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +2 -4
- data/lib/brakeman/processors/lib/render_helper.rb +3 -1
- data/lib/brakeman/processors/library_processor.rb +4 -6
- data/lib/brakeman/processors/template_alias_processor.rb +1 -1
- data/lib/brakeman/report.rb +257 -198
- data/lib/brakeman/rescanner.rb +112 -10
- data/lib/brakeman/scanner.rb +3 -4
- data/lib/brakeman/templates/controller_overview.html.erb +18 -0
- data/lib/brakeman/templates/controller_warnings.html.erb +17 -0
- data/lib/brakeman/templates/error_overview.html.erb +14 -0
- data/lib/brakeman/templates/header.html.erb +38 -0
- data/lib/brakeman/templates/model_warnings.html.erb +17 -0
- data/lib/brakeman/templates/overview.html.erb +28 -0
- data/lib/brakeman/templates/security_warnings.html.erb +28 -0
- data/lib/brakeman/templates/template_overview.html.erb +17 -0
- data/lib/brakeman/templates/view_warnings.html.erb +17 -0
- data/lib/brakeman/templates/warning_overview.html.erb +13 -0
- data/lib/brakeman/tracker.rb +1 -1
- data/lib/brakeman/util.rb +24 -4
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +11 -3
- data/lib/ruby_parser/bm_sexp.rb +5 -11
- metadata +84 -23
data/lib/brakeman/options.rb
CHANGED
@@ -144,6 +144,10 @@ module Brakeman::Options
|
|
144
144
|
options[:combine_locations] = combine
|
145
145
|
end
|
146
146
|
|
147
|
+
opts.on "--[no-]highlights", "Highlight user input in report" do |highlight|
|
148
|
+
options[:highlight_user_input] = highlight
|
149
|
+
end
|
150
|
+
|
147
151
|
opts.on "-m", "--routes", "Report controller information" do
|
148
152
|
options[:report_routes] = true
|
149
153
|
end
|
@@ -173,6 +177,10 @@ module Brakeman::Options
|
|
173
177
|
options[:min_confidence] = 3 - level.to_i
|
174
178
|
end
|
175
179
|
|
180
|
+
opts.on "--compare FILE", "Compare the results of a previous brakeman scan (only JSON is supported)" do |file|
|
181
|
+
options[:previous_results_json] = File.expand_path(file)
|
182
|
+
end
|
183
|
+
|
176
184
|
opts.separator ""
|
177
185
|
opts.separator "Configuration files:"
|
178
186
|
|
@@ -28,6 +28,7 @@ class Brakeman::AliasProcessor < SexpProcessor
|
|
28
28
|
@inside_if = false
|
29
29
|
@ignore_ifs = false
|
30
30
|
@exp_context = []
|
31
|
+
@current_module = nil
|
31
32
|
@tracker = tracker #set in subclass as necessary
|
32
33
|
set_env_defaults
|
33
34
|
end
|
@@ -462,14 +463,11 @@ class Brakeman::AliasProcessor < SexpProcessor
|
|
462
463
|
def process_hash_access target, args
|
463
464
|
if args.length == 1
|
464
465
|
index = args[0]
|
465
|
-
hash_iterate(target) do |key, value|
|
466
|
-
if key == index
|
467
|
-
return value
|
468
|
-
end
|
469
|
-
end
|
470
|
-
end
|
471
466
|
|
472
|
-
|
467
|
+
hash_access(target, index)
|
468
|
+
else
|
469
|
+
nil
|
470
|
+
end
|
473
471
|
end
|
474
472
|
|
475
473
|
#Join two array literals into one.
|
@@ -15,6 +15,7 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
15
15
|
@tracker = tracker
|
16
16
|
@rendered = false
|
17
17
|
@current_class = @current_module = @current_method = nil
|
18
|
+
@method_cache = {} #Cache method lookups
|
18
19
|
end
|
19
20
|
|
20
21
|
def process_controller name, src
|
@@ -248,6 +249,11 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
248
249
|
def find_method method_name, klass
|
249
250
|
return nil if sexp? method_name
|
250
251
|
method_name = method_name.to_sym
|
252
|
+
|
253
|
+
if method = @method_cache[method_name]
|
254
|
+
return method
|
255
|
+
end
|
256
|
+
|
251
257
|
controller = @tracker.controllers[klass]
|
252
258
|
controller ||= @tracker.libs[klass]
|
253
259
|
|
@@ -260,13 +266,14 @@ class Brakeman::ControllerAliasProcessor < Brakeman::AliasProcessor
|
|
260
266
|
controller[:includes].each do |included|
|
261
267
|
method = find_method method_name, included
|
262
268
|
if method
|
263
|
-
|
269
|
+
@method_cache[method_name] = method
|
270
|
+
return method
|
264
271
|
end
|
265
272
|
end
|
266
273
|
|
267
|
-
find_method method_name, controller[:parent]
|
274
|
+
@method_cache[method_name] = find_method method_name, controller[:parent]
|
268
275
|
else
|
269
|
-
{ :controller => controller[:name], :method => method }
|
276
|
+
@method_cache[method_name] = { :controller => controller[:name], :method => method }
|
270
277
|
end
|
271
278
|
else
|
272
279
|
nil
|
@@ -46,8 +46,10 @@ class Brakeman::ErbTemplateProcessor < Brakeman::TemplateProcessor
|
|
46
46
|
exp[3] = process(exp[3])
|
47
47
|
make_render_in_view exp
|
48
48
|
else
|
49
|
+
#TODO: Is it really necessary to create a new Sexp here?
|
49
50
|
args = exp[3] = process(exp[3])
|
50
51
|
call = Sexp.new :call, target, method, args
|
52
|
+
call.original_line(exp.original_line)
|
51
53
|
call.line(exp.line)
|
52
54
|
call
|
53
55
|
end
|
@@ -43,8 +43,10 @@ class Brakeman::ErubisTemplateProcessor < Brakeman::TemplateProcessor
|
|
43
43
|
exp[3] = process exp[3]
|
44
44
|
make_render_in_view exp
|
45
45
|
else
|
46
|
+
#TODO: Is it really necessary to create a new Sexp here?
|
46
47
|
args = exp[3] = process(exp[3])
|
47
48
|
call = Sexp.new :call, target, method, args
|
49
|
+
call.original_line(exp.original_line)
|
48
50
|
call.line(exp.line)
|
49
51
|
call
|
50
52
|
end
|
@@ -17,6 +17,12 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor
|
|
17
17
|
elsif @tracker.config[:gems][:rails] =~ /(\d+.\d+.\d+)/
|
18
18
|
@tracker.config[:rails_version] = $1
|
19
19
|
end
|
20
|
+
|
21
|
+
if @tracker.config[:gems][:rails_xss]
|
22
|
+
@tracker.config[:escape_html] = true
|
23
|
+
|
24
|
+
Brakeman.notify "[Notice] Escaping HTML by default"
|
25
|
+
end
|
20
26
|
end
|
21
27
|
|
22
28
|
def process_call exp
|
@@ -25,6 +31,8 @@ class Brakeman::GemProcessor < Brakeman::BaseProcessor
|
|
25
31
|
|
26
32
|
if sexp? args[1]
|
27
33
|
@tracker.config[:gems][args[0][1].to_sym] = args[1][1]
|
34
|
+
else
|
35
|
+
@tracker.config[:gems][args[0][1].to_sym] = ">=0.0.0"
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
@@ -94,8 +94,10 @@ class Brakeman::HamlTemplateProcessor < Brakeman::TemplateProcessor
|
|
94
94
|
exp[3] = process exp[3]
|
95
95
|
make_render_in_view exp
|
96
96
|
else
|
97
|
+
#TODO: Do we really need a new Sexp here?
|
97
98
|
args = process exp[3]
|
98
99
|
call = Sexp.new :call, target, method, args
|
100
|
+
call.original_line(exp.original_line)
|
99
101
|
call.line(exp.line)
|
100
102
|
call
|
101
103
|
end
|
@@ -258,12 +258,8 @@ class Brakeman::Rails2RoutesProcessor < Brakeman::BaseProcessor
|
|
258
258
|
#Otherwise, returns nil.
|
259
259
|
def check_for_controller_name args
|
260
260
|
args.each do |a|
|
261
|
-
if hash? a
|
262
|
-
|
263
|
-
if k[1] == :controller
|
264
|
-
return v[1]
|
265
|
-
end
|
266
|
-
end
|
261
|
+
if hash? a and value = hash_access(a, :controller)
|
262
|
+
return value[1]
|
267
263
|
end
|
268
264
|
end
|
269
265
|
|
@@ -70,14 +70,12 @@ class Brakeman::Rails3RoutesProcessor < Brakeman::BaseProcessor
|
|
70
70
|
def process_root exp
|
71
71
|
args = exp[3][1..-1]
|
72
72
|
|
73
|
-
|
74
|
-
if
|
73
|
+
if value = hash_access(args[0], :to)
|
74
|
+
if string? value[1]
|
75
75
|
controller, action = extract_action v[1]
|
76
76
|
|
77
77
|
self.current_controller = controller
|
78
78
|
@tracker.routes[@current_controller] << action.to_sym
|
79
|
-
|
80
|
-
break
|
81
79
|
end
|
82
80
|
end
|
83
81
|
|
@@ -115,7 +115,9 @@ module Brakeman::RenderHelper
|
|
115
115
|
#that values came from another file
|
116
116
|
template_env.all.each do |var, value|
|
117
117
|
unless value.original_line
|
118
|
-
|
118
|
+
#TODO: This has been broken for a while now and no one noticed
|
119
|
+
#so maybe we can skip it
|
120
|
+
value.original_line(value.line)
|
119
121
|
end
|
120
122
|
end
|
121
123
|
|
@@ -92,12 +92,11 @@ class Brakeman::LibraryProcessor < Brakeman::BaseProcessor
|
|
92
92
|
|
93
93
|
def process_defn exp
|
94
94
|
exp[0] = :methdef
|
95
|
-
exp[3] = @alias_processor.process exp[3]
|
96
95
|
|
97
96
|
if @current_class
|
98
|
-
@current_class[:public][exp[1]] = exp
|
97
|
+
@current_class[:public][exp[1]] = exp
|
99
98
|
elsif @current_module
|
100
|
-
@current_module[:public][exp[1]] = exp
|
99
|
+
@current_module[:public][exp[1]] = exp
|
101
100
|
end
|
102
101
|
|
103
102
|
exp
|
@@ -105,12 +104,11 @@ class Brakeman::LibraryProcessor < Brakeman::BaseProcessor
|
|
105
104
|
|
106
105
|
def process_defs exp
|
107
106
|
exp[0] = :selfdef
|
108
|
-
exp[4] = @alias_processor.process exp[4]
|
109
107
|
|
110
108
|
if @current_class
|
111
|
-
@current_class[:public][exp[2]] = exp
|
109
|
+
@current_class[:public][exp[2]] = exp
|
112
110
|
elsif @current_module
|
113
|
-
@current_module[:public][exp[3]] = exp
|
111
|
+
@current_module[:public][exp[3]] = exp
|
114
112
|
end
|
115
113
|
|
116
114
|
exp
|
@@ -17,7 +17,7 @@ class Brakeman::TemplateAliasProcessor < Brakeman::AliasProcessor
|
|
17
17
|
|
18
18
|
#Process template
|
19
19
|
def process_template name, args
|
20
|
-
if @called_from and @called_from.match(/Template:#{
|
20
|
+
if @called_from and @called_from.match(/Template:#{name}$/)
|
21
21
|
Brakeman.debug "Skipping circular render from #{@template[:name]} to #{name}"
|
22
22
|
return
|
23
23
|
end
|
data/lib/brakeman/report.rb
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
require 'cgi'
|
2
2
|
require 'set'
|
3
|
-
require 'ruport'
|
4
3
|
require 'brakeman/processors/output_processor'
|
5
4
|
require 'brakeman/util'
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
require 'terminal-table'
|
6
|
+
require 'highline/system_extensions'
|
7
|
+
require "csv"
|
8
|
+
require 'brakeman/version'
|
9
|
+
|
10
|
+
if CSV.const_defined? :Reader
|
11
|
+
# Ruby 1.8 compatible
|
12
|
+
require 'fastercsv'
|
13
|
+
Object.send(:remove_const, :CSV)
|
14
|
+
CSV = FasterCSV
|
15
|
+
else
|
16
|
+
# CSV is now FasterCSV in ruby 1.9
|
16
17
|
end
|
17
18
|
|
18
19
|
#Generates a report based on the Tracker and the results of
|
@@ -33,66 +34,54 @@ class Brakeman::Report
|
|
33
34
|
@checks = tracker.checks
|
34
35
|
@element_id = 0 #Used for HTML ids
|
35
36
|
@warnings_summary = nil
|
37
|
+
@highlight_user_input = tracker.options[:highlight_user_input]
|
36
38
|
end
|
37
39
|
|
38
40
|
#Generate summary table of what was parsed
|
39
41
|
def generate_overview html = false
|
40
|
-
|
41
|
-
warnings = checks.warnings.length +
|
42
|
-
checks.controller_warnings.length +
|
43
|
-
checks.model_warnings.length +
|
44
|
-
checks.template_warnings.length
|
45
|
-
|
46
|
-
#Add number of high confidence warnings in summary.
|
47
|
-
#Skipping for CSV because it makes the cell text instead of
|
48
|
-
#a number.
|
49
|
-
unless tracker.options[:output_format] == :to_csv
|
50
|
-
summary = warnings_summary
|
42
|
+
warnings = all_warnings.length
|
51
43
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
44
|
+
if html
|
45
|
+
load_and_render_erb('overview', binding)
|
46
|
+
else
|
47
|
+
Terminal::Table.new(:headings => ['Scanned/Reported', 'Total']) do |t|
|
48
|
+
t.add_row ['Controllers', tracker.controllers.length]
|
49
|
+
t.add_row ['Models', tracker.models.length - 1]
|
50
|
+
t.add_row ['Templates', number_of_templates(@tracker)]
|
51
|
+
t.add_row ['Errors', tracker.errors.length]
|
52
|
+
t.add_row ['Security Warnings', "#{warnings} (#{warnings_summary[:high_confidence]})"]
|
56
53
|
end
|
57
54
|
end
|
58
|
-
|
59
|
-
table = Ruport::Data::Table(["Scanned/Reported", "Total"])
|
60
|
-
table << { "Scanned/Reported" => "Controllers", "Total" => tracker.controllers.length }
|
61
|
-
#One less because of the 'fake' one used for unknown models
|
62
|
-
table << { "Scanned/Reported" => "Models", "Total" => tracker.models.length - 1 }
|
63
|
-
table << { "Scanned/Reported" => "Templates", "Total" => templates }
|
64
|
-
table << { "Scanned/Reported" => "Errors", "Total" => tracker.errors.length }
|
65
|
-
table << { "Scanned/Reported" => "Security Warnings", "Total" => warnings}
|
66
55
|
end
|
67
56
|
|
68
57
|
#Generate table of how many warnings of each warning type were reported
|
69
|
-
def generate_warning_overview
|
70
|
-
table = Ruport::Data::Table(["Warning Type", "Total"])
|
58
|
+
def generate_warning_overview html = false
|
71
59
|
types = warnings_summary.keys
|
72
60
|
types.delete :high_confidence
|
73
|
-
|
74
|
-
|
61
|
+
|
62
|
+
if html
|
63
|
+
load_and_render_erb('warning_overview', binding)
|
64
|
+
else
|
65
|
+
Terminal::Table.new(:headings => ['Warning Type', 'Total']) do |t|
|
66
|
+
types.sort.each do |warning_type|
|
67
|
+
t.add_row [warning_type, warnings_summary[warning_type]]
|
68
|
+
end
|
69
|
+
end
|
75
70
|
end
|
76
|
-
table
|
77
71
|
end
|
78
72
|
|
79
73
|
#Generate table of errors or return nil if no errors
|
80
74
|
def generate_errors html = false
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
w[:error] = CGI.escapeHTML w[:error]
|
75
|
+
if tracker.errors.any?
|
76
|
+
if html
|
77
|
+
load_and_render_erb('error_overview', binding)
|
78
|
+
else
|
79
|
+
Terminal::Table.new(:headings => ['Error', 'Location']) do |t|
|
80
|
+
tracker.errors.each do |error|
|
81
|
+
t.add_row [error[:error], error[:backtrace][0]]
|
82
|
+
end
|
90
83
|
end
|
91
|
-
|
92
|
-
table << { "Error" => w[:error], "Location" => w[:backtrace][0] }
|
93
84
|
end
|
94
|
-
|
95
|
-
table
|
96
85
|
else
|
97
86
|
nil
|
98
87
|
end
|
@@ -100,7 +89,7 @@ class Brakeman::Report
|
|
100
89
|
|
101
90
|
#Generate table of general security warnings
|
102
91
|
def generate_warnings html = false
|
103
|
-
|
92
|
+
warning_messages = []
|
104
93
|
checks.warnings.each do |warning|
|
105
94
|
w = warning.to_row
|
106
95
|
|
@@ -109,27 +98,36 @@ class Brakeman::Report
|
|
109
98
|
w["Message"] = with_context warning, w["Message"]
|
110
99
|
else
|
111
100
|
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
101
|
+
w["Message"] = text_message warning, w["Message"]
|
112
102
|
end
|
113
103
|
|
114
|
-
|
104
|
+
warning_messages << w
|
115
105
|
end
|
116
106
|
|
117
|
-
|
118
|
-
|
119
|
-
table.sort_rows_by! "Confidence"
|
107
|
+
stabilizer = 0
|
108
|
+
warning_messages = warning_messages.sort_by{|row| stabilizer += 1; [row['Confidence'], row['Warning Type'], row['Class'], stabilizer]}
|
120
109
|
|
121
|
-
if
|
122
|
-
|
123
|
-
|
110
|
+
if html
|
111
|
+
load_and_render_erb('security_warnings', binding)
|
112
|
+
else
|
113
|
+
if warning_messages.empty?
|
114
|
+
Terminal::Table.new(:headings => ['General Warnings']) do |t|
|
115
|
+
t.add_row ['[NONE]']
|
116
|
+
end
|
117
|
+
else
|
118
|
+
Terminal::Table.new(:headings => ["Confidence", "Class", "Method", "Warning Type", "Message"]) do |t|
|
119
|
+
warning_messages.each do |row|
|
120
|
+
t.add_row [row["Confidence"], row["Class"], row["Method"], row["Warning Type"], row["Message"]]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
124
|
end
|
125
|
-
|
126
|
-
table
|
127
125
|
end
|
128
126
|
|
129
127
|
#Generate table of template warnings or return nil if no warnings
|
130
128
|
def generate_template_warnings html = false
|
131
|
-
|
132
|
-
|
129
|
+
if checks.template_warnings.any?
|
130
|
+
warnings = []
|
133
131
|
checks.template_warnings.each do |warning|
|
134
132
|
w = warning.to_row :template
|
135
133
|
|
@@ -138,18 +136,24 @@ class Brakeman::Report
|
|
138
136
|
w["Message"] = with_context warning, w["Message"]
|
139
137
|
else
|
140
138
|
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
139
|
+
w["Message"] = text_message warning, w["Message"]
|
141
140
|
end
|
142
141
|
|
143
|
-
|
142
|
+
warnings << w
|
144
143
|
end
|
145
144
|
|
146
|
-
if
|
147
|
-
|
145
|
+
return nil if warnings.empty?
|
146
|
+
|
147
|
+
stabilizer = 0
|
148
|
+
warnings = warnings.sort_by{|row| stabilizer += 1; [row["Confidence"], row["Warning Type"], row["Template"], stabilizer]}
|
149
|
+
if html
|
150
|
+
load_and_render_erb('view_warnings', binding)
|
148
151
|
else
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
152
|
+
Terminal::Table.new(:headings => ["Confidence", "Template", "Warning Type", "Message"]) do |t|
|
153
|
+
warnings.each do |warning|
|
154
|
+
t.add_row [warning["Confidence"], warning["Template"], warning["Warning Type"], warning["Message"]]
|
155
|
+
end
|
156
|
+
end
|
153
157
|
end
|
154
158
|
else
|
155
159
|
nil
|
@@ -158,8 +162,8 @@ class Brakeman::Report
|
|
158
162
|
|
159
163
|
#Generate table of model warnings or return nil if no warnings
|
160
164
|
def generate_model_warnings html = false
|
161
|
-
|
162
|
-
|
165
|
+
if checks.model_warnings.any?
|
166
|
+
warnings = []
|
163
167
|
checks.model_warnings.each do |warning|
|
164
168
|
w = warning.to_row :model
|
165
169
|
|
@@ -168,18 +172,24 @@ class Brakeman::Report
|
|
168
172
|
w["Message"] = with_context warning, w["Message"]
|
169
173
|
else
|
170
174
|
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
175
|
+
w["Message"] = text_message warning, w["Message"]
|
171
176
|
end
|
172
177
|
|
173
|
-
|
178
|
+
warnings << w
|
174
179
|
end
|
175
180
|
|
176
|
-
if
|
177
|
-
|
181
|
+
return nil if warnings.empty?
|
182
|
+
stabilizer = 0
|
183
|
+
warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"],row["Warning Type"], row["Model"], stabilizer]}
|
184
|
+
|
185
|
+
if html
|
186
|
+
load_and_render_erb('model_warnings', binding)
|
178
187
|
else
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
188
|
+
Terminal::Table.new(:headings => ["Confidence", "Model", "Warning Type", "Message"]) do |t|
|
189
|
+
warnings.each do |warning|
|
190
|
+
t.add_row [warning["Confidence"], warning["Model"], warning["Warning Type"], warning["Message"]]
|
191
|
+
end
|
192
|
+
end
|
183
193
|
end
|
184
194
|
else
|
185
195
|
nil
|
@@ -189,7 +199,7 @@ class Brakeman::Report
|
|
189
199
|
#Generate table of controller warnings or nil if no warnings
|
190
200
|
def generate_controller_warnings html = false
|
191
201
|
unless checks.controller_warnings.empty?
|
192
|
-
|
202
|
+
warnings = []
|
193
203
|
checks.controller_warnings.each do |warning|
|
194
204
|
w = warning.to_row :controller
|
195
205
|
|
@@ -198,18 +208,25 @@ class Brakeman::Report
|
|
198
208
|
w["Message"] = with_context warning, w["Message"]
|
199
209
|
else
|
200
210
|
w["Confidence"] = TEXT_CONFIDENCE[w["Confidence"]]
|
211
|
+
w["Message"] = text_message warning, w["Message"]
|
201
212
|
end
|
202
213
|
|
203
|
-
|
214
|
+
warnings << w
|
204
215
|
end
|
205
216
|
|
206
|
-
if
|
207
|
-
|
217
|
+
return nil if warnings.empty?
|
218
|
+
|
219
|
+
stabilizer = 0
|
220
|
+
warnings = warnings.sort_by{|row| stabilizer +=1; [row["Confidence"], row["Warning Type"], row["Controller"], stabilizer]}
|
221
|
+
|
222
|
+
if html
|
223
|
+
load_and_render_erb('controller_warnings', binding)
|
208
224
|
else
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
225
|
+
Terminal::Table.new(:headings => ["Confidence", "Controller", "Warning Type", "Message"]) do |t|
|
226
|
+
warnings.each do |warning|
|
227
|
+
t.add_row [warning["Confidence"], warning["Controller"], warning["Warning Type"], warning["Message"]]
|
228
|
+
end
|
229
|
+
end
|
213
230
|
end
|
214
231
|
else
|
215
232
|
nil
|
@@ -217,8 +234,8 @@ class Brakeman::Report
|
|
217
234
|
end
|
218
235
|
|
219
236
|
#Generate table of controllers and routes found for those controllers
|
220
|
-
def generate_controllers
|
221
|
-
|
237
|
+
def generate_controllers html=false
|
238
|
+
controller_rows = []
|
222
239
|
tracker.controllers.keys.map{|k| k.to_s}.sort.each do |name|
|
223
240
|
name = name.to_sym
|
224
241
|
c = tracker.controllers[name]
|
@@ -243,70 +260,86 @@ class Brakeman::Report
|
|
243
260
|
routes = "[None]"
|
244
261
|
end
|
245
262
|
|
246
|
-
|
263
|
+
controller_rows << { "Name" => name.to_s,
|
247
264
|
"Parent" => c[:parent].to_s,
|
248
265
|
"Includes" => c[:includes].join(", "),
|
249
266
|
"Routes" => routes
|
250
267
|
}
|
251
268
|
end
|
252
|
-
|
269
|
+
controller_rows = controller_rows.sort_by{|row| row['Name']}
|
270
|
+
|
271
|
+
if html
|
272
|
+
load_and_render_erb('controller_overview', binding)
|
273
|
+
else
|
274
|
+
Terminal::Table.new(:headings => ['Name', 'Parent', 'Includes', 'Routes']) do |t|
|
275
|
+
controller_rows.each do |row|
|
276
|
+
t.add_row [row['Name'], row['Parent'], row['Includes'], row['Routes']]
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
253
280
|
end
|
254
281
|
|
255
282
|
#Generate listings of templates and their output
|
256
283
|
def generate_templates html = false
|
257
284
|
out_processor = Brakeman::OutputProcessor.new
|
258
|
-
|
285
|
+
template_rows = {}
|
259
286
|
tracker.templates.each do |name, template|
|
260
287
|
unless template[:outputs].empty?
|
261
288
|
template[:outputs].each do |out|
|
262
289
|
out = out_processor.format out
|
263
290
|
out = CGI.escapeHTML(out) if html
|
264
|
-
|
265
|
-
|
291
|
+
template_rows[name] ||= []
|
292
|
+
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
template_rows = template_rows.sort_by{|name, value| name.to_s}
|
298
|
+
|
299
|
+
if html
|
300
|
+
load_and_render_erb('template_overview', binding)
|
301
|
+
else
|
302
|
+
output = ''
|
303
|
+
template_rows.each do |template|
|
304
|
+
output << template.first.to_s << "\n\n"
|
305
|
+
table = Terminal::Table.new(:headings => ['Output']) do |t|
|
306
|
+
# template[1] is an array of calls
|
307
|
+
template[1].each do |v|
|
308
|
+
t.add_row [v]
|
309
|
+
end
|
266
310
|
end
|
311
|
+
|
312
|
+
output << table.to_s << "\n\n"
|
267
313
|
end
|
314
|
+
|
315
|
+
output
|
268
316
|
end
|
269
|
-
Ruport::Data::Grouping(table, :by => "Name")
|
270
317
|
end
|
271
318
|
|
272
319
|
#Generate HTML output
|
273
320
|
def to_html
|
274
321
|
out = html_header <<
|
275
|
-
|
276
|
-
|
277
|
-
generate_warning_overview.to_html
|
322
|
+
generate_overview(true) <<
|
323
|
+
generate_warning_overview(true)
|
278
324
|
|
279
|
-
#Return early if only summarizing
|
325
|
+
# Return early if only summarizing
|
280
326
|
if tracker.options[:summary_only]
|
281
327
|
return out
|
282
328
|
end
|
283
329
|
|
284
330
|
if tracker.options[:report_routes] or tracker.options[:debug]
|
285
|
-
out <<
|
286
|
-
generate_controllers.to_html
|
331
|
+
out << generate_controllers(true).to_s
|
287
332
|
end
|
288
333
|
|
289
334
|
if tracker.options[:debug]
|
290
|
-
out <<
|
291
|
-
generate_templates(true).to_html
|
335
|
+
out << generate_templates(true).to_s
|
292
336
|
end
|
293
337
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
res = generate_warnings(true)
|
300
|
-
out << "<h2>Security Warnings</h2>" << res.to_html if res
|
301
|
-
|
302
|
-
res = generate_controller_warnings(true)
|
303
|
-
out << res.to_html if res
|
304
|
-
|
305
|
-
res = generate_model_warnings(true)
|
306
|
-
out << res.to_html if res
|
307
|
-
|
308
|
-
res = generate_template_warnings(true)
|
309
|
-
out << res.to_html if res
|
338
|
+
out << generate_errors(true).to_s
|
339
|
+
out << generate_warnings(true).to_s
|
340
|
+
out << generate_controller_warnings(true).to_s
|
341
|
+
out << generate_model_warnings(true).to_s
|
342
|
+
out << generate_template_warnings(true).to_s
|
310
343
|
|
311
344
|
out << "</body></html>"
|
312
345
|
end
|
@@ -314,9 +347,9 @@ class Brakeman::Report
|
|
314
347
|
#Output text version of the report
|
315
348
|
def to_s
|
316
349
|
out = text_header <<
|
317
|
-
"\n+SUMMARY+\n" <<
|
318
|
-
generate_overview.to_s << "\n" <<
|
319
|
-
generate_warning_overview.to_s << "\n"
|
350
|
+
"\n\n+SUMMARY+\n\n" <<
|
351
|
+
truncate_table(generate_overview.to_s) << "\n\n" <<
|
352
|
+
truncate_table(generate_warning_overview.to_s) << "\n"
|
320
353
|
|
321
354
|
#Return output early if only summarizing
|
322
355
|
if tracker.options[:summary_only]
|
@@ -324,71 +357,77 @@ class Brakeman::Report
|
|
324
357
|
end
|
325
358
|
|
326
359
|
if tracker.options[:report_routes] or tracker.options[:debug]
|
327
|
-
out << "+CONTROLLERS+\n" <<
|
328
|
-
generate_controllers.to_s << "\n"
|
360
|
+
out << "\n+CONTROLLERS+\n" <<
|
361
|
+
truncate_table(generate_controllers.to_s) << "\n"
|
329
362
|
end
|
330
363
|
|
331
364
|
if tracker.options[:debug]
|
332
|
-
out << "+TEMPLATES+\n\n" <<
|
333
|
-
generate_templates.to_s << "\n"
|
365
|
+
out << "\n+TEMPLATES+\n\n" <<
|
366
|
+
truncate_table(generate_templates.to_s) << "\n"
|
334
367
|
end
|
335
368
|
|
336
369
|
res = generate_errors
|
337
|
-
out << "+
|
370
|
+
out << "+Errors+\n" << truncate_table(res.to_s) if res
|
338
371
|
|
339
372
|
res = generate_warnings
|
340
|
-
out << "+SECURITY WARNINGS+\n" << res.to_s
|
373
|
+
out << "\n\n+SECURITY WARNINGS+\n\n" << truncate_table(res.to_s) if res
|
341
374
|
|
342
375
|
res = generate_controller_warnings
|
343
|
-
out <<
|
376
|
+
out << "\n\n\nController Warnings:\n\n" << truncate_table(res.to_s) if res
|
344
377
|
|
345
378
|
res = generate_model_warnings
|
346
|
-
out <<
|
379
|
+
out << "\n\n\nModel Warnings:\n\n" << truncate_table(res.to_s) if res
|
347
380
|
|
348
381
|
res = generate_template_warnings
|
349
|
-
out <<
|
382
|
+
out << "\n\nView Warnings:\n\n" << truncate_table(res.to_s) if res
|
350
383
|
|
384
|
+
out << "\n"
|
351
385
|
out
|
352
386
|
end
|
353
387
|
|
354
388
|
#Generate CSV output
|
355
389
|
def to_csv
|
356
|
-
|
357
|
-
"\nSUMMARY\n"
|
358
|
-
|
359
|
-
|
390
|
+
output = csv_header
|
391
|
+
output << "\nSUMMARY\n"
|
392
|
+
|
393
|
+
output << table_to_csv(generate_overview) << "\n"
|
394
|
+
|
395
|
+
output << table_to_csv(generate_warning_overview) << "\n"
|
360
396
|
|
361
397
|
#Return output early if only summarizing
|
362
398
|
if tracker.options[:summary_only]
|
363
|
-
return
|
399
|
+
return output
|
364
400
|
end
|
365
401
|
|
366
402
|
if tracker.options[:report_routes] or tracker.options[:debug]
|
367
|
-
|
368
|
-
generate_controllers
|
403
|
+
output << "CONTROLLERS\n"
|
404
|
+
output << table_to_csv(generate_controllers) << "\n"
|
369
405
|
end
|
370
406
|
|
371
407
|
if tracker.options[:debug]
|
372
|
-
|
373
|
-
generate_templates
|
408
|
+
output << "TEMPLATES\n\n"
|
409
|
+
output << table_to_csv(generate_templates) << "\n"
|
374
410
|
end
|
375
411
|
|
376
412
|
res = generate_errors
|
377
|
-
|
413
|
+
output << "ERRORS\n" << table_to_csv(res) << "\n" if res
|
378
414
|
|
379
415
|
res = generate_warnings
|
380
|
-
|
416
|
+
output << "SECURITY WARNINGS\n" << table_to_csv(res) << "\n" if res
|
381
417
|
|
418
|
+
output << "Controller Warnings\n"
|
382
419
|
res = generate_controller_warnings
|
383
|
-
|
420
|
+
output << table_to_csv(res) << "\n" if res
|
384
421
|
|
422
|
+
output << "Model Warnings\n"
|
385
423
|
res = generate_model_warnings
|
386
|
-
|
424
|
+
output << table_to_csv(res) << "\n" if res
|
387
425
|
|
388
426
|
res = generate_template_warnings
|
389
|
-
|
427
|
+
output << "Template Warnings\n"
|
428
|
+
output << table_to_csv(res) << "\n" if res
|
390
429
|
|
391
|
-
|
430
|
+
output
|
392
431
|
end
|
393
432
|
|
394
433
|
#Not yet implemented
|
@@ -414,44 +453,7 @@ class Brakeman::Report
|
|
414
453
|
raise "Cannot find CSS stylesheet for HTML: #{tracker.options[:html_style]}"
|
415
454
|
end
|
416
455
|
|
417
|
-
|
418
|
-
<!DOCTYPE HTML SYSTEM>
|
419
|
-
<html>
|
420
|
-
<head>
|
421
|
-
<title>Brakeman Report</title>
|
422
|
-
<script type="text/javascript">
|
423
|
-
function toggle(context) {
|
424
|
-
var elem = document.getElementById(context);
|
425
|
-
|
426
|
-
if (elem.style.display != "block")
|
427
|
-
elem.style.display = "block";
|
428
|
-
else
|
429
|
-
elem.style.display = "none";
|
430
|
-
|
431
|
-
elem.parentNode.scrollIntoView();
|
432
|
-
}
|
433
|
-
</script>
|
434
|
-
<style type="text/css">
|
435
|
-
#{css}
|
436
|
-
</style>
|
437
|
-
</head>
|
438
|
-
<body>
|
439
|
-
<h1>Brakeman Report</h1>
|
440
|
-
<table>
|
441
|
-
<tr>
|
442
|
-
<th>Application Path</th>
|
443
|
-
<th>Rails Version</th>
|
444
|
-
<th>Report Generation Time</th>
|
445
|
-
<th>Checks Performed</th>
|
446
|
-
</tr>
|
447
|
-
<tr>
|
448
|
-
<td>#{File.expand_path tracker.options[:app_path]}</td>
|
449
|
-
<td>#{rails_version}</td>
|
450
|
-
<td>#{Time.now}</td>
|
451
|
-
<td>#{checks.checks_run.sort.join(", ")}</td>
|
452
|
-
</tr>
|
453
|
-
</table>
|
454
|
-
HTML
|
456
|
+
load_and_render_erb('header', binding)
|
455
457
|
end
|
456
458
|
|
457
459
|
#Generate header for text output
|
@@ -461,9 +463,9 @@ class Brakeman::Report
|
|
461
463
|
|
462
464
|
#Generate header for CSV output
|
463
465
|
def csv_header
|
464
|
-
header =
|
465
|
-
header << [File.expand_path(tracker.options[:app_path]), Time.now.to_s, checks.checks_run.sort.join(", "), rails_version]
|
466
|
-
"BRAKEMAN REPORT\n\n"
|
466
|
+
header = CSV.generate_line(["Application Path", "Report Generation Time", "Checks Performed", "Rails Version"])
|
467
|
+
header << CSV.generate_line([File.expand_path(tracker.options[:app_path]), Time.now.to_s, checks.checks_run.sort.join(", "), rails_version])
|
468
|
+
"BRAKEMAN REPORT\n\n" + header
|
467
469
|
end
|
468
470
|
|
469
471
|
#Return summary of warnings in hash and store in @warnings_summary
|
@@ -473,10 +475,7 @@ class Brakeman::Report
|
|
473
475
|
summary = Hash.new(0)
|
474
476
|
high_confidence_warnings = 0
|
475
477
|
|
476
|
-
[
|
477
|
-
checks.controller_warnings,
|
478
|
-
checks.model_warnings,
|
479
|
-
checks.template_warnings].each do |warnings|
|
478
|
+
[all_warnings].each do |warnings|
|
480
479
|
|
481
480
|
warnings.each do |warning|
|
482
481
|
summary[warning.warning_type.to_s] += 1
|
@@ -491,6 +490,28 @@ class Brakeman::Report
|
|
491
490
|
@warnings_summary = summary
|
492
491
|
end
|
493
492
|
|
493
|
+
#Escape warning message and highlight user input in text output
|
494
|
+
def text_message warning, message
|
495
|
+
if @highlight_user_input and warning.user_input
|
496
|
+
user_input = warning.format_user_input
|
497
|
+
message.gsub(user_input, "+#{user_input}+")
|
498
|
+
else
|
499
|
+
message
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
#Escape warning message and highlight user input in HTML output
|
504
|
+
def html_message warning, message
|
505
|
+
message = CGI.escapeHTML(message)
|
506
|
+
|
507
|
+
if @highlight_user_input and warning.user_input
|
508
|
+
user_input = warning.format_user_input
|
509
|
+
|
510
|
+
message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
|
511
|
+
end
|
512
|
+
|
513
|
+
message
|
514
|
+
end
|
494
515
|
|
495
516
|
#Generate HTML for warnings, including context show/hidden via Javascript
|
496
517
|
def with_context warning, message
|
@@ -501,12 +522,14 @@ class Brakeman::Report
|
|
501
522
|
tracker.options[:message_limit] > 0 and
|
502
523
|
message.length > tracker.options[:message_limit]
|
503
524
|
|
504
|
-
full_message = message
|
525
|
+
full_message = html_message(warning, message)
|
505
526
|
message = message[0..tracker.options[:message_limit]] << "..."
|
506
527
|
end
|
507
528
|
|
529
|
+
message = html_message(warning, message)
|
530
|
+
|
508
531
|
if context.empty? and not full_message
|
509
|
-
return
|
532
|
+
return message
|
510
533
|
end
|
511
534
|
|
512
535
|
@element_id += 1
|
@@ -516,10 +539,10 @@ class Brakeman::Report
|
|
516
539
|
alt = false
|
517
540
|
output = "<div class='warning_message' onClick=\"toggle('#{code_id}');toggle('#{message_id}');toggle('#{full_message_id}')\" >" <<
|
518
541
|
if full_message
|
519
|
-
"<span id='#{message_id}' style='display:block' >#{
|
520
|
-
"<span id='#{full_message_id}' style='display:none'>#{
|
542
|
+
"<span id='#{message_id}' style='display:block' >#{message}</span>" <<
|
543
|
+
"<span id='#{full_message_id}' style='display:none'>#{full_message}</span>"
|
521
544
|
else
|
522
|
-
|
545
|
+
message
|
523
546
|
end <<
|
524
547
|
"<table id='#{code_id}' class='context' style='display:none'>" <<
|
525
548
|
"<caption>#{(warning.file || '').gsub(tracker.options[:app_path], "")}</caption>"
|
@@ -614,6 +637,42 @@ class Brakeman::Report
|
|
614
637
|
def to_json
|
615
638
|
require 'json'
|
616
639
|
|
617
|
-
|
640
|
+
errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
|
641
|
+
warnings = all_warnings.map { |w| w.to_hash }.sort_by{|w| w[:file]}
|
642
|
+
scan_info = {
|
643
|
+
:app_path => File.expand_path(tracker.options[:app_path]),
|
644
|
+
:rails_version => rails_version,
|
645
|
+
:security_warnings => all_warnings.length,
|
646
|
+
:timestamp => Time.now,
|
647
|
+
:checks_performed => checks.checks_run.sort,
|
648
|
+
:number_of_controllers =>tracker.controllers.length,
|
649
|
+
# ignore the "fake" model
|
650
|
+
:number_of_models => tracker.models.length - 1,
|
651
|
+
:number_of_templates => number_of_templates(@tracker),
|
652
|
+
:ruby_version => RUBY_VERSION,
|
653
|
+
:brakeman_version => Brakeman::Version
|
654
|
+
}
|
655
|
+
|
656
|
+
JSON.pretty_generate({
|
657
|
+
:scan_info => scan_info,
|
658
|
+
:warnings => warnings,
|
659
|
+
:errors => errors
|
660
|
+
})
|
661
|
+
end
|
662
|
+
|
663
|
+
def all_warnings
|
664
|
+
@all_warnings ||= @checks.all_warnings
|
665
|
+
end
|
666
|
+
|
667
|
+
def number_of_templates tracker
|
668
|
+
Set.new(tracker.templates.map {|k,v| v[:name].to_s[/[^.]+/]}).length
|
669
|
+
end
|
670
|
+
|
671
|
+
private
|
672
|
+
|
673
|
+
def load_and_render_erb file, bind
|
674
|
+
content = File.read(File.expand_path("templates/#{file}.html.erb", File.dirname(__FILE__)))
|
675
|
+
template = ERB.new(content)
|
676
|
+
template.result(bind)
|
618
677
|
end
|
619
678
|
end
|