brakeman 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +20 -0
- data/README.md +6 -1
- data/bin/brakeman +13 -3
- data/lib/brakeman.rb +64 -7
- data/lib/brakeman/call_index.rb +6 -4
- data/lib/brakeman/checks/check_basic_auth.rb +47 -2
- data/lib/brakeman/checks/check_cross_site_scripting.rb +50 -12
- data/lib/brakeman/checks/check_execute.rb +4 -1
- data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
- data/lib/brakeman/checks/check_sql.rb +101 -154
- data/lib/brakeman/options.rb +16 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +2 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +2 -0
- data/lib/brakeman/parsers/rails3_erubis.rb +2 -0
- data/lib/brakeman/processors/alias_processor.rb +19 -4
- data/lib/brakeman/processors/controller_alias_processor.rb +2 -3
- data/lib/brakeman/processors/gem_processor.rb +5 -4
- data/lib/brakeman/processors/lib/find_all_calls.rb +43 -16
- data/lib/brakeman/report.rb +39 -640
- data/lib/brakeman/report/ignore/config.rb +130 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/renderer.rb +2 -0
- data/lib/brakeman/report/report_base.rb +279 -0
- data/lib/brakeman/report/report_csv.rb +56 -0
- data/lib/brakeman/report/report_hash.rb +22 -0
- data/lib/brakeman/report/report_html.rb +203 -0
- data/lib/brakeman/report/report_json.rb +46 -0
- data/lib/brakeman/report/report_table.rb +109 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/overview.html.erb +6 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +1 -1
- data/lib/brakeman/scanner.rb +14 -12
- data/lib/brakeman/tracker.rb +5 -1
- data/lib/brakeman/util.rb +2 -0
- data/lib/brakeman/version.rb +1 -1
- data/lib/ruby_parser/bm_sexp.rb +12 -1
- metadata +179 -90
- checksums.yaml +0 -7
@@ -0,0 +1,56 @@
|
|
1
|
+
Brakeman.load_dependency 'csv'
|
2
|
+
require "brakeman/report/initializers/faster_csv"
|
3
|
+
require "brakeman/report/report_table"
|
4
|
+
|
5
|
+
class Brakeman::Report::CSV < Brakeman::Report::Table
|
6
|
+
def generate_report
|
7
|
+
output = csv_header
|
8
|
+
output << "\nSUMMARY\n"
|
9
|
+
|
10
|
+
output << table_to_csv(generate_overview) << "\n"
|
11
|
+
|
12
|
+
output << table_to_csv(generate_warning_overview) << "\n"
|
13
|
+
|
14
|
+
#Return output early if only summarizing
|
15
|
+
if tracker.options[:summary_only]
|
16
|
+
return output
|
17
|
+
end
|
18
|
+
|
19
|
+
if tracker.options[:report_routes] or tracker.options[:debug]
|
20
|
+
output << "CONTROLLERS\n"
|
21
|
+
output << table_to_csv(generate_controllers) << "\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
if tracker.options[:debug]
|
25
|
+
output << "TEMPLATES\n\n"
|
26
|
+
output << table_to_csv(generate_templates) << "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
res = generate_errors
|
30
|
+
output << "ERRORS\n" << table_to_csv(res) << "\n" if res
|
31
|
+
|
32
|
+
res = generate_warnings
|
33
|
+
output << "SECURITY WARNINGS\n" << table_to_csv(res) << "\n" if res
|
34
|
+
|
35
|
+
output << "Controller Warnings\n"
|
36
|
+
res = generate_controller_warnings
|
37
|
+
output << table_to_csv(res) << "\n" if res
|
38
|
+
|
39
|
+
output << "Model Warnings\n"
|
40
|
+
res = generate_model_warnings
|
41
|
+
output << table_to_csv(res) << "\n" if res
|
42
|
+
|
43
|
+
res = generate_template_warnings
|
44
|
+
output << "Template Warnings\n"
|
45
|
+
output << table_to_csv(res) << "\n" if res
|
46
|
+
|
47
|
+
output
|
48
|
+
end
|
49
|
+
|
50
|
+
#Generate header for CSV output
|
51
|
+
def csv_header
|
52
|
+
header = CSV.generate_line(["Application Path", "Report Generation Time", "Checks Performed", "Rails Version"])
|
53
|
+
header << CSV.generate_line([File.expand_path(tracker.options[:app_path]), Time.now.to_s, checks.checks_run.sort.join(", "), rails_version])
|
54
|
+
"BRAKEMAN REPORT\n\n" + header
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Generates a hash table for use in Brakeman tests
|
2
|
+
class Brakeman::Report::Hash < Brakeman::Report::Base
|
3
|
+
def generate_report
|
4
|
+
report = { :errors => tracker.errors,
|
5
|
+
:controllers => tracker.controllers,
|
6
|
+
:models => tracker.models,
|
7
|
+
:templates => tracker.templates
|
8
|
+
}
|
9
|
+
|
10
|
+
[:generic_warnings, :controller_warnings, :model_warnings, :template_warnings].each do |meth|
|
11
|
+
report[meth] = self.send(meth)
|
12
|
+
report[meth].each do |w|
|
13
|
+
w.message = w.format_message
|
14
|
+
w.context = context_for(@app_tree, w).join("\n")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
report[:config] = tracker.config
|
19
|
+
|
20
|
+
report
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
class Brakeman::Report::HTML < Brakeman::Report::Base
|
4
|
+
HTML_CONFIDENCE = [ "<span class='high-confidence'>High</span>",
|
5
|
+
"<span class='med-confidence'>Medium</span>",
|
6
|
+
"<span class='weak-confidence'>Weak</span>" ]
|
7
|
+
|
8
|
+
def initialize *args
|
9
|
+
super
|
10
|
+
|
11
|
+
@element_id = 0 #Used for HTML ids
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_report
|
15
|
+
out = html_header <<
|
16
|
+
generate_overview <<
|
17
|
+
generate_warning_overview.to_s
|
18
|
+
|
19
|
+
# Return early if only summarizing
|
20
|
+
return out if tracker.options[:summary_only]
|
21
|
+
|
22
|
+
out << generate_controllers.to_s if tracker.options[:report_routes] or tracker.options[:debug]
|
23
|
+
out << generate_templates.to_s if tracker.options[:debug]
|
24
|
+
out << generate_errors.to_s
|
25
|
+
out << generate_warnings.to_s
|
26
|
+
out << generate_controller_warnings.to_s
|
27
|
+
out << generate_model_warnings.to_s
|
28
|
+
out << generate_template_warnings.to_s
|
29
|
+
out << generate_ignored_warnings.to_s
|
30
|
+
out << "</body></html>"
|
31
|
+
end
|
32
|
+
|
33
|
+
def generate_overview
|
34
|
+
locals = {
|
35
|
+
:tracker => tracker,
|
36
|
+
:warnings => all_warnings.length,
|
37
|
+
:warnings_summary => warnings_summary,
|
38
|
+
:number_of_templates => number_of_templates(@tracker),
|
39
|
+
:ignored_warnings => ignored_warnings.length
|
40
|
+
}
|
41
|
+
|
42
|
+
Brakeman::Report::Renderer.new('overview', :locals => locals).render
|
43
|
+
end
|
44
|
+
|
45
|
+
#Generate listings of templates and their output
|
46
|
+
def generate_templates
|
47
|
+
out_processor = Brakeman::OutputProcessor.new
|
48
|
+
template_rows = {}
|
49
|
+
tracker.templates.each do |name, template|
|
50
|
+
unless template[:outputs].empty?
|
51
|
+
template[:outputs].each do |out|
|
52
|
+
out = CGI.escapeHTML(out_processor.format(out))
|
53
|
+
template_rows[name] ||= []
|
54
|
+
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
template_rows = template_rows.sort_by{|name, value| name.to_s}
|
60
|
+
|
61
|
+
Brakeman::Report::Renderer.new('template_overview', :locals => {:template_rows => template_rows}).render
|
62
|
+
end
|
63
|
+
|
64
|
+
def render_array template, headings, value_array, locals
|
65
|
+
return if value_array.empty?
|
66
|
+
|
67
|
+
Brakeman::Report::Renderer.new(template, :locals => locals).render
|
68
|
+
end
|
69
|
+
|
70
|
+
def convert_warning warning, original
|
71
|
+
warning["Confidence"] = HTML_CONFIDENCE[warning["Confidence"]]
|
72
|
+
warning["Message"] = with_context original, warning["Message"]
|
73
|
+
warning["Warning Type"] = with_link original, warning["Warning Type"]
|
74
|
+
warning
|
75
|
+
end
|
76
|
+
|
77
|
+
def with_link warning, message
|
78
|
+
"<a rel=\"no-referrer\" href=\"#{warning.link}\">#{message}</a>"
|
79
|
+
end
|
80
|
+
|
81
|
+
def convert_template_warning warning, original
|
82
|
+
warning["Confidence"] = HTML_CONFIDENCE[warning["Confidence"]]
|
83
|
+
warning["Message"] = with_context original, warning["Message"]
|
84
|
+
warning["Warning Type"] = with_link original, warning["Warning Type"]
|
85
|
+
warning["Called From"] = original.called_from
|
86
|
+
warning["Template Name"] = original.template[:name]
|
87
|
+
warning
|
88
|
+
end
|
89
|
+
|
90
|
+
def convert_ignored_warning warning, original
|
91
|
+
warning = convert_warning(warning, original)
|
92
|
+
warning['File'] = original.relative_path
|
93
|
+
warning['Note'] = CGI.escapeHTML(@ignore_filter.note_for(original) || "")
|
94
|
+
warning
|
95
|
+
end
|
96
|
+
|
97
|
+
#Return header for HTML output. Uses CSS from tracker.options[:html_style]
|
98
|
+
def html_header
|
99
|
+
if File.exist? tracker.options[:html_style]
|
100
|
+
css = File.read tracker.options[:html_style]
|
101
|
+
else
|
102
|
+
raise "Cannot find CSS stylesheet for HTML: #{tracker.options[:html_style]}"
|
103
|
+
end
|
104
|
+
|
105
|
+
locals = {
|
106
|
+
:css => css,
|
107
|
+
:tracker => tracker,
|
108
|
+
:checks => checks,
|
109
|
+
:rails_version => rails_version,
|
110
|
+
:brakeman_version => Brakeman::Version
|
111
|
+
}
|
112
|
+
|
113
|
+
Brakeman::Report::Renderer.new('header', :locals => locals).render
|
114
|
+
end
|
115
|
+
|
116
|
+
#Generate HTML for warnings, including context show/hidden via Javascript
|
117
|
+
def with_context warning, message
|
118
|
+
context = context_for(@app_tree, warning)
|
119
|
+
full_message = nil
|
120
|
+
|
121
|
+
if tracker.options[:message_limit] and tracker.options[:message_limit] > 0 and message.length > tracker.options[:message_limit]
|
122
|
+
full_message = html_message(warning, message)
|
123
|
+
message = message[0..tracker.options[:message_limit]] << "..."
|
124
|
+
end
|
125
|
+
|
126
|
+
message = html_message(warning, message)
|
127
|
+
return message if context.empty? and not full_message
|
128
|
+
|
129
|
+
@element_id += 1
|
130
|
+
code_id = "context#@element_id"
|
131
|
+
message_id = "message#@element_id"
|
132
|
+
full_message_id = "full_message#@element_id"
|
133
|
+
alt = false
|
134
|
+
output = "<div class='warning_message' onClick=\"toggle('#{code_id}');toggle('#{message_id}');toggle('#{full_message_id}')\" >" <<
|
135
|
+
if full_message
|
136
|
+
"<span id='#{message_id}' style='display:block' >#{message}</span>" <<
|
137
|
+
"<span id='#{full_message_id}' style='display:none'>#{full_message}</span>"
|
138
|
+
else
|
139
|
+
message
|
140
|
+
end <<
|
141
|
+
"<table id='#{code_id}' class='context' style='display:none'>" <<
|
142
|
+
"<caption>#{warning_file(warning) || ''}</caption>"
|
143
|
+
|
144
|
+
unless context.empty?
|
145
|
+
if warning.line - 1 == 1 or warning.line + 1 == 1
|
146
|
+
error = " near_error"
|
147
|
+
elsif 1 == warning.line
|
148
|
+
error = " error"
|
149
|
+
else
|
150
|
+
error = ""
|
151
|
+
end
|
152
|
+
|
153
|
+
output << <<-HTML
|
154
|
+
<tr class='context first#{error}'>
|
155
|
+
<td class='context_line'>
|
156
|
+
<pre class='context'>#{context.first[0]}</pre>
|
157
|
+
</td>
|
158
|
+
<td class='context'>
|
159
|
+
<pre class='context'>#{CGI.escapeHTML context.first[1].chomp}</pre>
|
160
|
+
</td>
|
161
|
+
</tr>
|
162
|
+
HTML
|
163
|
+
|
164
|
+
if context.length > 1
|
165
|
+
output << context[1..-1].map do |code|
|
166
|
+
alt = !alt
|
167
|
+
if code[0] == warning.line - 1 or code[0] == warning.line + 1
|
168
|
+
error = " near_error"
|
169
|
+
elsif code[0] == warning.line
|
170
|
+
error = " error"
|
171
|
+
else
|
172
|
+
error = ""
|
173
|
+
end
|
174
|
+
|
175
|
+
<<-HTML
|
176
|
+
<tr class='context#{alt ? ' alt' : ''}#{error}'>
|
177
|
+
<td class='context_line'>
|
178
|
+
<pre class='context'>#{code[0]}</pre>
|
179
|
+
</td>
|
180
|
+
<td class='context'>
|
181
|
+
<pre class='context'>#{CGI.escapeHTML code[1].chomp}</pre>
|
182
|
+
</td>
|
183
|
+
</tr>
|
184
|
+
HTML
|
185
|
+
end.join
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
output << "</table></div>"
|
190
|
+
end
|
191
|
+
|
192
|
+
#Escape warning message and highlight user input in HTML output
|
193
|
+
def html_message warning, message
|
194
|
+
message = CGI.escapeHTML(message)
|
195
|
+
|
196
|
+
if @highlight_user_input and warning.user_input
|
197
|
+
user_input = CGI.escapeHTML(warning.format_user_input)
|
198
|
+
message.gsub!(user_input, "<span class=\"user_input\">#{user_input}</span>")
|
199
|
+
end
|
200
|
+
|
201
|
+
message
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
Brakeman.load_dependency 'multi_json'
|
2
|
+
require 'brakeman/report/initializers/multi_json'
|
3
|
+
|
4
|
+
class Brakeman::Report::JSON < Brakeman::Report::Base
|
5
|
+
def generate_report
|
6
|
+
errors = tracker.errors.map{|e| { :error => e[:error], :location => e[:backtrace][0] }}
|
7
|
+
app_path = tracker.options[:app_path]
|
8
|
+
|
9
|
+
warnings = convert_to_hashes all_warnings
|
10
|
+
|
11
|
+
ignored = convert_to_hashes ignored_warnings
|
12
|
+
|
13
|
+
scan_info = {
|
14
|
+
:app_path => File.expand_path(tracker.options[:app_path]),
|
15
|
+
:rails_version => rails_version,
|
16
|
+
:security_warnings => all_warnings.length,
|
17
|
+
:start_time => tracker.start_time.to_s,
|
18
|
+
:end_time => tracker.end_time.to_s,
|
19
|
+
:duration => tracker.duration,
|
20
|
+
:checks_performed => checks.checks_run.sort,
|
21
|
+
:number_of_controllers => tracker.controllers.length,
|
22
|
+
# ignore the "fake" model
|
23
|
+
:number_of_models => tracker.models.length - 1,
|
24
|
+
:number_of_templates => number_of_templates(@tracker),
|
25
|
+
:ruby_version => RUBY_VERSION,
|
26
|
+
:brakeman_version => Brakeman::Version
|
27
|
+
}
|
28
|
+
|
29
|
+
report_info = {
|
30
|
+
:scan_info => scan_info,
|
31
|
+
:warnings => warnings,
|
32
|
+
:ignored_warnings => ignored,
|
33
|
+
:errors => errors
|
34
|
+
}
|
35
|
+
|
36
|
+
MultiJson.dump(report_info, :pretty => true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def convert_to_hashes warnings
|
40
|
+
warnings.map do |w|
|
41
|
+
hash = w.to_hash
|
42
|
+
hash[:file] = warning_file w
|
43
|
+
hash
|
44
|
+
end.sort_by { |w| w[:file] }
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
Brakeman.load_dependency 'terminal-table'
|
2
|
+
|
3
|
+
class Brakeman::Report::Table < Brakeman::Report::Base
|
4
|
+
def generate_report
|
5
|
+
out = text_header <<
|
6
|
+
"\n\n+SUMMARY+\n\n" <<
|
7
|
+
truncate_table(generate_overview.to_s) << "\n\n" <<
|
8
|
+
truncate_table(generate_warning_overview.to_s) << "\n"
|
9
|
+
|
10
|
+
#Return output early if only summarizing
|
11
|
+
return out if tracker.options[:summary_only]
|
12
|
+
|
13
|
+
if tracker.options[:report_routes] or tracker.options[:debug]
|
14
|
+
out << "\n+CONTROLLERS+\n" <<
|
15
|
+
truncate_table(generate_controllers.to_s) << "\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
if tracker.options[:debug]
|
19
|
+
out << "\n+TEMPLATES+\n\n" <<
|
20
|
+
truncate_table(generate_templates.to_s) << "\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
res = generate_errors
|
24
|
+
out << "+Errors+\n" << truncate_table(res.to_s) if res
|
25
|
+
|
26
|
+
res = generate_warnings
|
27
|
+
out << "\n\n+SECURITY WARNINGS+\n\n" << truncate_table(res.to_s) if res
|
28
|
+
|
29
|
+
res = generate_controller_warnings
|
30
|
+
out << "\n\n\nController Warnings:\n\n" << truncate_table(res.to_s) if res
|
31
|
+
|
32
|
+
res = generate_model_warnings
|
33
|
+
out << "\n\n\nModel Warnings:\n\n" << truncate_table(res.to_s) if res
|
34
|
+
|
35
|
+
res = generate_template_warnings
|
36
|
+
out << "\n\nView Warnings:\n\n" << truncate_table(res.to_s) if res
|
37
|
+
|
38
|
+
out << "\n"
|
39
|
+
out
|
40
|
+
end
|
41
|
+
|
42
|
+
def generate_overview
|
43
|
+
num_warnings = all_warnings.length
|
44
|
+
|
45
|
+
Terminal::Table.new(:headings => ['Scanned/Reported', 'Total']) do |t|
|
46
|
+
t.add_row ['Controllers', tracker.controllers.length]
|
47
|
+
t.add_row ['Models', tracker.models.length - 1]
|
48
|
+
t.add_row ['Templates', number_of_templates(@tracker)]
|
49
|
+
t.add_row ['Errors', tracker.errors.length]
|
50
|
+
t.add_row ['Security Warnings', "#{num_warnings} (#{warnings_summary[:high_confidence]})"]
|
51
|
+
t.add_row ['Ignored Warnings', ignored_warnings.length] unless ignored_warnings.empty?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#Generate listings of templates and their output
|
56
|
+
def generate_templates
|
57
|
+
out_processor = Brakeman::OutputProcessor.new
|
58
|
+
template_rows = {}
|
59
|
+
tracker.templates.each do |name, template|
|
60
|
+
unless template[:outputs].empty?
|
61
|
+
template[:outputs].each do |out|
|
62
|
+
out = out_processor.format out
|
63
|
+
template_rows[name] ||= []
|
64
|
+
template_rows[name] << out.gsub("\n", ";").gsub(/\s+/, " ")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
template_rows = template_rows.sort_by{|name, value| name.to_s}
|
70
|
+
|
71
|
+
output = ''
|
72
|
+
template_rows.each do |template|
|
73
|
+
output << template.first.to_s << "\n\n"
|
74
|
+
table = Terminal::Table.new(:headings => ['Output']) do |t|
|
75
|
+
# template[1] is an array of calls
|
76
|
+
template[1].each do |v|
|
77
|
+
t.add_row [v]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
output << table.to_s << "\n\n"
|
82
|
+
end
|
83
|
+
|
84
|
+
output
|
85
|
+
end
|
86
|
+
|
87
|
+
def render_array template, headings, value_array, locals
|
88
|
+
return if value_array.empty?
|
89
|
+
|
90
|
+
Terminal::Table.new(:headings => headings) do |t|
|
91
|
+
value_array.each { |value_row| t.add_row value_row }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
#Generate header for text output
|
96
|
+
def text_header
|
97
|
+
<<-HEADER
|
98
|
+
|
99
|
+
+BRAKEMAN REPORT+
|
100
|
+
|
101
|
+
Application path: #{File.expand_path tracker.options[:app_path]}
|
102
|
+
Rails version: #{rails_version}
|
103
|
+
Brakeman version: #{Brakeman::Version}
|
104
|
+
Started at #{tracker.start_time}
|
105
|
+
Duration: #{tracker.duration} seconds
|
106
|
+
Checks run: #{checks.checks_run.sort.join(", ")}
|
107
|
+
HEADER
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#Generated tab-separated output suitable for the Jenkins Brakeman Plugin:
|
2
|
+
#https://github.com/presidentbeef/brakeman-jenkins-plugin
|
3
|
+
class Brakeman::Report::Tabs < Brakeman::Report::Base
|
4
|
+
def generate_report
|
5
|
+
[[:warnings, "General"], [:controller_warnings, "Controller"],
|
6
|
+
[:model_warnings, "Model"], [:template_warnings, "Template"]].map do |meth, category|
|
7
|
+
|
8
|
+
checks.send(meth).map do |w|
|
9
|
+
line = w.line || 0
|
10
|
+
w.warning_type.gsub!(/[^\w\s]/, ' ')
|
11
|
+
"#{warning_file(w, :absolute)}\t#{line}\t#{w.warning_type}\t#{category}\t#{w.format_message}\t#{TEXT_CONFIDENCE[w.confidence]}"
|
12
|
+
end.join "\n"
|
13
|
+
|
14
|
+
end.join "\n"
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|