brakeman 1.5.3 → 1.6.0.pre1
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 +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
|