brakeman 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/CHANGES +20 -0
  2. data/README.md +6 -1
  3. data/bin/brakeman +13 -3
  4. data/lib/brakeman.rb +64 -7
  5. data/lib/brakeman/call_index.rb +6 -4
  6. data/lib/brakeman/checks/check_basic_auth.rb +47 -2
  7. data/lib/brakeman/checks/check_cross_site_scripting.rb +50 -12
  8. data/lib/brakeman/checks/check_execute.rb +4 -1
  9. data/lib/brakeman/checks/check_model_attr_accessible.rb +48 -0
  10. data/lib/brakeman/checks/check_sql.rb +101 -154
  11. data/lib/brakeman/options.rb +16 -0
  12. data/lib/brakeman/parsers/rails2_erubis.rb +2 -0
  13. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +2 -0
  14. data/lib/brakeman/parsers/rails3_erubis.rb +2 -0
  15. data/lib/brakeman/processors/alias_processor.rb +19 -4
  16. data/lib/brakeman/processors/controller_alias_processor.rb +2 -3
  17. data/lib/brakeman/processors/gem_processor.rb +5 -4
  18. data/lib/brakeman/processors/lib/find_all_calls.rb +43 -16
  19. data/lib/brakeman/report.rb +39 -640
  20. data/lib/brakeman/report/ignore/config.rb +130 -0
  21. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  22. data/lib/brakeman/report/renderer.rb +2 -0
  23. data/lib/brakeman/report/report_base.rb +279 -0
  24. data/lib/brakeman/report/report_csv.rb +56 -0
  25. data/lib/brakeman/report/report_hash.rb +22 -0
  26. data/lib/brakeman/report/report_html.rb +203 -0
  27. data/lib/brakeman/report/report_json.rb +46 -0
  28. data/lib/brakeman/report/report_table.rb +109 -0
  29. data/lib/brakeman/report/report_tabs.rb +17 -0
  30. data/lib/brakeman/report/templates/ignored_warnings.html.erb +21 -0
  31. data/lib/brakeman/report/templates/overview.html.erb +6 -0
  32. data/lib/brakeman/report/templates/security_warnings.html.erb +1 -1
  33. data/lib/brakeman/scanner.rb +14 -12
  34. data/lib/brakeman/tracker.rb +5 -1
  35. data/lib/brakeman/util.rb +2 -0
  36. data/lib/brakeman/version.rb +1 -1
  37. data/lib/ruby_parser/bm_sexp.rb +12 -1
  38. metadata +179 -90
  39. 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