brakeman 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|