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.
Files changed (54) hide show
  1. data/bin/brakeman +20 -6
  2. data/lib/brakeman.rb +21 -1
  3. data/lib/brakeman/checks.rb +2 -7
  4. data/lib/brakeman/checks/base_check.rb +31 -26
  5. data/lib/brakeman/checks/check_basic_auth.rb +1 -7
  6. data/lib/brakeman/checks/check_cross_site_scripting.rb +38 -33
  7. data/lib/brakeman/checks/check_evaluation.rb +2 -1
  8. data/lib/brakeman/checks/check_execute.rb +5 -1
  9. data/lib/brakeman/checks/check_file_access.rb +6 -4
  10. data/lib/brakeman/checks/check_link_to.rb +8 -5
  11. data/lib/brakeman/checks/check_link_to_href.rb +6 -5
  12. data/lib/brakeman/checks/check_mail_to.rb +2 -4
  13. data/lib/brakeman/checks/check_mass_assignment.rb +12 -6
  14. data/lib/brakeman/checks/check_redirect.rb +17 -14
  15. data/lib/brakeman/checks/check_render.rb +4 -4
  16. data/lib/brakeman/checks/check_send.rb +4 -2
  17. data/lib/brakeman/checks/check_session_settings.rb +16 -21
  18. data/lib/brakeman/checks/check_skip_before_filter.rb +2 -4
  19. data/lib/brakeman/checks/check_sql.rb +8 -7
  20. data/lib/brakeman/checks/check_validation_regex.rb +2 -4
  21. data/lib/brakeman/checks/check_without_protection.rb +8 -9
  22. data/lib/brakeman/differ.rb +61 -0
  23. data/lib/brakeman/format/style.css +4 -0
  24. data/lib/brakeman/options.rb +8 -0
  25. data/lib/brakeman/processors/alias_processor.rb +5 -7
  26. data/lib/brakeman/processors/controller_alias_processor.rb +10 -3
  27. data/lib/brakeman/processors/erb_template_processor.rb +2 -0
  28. data/lib/brakeman/processors/erubis_template_processor.rb +2 -0
  29. data/lib/brakeman/processors/gem_processor.rb +8 -0
  30. data/lib/brakeman/processors/haml_template_processor.rb +2 -0
  31. data/lib/brakeman/processors/lib/rails2_route_processor.rb +2 -6
  32. data/lib/brakeman/processors/lib/rails3_route_processor.rb +2 -4
  33. data/lib/brakeman/processors/lib/render_helper.rb +3 -1
  34. data/lib/brakeman/processors/library_processor.rb +4 -6
  35. data/lib/brakeman/processors/template_alias_processor.rb +1 -1
  36. data/lib/brakeman/report.rb +257 -198
  37. data/lib/brakeman/rescanner.rb +112 -10
  38. data/lib/brakeman/scanner.rb +3 -4
  39. data/lib/brakeman/templates/controller_overview.html.erb +18 -0
  40. data/lib/brakeman/templates/controller_warnings.html.erb +17 -0
  41. data/lib/brakeman/templates/error_overview.html.erb +14 -0
  42. data/lib/brakeman/templates/header.html.erb +38 -0
  43. data/lib/brakeman/templates/model_warnings.html.erb +17 -0
  44. data/lib/brakeman/templates/overview.html.erb +28 -0
  45. data/lib/brakeman/templates/security_warnings.html.erb +28 -0
  46. data/lib/brakeman/templates/template_overview.html.erb +17 -0
  47. data/lib/brakeman/templates/view_warnings.html.erb +17 -0
  48. data/lib/brakeman/templates/warning_overview.html.erb +13 -0
  49. data/lib/brakeman/tracker.rb +1 -1
  50. data/lib/brakeman/util.rb +24 -4
  51. data/lib/brakeman/version.rb +1 -1
  52. data/lib/brakeman/warning.rb +11 -3
  53. data/lib/ruby_parser/bm_sexp.rb +5 -11
  54. metadata +84 -23
@@ -1,4 +1,6 @@
1
1
  require 'brakeman/scanner'
2
+ require 'terminal-table'
3
+ require 'brakeman/util'
2
4
 
3
5
  #Class for rescanning changed files after an initial scan
4
6
  class Brakeman::Rescanner < Brakeman::Scanner
@@ -64,6 +66,10 @@ class Brakeman::Rescanner < Brakeman::Scanner
64
66
  def rescan_file path, type = nil
65
67
  type ||= file_type path
66
68
 
69
+ unless File.exist? path
70
+ return rescan_deleted_file path, type
71
+ end
72
+
67
73
  case type
68
74
  when :controller
69
75
  rescan_controller path
@@ -89,6 +95,10 @@ class Brakeman::Rescanner < Brakeman::Scanner
89
95
  process_controllers
90
96
  @reindex << :controllers << :templates
91
97
  when :gemfile
98
+ if tracker.config[:gems][:rails_xss] and tracker.config[:escape_html]
99
+ tracker.config[:escape_html] = false
100
+ end
101
+
92
102
  process_gems
93
103
  else
94
104
  return false #Nothing to do, file hopefully does not need to be rescanned
@@ -111,7 +121,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
111
121
  end
112
122
  end
113
123
 
114
- @processor.process_controller_alias controller[:src]
124
+ @processor.process_controller_alias controller[:name], controller[:src]
115
125
  end
116
126
  end
117
127
  end
@@ -154,7 +164,7 @@ class Brakeman::Rescanner < Brakeman::Scanner
154
164
  controller = tracker.controllers[r[1]]
155
165
 
156
166
  unless @paths.include? controller[:file]
157
- @processor.process_controller_alias controller[:src], r[2]
167
+ @processor.process_controller_alias controller[:name], controller[:src], r[2]
158
168
  end
159
169
  elsif r[0] == :template
160
170
  template = tracker.templates[r[1]]
@@ -179,6 +189,98 @@ class Brakeman::Rescanner < Brakeman::Scanner
179
189
  @reindex << :models
180
190
  end
181
191
 
192
+ #Handle rescanning when a file is deleted
193
+ def rescan_deleted_file path, type
194
+ case type
195
+ when :controller
196
+ rescan_deleted_controller path
197
+ when :template
198
+ rescan_deleted_template path
199
+ when :model
200
+ rescan_model path
201
+ when :lib
202
+ rescan_deleted_lib path
203
+ when :initializer
204
+ rescan_deleted_initializer path
205
+ else
206
+ if remove_deleted_file path
207
+ return true
208
+ else
209
+ Brakeman.notify "Ignoring deleted file: #{path}"
210
+ end
211
+ end
212
+
213
+ true
214
+ end
215
+
216
+ def rescan_deleted_controller path
217
+ #Remove from controller
218
+ tracker.controllers.delete_if do |name, controller|
219
+ if controller[:file] == path
220
+ template_matcher = /(.+)\.#{name}#/
221
+
222
+ #Remove templates rendered from this controller
223
+ tracker.templates.keys.each do |template_name|
224
+ if template_name.to_s.match template_matcher
225
+ tracker.templates.delete template_name
226
+ end
227
+ end
228
+
229
+ true
230
+ end
231
+ end
232
+ end
233
+
234
+ def rescan_deleted_template path
235
+ return unless path.match KNOWN_TEMPLATE_EXTENSIONS
236
+
237
+ template_name = template_path_to_name(path)
238
+
239
+ #Remove template
240
+ tracker.reset_template template_name
241
+
242
+ rendered_from_controller = /^#{template_name}\.(.+Controller)#(.+)/
243
+ rendered_from_view = /^#{template_name}\.Template:(.+)/
244
+
245
+ #Remove any rendered versions, or partials rendered from it
246
+ tracker.templates.delete_if do |name, template|
247
+ if template[:file] == path
248
+ true
249
+ elsif template[:file].nil?
250
+ name = name.to_s
251
+
252
+ name.match(rendered_from_controller) or name.match(rendered_from_view)
253
+ end
254
+ end
255
+ end
256
+
257
+ def rescan_deleted_lib path
258
+ tracker.libs.delete_if do |name, lib|
259
+ lib[:path] == path
260
+ end
261
+ end
262
+
263
+ def rescan_deleted_initializer path
264
+ tracker.initializers.delete Pathname.new(path).basename.to_s
265
+ end
266
+
267
+ #Check controllers, templates, models and libs for data from file
268
+ #and delete it.
269
+ def remove_deleted_file path
270
+ deleted = false
271
+
272
+ [:controllers, :templates, :models, :libs].each do |collection|
273
+ tracker.send(collection).delete_if do |name, data|
274
+ if data[:file] == path
275
+ deleted = true
276
+ true
277
+ end
278
+ end
279
+ end
280
+
281
+ deleted
282
+ end
283
+
182
284
  #Guess at what kind of file the path contains
183
285
  def file_type path
184
286
  case path
@@ -206,6 +308,7 @@ end
206
308
 
207
309
  #Class to make reporting of rescan results simpler to deal with
208
310
  class Brakeman::RescanReport
311
+ include Brakeman::Util
209
312
  attr_reader :old_results, :new_results
210
313
 
211
314
  def initialize old_results, tracker
@@ -271,17 +374,16 @@ New warnings: #{new_warnings.length}
271
374
  if warnings.length > 0
272
375
  out << "#{warning_type.to_s.titleize} warnings: #{warnings.length}\n"
273
376
 
274
- table = Ruport::Data::Table(["Confidence", "Class", "Method", "Warning Type", "Message"])
275
-
276
- warnings.sort_by { |w| w.confidence}.each do |warning|
277
- w = warning.to_row
377
+ table = Terminal::Table.new(:headings => ["Confidence", "Class", "Method", "Warning Type", "Message"]) do |t|
378
+ warnings.sort_by { |w| w.confidence}.each do |warning|
379
+ w = warning.to_row
278
380
 
279
- w["Confidence"] = Brakeman::Report::TEXT_CONFIDENCE[w["Confidence"]]
381
+ w["Confidence"] = Brakeman::Report::TEXT_CONFIDENCE[w["Confidence"]]
280
382
 
281
- table << w
383
+ t << [w["Confidence"], w["Class"], w["Method"], w["Warning Type"], w["Message"]]
384
+ end
282
385
  end
283
-
284
- out << table.to_s
386
+ out << truncate_table(table.to_s)
285
387
  end
286
388
  end
287
389
 
@@ -92,8 +92,7 @@ class Brakeman::Scanner
92
92
  end
93
93
 
94
94
  if File.exists? "#@path/vendor/plugins/rails_xss" or
95
- options[:rails3] or options[:escape_html] or
96
- (File.exists? "#@path/Gemfile" and File.read("#@path/Gemfile").include? "rails_xss")
95
+ options[:rails3] or options[:escape_html]
97
96
 
98
97
  tracker.config[:escape_html] = true
99
98
  Brakeman.notify "[Notice] Escaping HTML by default"
@@ -228,7 +227,7 @@ class Brakeman::Scanner
228
227
 
229
228
  Brakeman.notify "Processing data flow in controllers..."
230
229
 
231
- tracker.controllers.each do |name, controller|
230
+ tracker.controllers.sort_by{|name| name.to_s}.each do |name, controller|
232
231
  Brakeman.debug "Processing #{name}"
233
232
  if @report_progress
234
233
  $stderr.print " #{current}/#{total} controllers processed\r"
@@ -281,7 +280,7 @@ class Brakeman::Scanner
281
280
 
282
281
  Brakeman.notify "Processing data flow in templates..."
283
282
 
284
- tracker.templates.keys.dup.each do |name|
283
+ tracker.templates.keys.dup.sort_by{|name| name.to_s}.each do |name|
285
284
  Brakeman.debug "Processing #{name}"
286
285
  if @report_progress
287
286
  count += 1
@@ -0,0 +1,18 @@
1
+ <h2>Controllers</h2>
2
+
3
+ <table>
4
+ <tr>
5
+ <th>Name</th>
6
+ <th>Parent</th>
7
+ <th>Includes</th>
8
+ <th>Routes</th>
9
+ </tr>
10
+ <% controller_rows.each do |row| %>
11
+ <tr>
12
+ <td><%= row['Name'] %></td>
13
+ <td><%= row['Parent'] %></td>
14
+ <td><%= row['Includes'] %></td>
15
+ <td><%= row['Routes'] %></td>
16
+ </tr>
17
+ <% end %>
18
+ </table>
@@ -0,0 +1,17 @@
1
+ <p>Controller Warnings</p>
2
+ <table>
3
+ <tr>
4
+ <th>Confidence</th>
5
+ <th>Controller</th>
6
+ <th>Warning Type</th>
7
+ <th>Message</th>
8
+ </tr>
9
+ <% warnings.each do |warning| %>
10
+ <tr>
11
+ <td><%= warning['Confidence']%></td>
12
+ <td><%= warning['Controller']%></td>
13
+ <td><%= warning['Warning Type']%></td>
14
+ <td><%= warning['Message']%></td>
15
+ </tr>
16
+ <% end %>
17
+ </table>
@@ -0,0 +1,14 @@
1
+ <div onClick="toggle('errors_table');"> <h2>Exceptions raised during the analysis (click to see them)</h2 ></div> <div id='errors_table' style='display:none'>
2
+ <table>
3
+ <tr>
4
+ <th>Error</th>
5
+ <th>Location</th>
6
+ </tr>
7
+ <% tracker.errors.each do |warning| %>
8
+ <tr>
9
+ <td><%= CGI.escapeHTML warning[:error] %></td>
10
+ <td><%= warning[:backtrace][0] %></td>
11
+ </tr>
12
+ <% end %>
13
+ </table>
14
+ </div>
@@ -0,0 +1,38 @@
1
+ <!DOCTYPE HTML SYSTEM>
2
+ <html>
3
+ <head>
4
+ <title>Brakeman Report</title>
5
+ <script>
6
+ function toggle(context) {
7
+ var elem = document.getElementById(context);
8
+
9
+ if (elem.style.display != "block")
10
+ elem.style.display = "block";
11
+ else
12
+ elem.style.display = "none";
13
+
14
+ elem.parentNode.scrollIntoView();
15
+ }
16
+ </script>
17
+ <style>
18
+ <%= css %>
19
+ </style>
20
+ </head>
21
+ <body>
22
+
23
+ <h1>Brakeman Report</h1>
24
+ <table>
25
+ <tr>
26
+ <th>Application Path</th>
27
+ <th>Rails Version</th>
28
+ <th>Report Generation Time</th>
29
+ <th>Checks Performed</th>
30
+ </tr>
31
+ <tr>
32
+ <td><%= File.expand_path tracker.options[:app_path] %></td>
33
+ <td><%= rails_version %></td>
34
+ <td><%= Time.now %></td>
35
+ <td><%= checks.checks_run.sort.join(", ") %></td>
36
+ </tr>
37
+ </table>
38
+ <br>
@@ -0,0 +1,17 @@
1
+ <p>Model Warnings</p>
2
+ <table>
3
+ <tr>
4
+ <th>Confidence</th>
5
+ <th>Model</th>
6
+ <th>Warning Type</th>
7
+ <th>Message</th>
8
+ </tr>
9
+ <% warnings.each do |warning| %>
10
+ <tr>
11
+ <td><%= warning['Confidence']%></td>
12
+ <td><%= warning['Model']%></td>
13
+ <td><%= warning['Warning Type']%></td>
14
+ <td><%= warning['Message']%></td>
15
+ </tr>
16
+ <% end %>
17
+ </table>
@@ -0,0 +1,28 @@
1
+ <h2 id='summary'>Summary</h2>
2
+ <table>
3
+ <tr>
4
+ <th>Scanned/Reported</th>
5
+ <th>Total</th>
6
+ </tr>
7
+ <tr>
8
+ <td>Controllers</td>
9
+ <td><%= tracker.controllers.length %></td>
10
+ </tr>
11
+ <tr>
12
+ <td>Models</td>
13
+ <td><%= tracker.models.length - 1 %></td>
14
+ </tr>
15
+ <tr>
16
+ <td>Templates</td>
17
+ <td><%= number_of_templates(@tracker) %></td>
18
+ </tr>
19
+ <tr>
20
+ <td>Errors</td>
21
+ <td><%= tracker.errors.length %></td>
22
+ </tr>
23
+ <tr>
24
+ <td>Security Warnings</td>
25
+ <td><%= warnings %> <span class='high-confidence'>(<%= warnings_summary[:high_confidence] %>)</span></td>
26
+ </tr>
27
+ </table>
28
+ <br>
@@ -0,0 +1,28 @@
1
+ <h2>Security Warnings</h2>
2
+ <table>
3
+ <% if warning_messages.any? %>
4
+ <tr>
5
+ <th>Confidence</th>
6
+ <th>Class</th>
7
+ <th>Method</th>
8
+ <th>Warning Type</th>
9
+ <th>Message</th>
10
+ </tr>
11
+ <% warning_messages.each do |warning| %>
12
+ <tr>
13
+ <td><%= warning['Confidence']%></td>
14
+ <td><%= warning['Class']%></td>
15
+ <td><%= warning['Method']%></td>
16
+ <td><%= warning['Warning Type']%></td>
17
+ <td><%= warning['Message']%></td>
18
+ </tr>
19
+ <% end %>
20
+ <% else %>
21
+ <tr>
22
+ <th>General Warnings</th>
23
+ </tr>
24
+ <tr>
25
+ <td>[NONE]</td>
26
+ </tr>
27
+ <% end %>
28
+ </table>
@@ -0,0 +1,17 @@
1
+ <h2>Templates</h2>
2
+
3
+ <% template_rows.each do |template| %>
4
+
5
+ <p><%= template[0] %></p>
6
+ <table>
7
+ <tr>
8
+ <th>Output</th>
9
+ </tr>
10
+ <% template[1].each do |call| %>
11
+ <tr>
12
+ <td><%= call %></td>
13
+ </tr>
14
+ <% end %>
15
+ </table>
16
+
17
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <p>View Warnings</p>
2
+ <table>
3
+ <tr>
4
+ <th>Confidence</th>
5
+ <th>Template</th>
6
+ <th>Warning Type</th>
7
+ <th>Message</th>
8
+ </tr>
9
+ <% warnings.each do |warning| %>
10
+ <tr>
11
+ <td><%= warning['Confidence']%></td>
12
+ <td><%= warning['Template']%></td>
13
+ <td><%= warning['Warning Type']%></td>
14
+ <td><%= warning['Message']%></td>
15
+ </tr>
16
+ <% end %>
17
+ </table>
@@ -0,0 +1,13 @@
1
+ <table>
2
+ <tr>
3
+ <th>Warning Type</th>
4
+ <th>Total</th>
5
+ </tr>
6
+ <% types.sort.each do |warning_type| %>
7
+ <tr>
8
+ <td><%= warning_type %></td>
9
+ <td><%= warnings_summary[warning_type] %></td>
10
+ </tr>
11
+ <% end %>
12
+ </table>
13
+ <br>
@@ -85,7 +85,7 @@ class Brakeman::Tracker
85
85
  #Prioritizes templates which have been rendered.
86
86
  def each_template
87
87
  if @processed.nil?
88
- @processed, @rest = templates.keys.partition { |k| k.to_s.include? "." }
88
+ @processed, @rest = templates.keys.sort_by{|template| template.to_s}.partition { |k| k.to_s.include? "." }
89
89
  end
90
90
 
91
91
  @processed.each do |k|
data/lib/brakeman/util.rb CHANGED
@@ -88,10 +88,8 @@ module Brakeman::Util
88
88
  key = Sexp.new(:lit, key)
89
89
  end
90
90
 
91
- hash_iterate hash do |k, v|
92
- if k == key
93
- return v
94
- end
91
+ if index = hash.find_index(key) and index > 0
92
+ return hash[index + 1]
95
93
  end
96
94
 
97
95
  nil
@@ -326,4 +324,26 @@ module Brakeman::Util
326
324
 
327
325
  context
328
326
  end
327
+
328
+ def truncate_table str
329
+ @terminal_width ||= ::HighLine::SystemExtensions::terminal_size[0]
330
+ lines = str.lines
331
+
332
+ lines.map do |line|
333
+ if line.chomp.length > @terminal_width
334
+ line[0..(@terminal_width - 3)] + ">>"
335
+ else
336
+ line
337
+ end
338
+ end.join
339
+ end
340
+
341
+ # rely on Terminal::Table to build the structure, extract the data out in CSV format
342
+ def table_to_csv table
343
+ output = CSV.generate_line(table.headings.cells.map{|cell| cell.to_s.strip})
344
+ table.rows.each do |row|
345
+ output << CSV.generate_line(row.cells.map{|cell| cell.to_s.strip})
346
+ end
347
+ output
348
+ end
329
349
  end