brakeman 1.5.3 → 1.6.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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