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.
- data/bin/brakeman +20 -6
- data/lib/brakeman.rb +21 -1
- data/lib/brakeman/checks.rb +2 -7
- data/lib/brakeman/checks/base_check.rb +31 -26
- data/lib/brakeman/checks/check_basic_auth.rb +1 -7
- data/lib/brakeman/checks/check_cross_site_scripting.rb +38 -33
- data/lib/brakeman/checks/check_evaluation.rb +2 -1
- data/lib/brakeman/checks/check_execute.rb +5 -1
- data/lib/brakeman/checks/check_file_access.rb +6 -4
- data/lib/brakeman/checks/check_link_to.rb +8 -5
- data/lib/brakeman/checks/check_link_to_href.rb +6 -5
- data/lib/brakeman/checks/check_mail_to.rb +2 -4
- data/lib/brakeman/checks/check_mass_assignment.rb +12 -6
- data/lib/brakeman/checks/check_redirect.rb +17 -14
- data/lib/brakeman/checks/check_render.rb +4 -4
- data/lib/brakeman/checks/check_send.rb +4 -2
- data/lib/brakeman/checks/check_session_settings.rb +16 -21
- data/lib/brakeman/checks/check_skip_before_filter.rb +2 -4
- data/lib/brakeman/checks/check_sql.rb +8 -7
- data/lib/brakeman/checks/check_validation_regex.rb +2 -4
- data/lib/brakeman/checks/check_without_protection.rb +8 -9
- data/lib/brakeman/differ.rb +61 -0
- data/lib/brakeman/format/style.css +4 -0
- data/lib/brakeman/options.rb +8 -0
- data/lib/brakeman/processors/alias_processor.rb +5 -7
- data/lib/brakeman/processors/controller_alias_processor.rb +10 -3
- data/lib/brakeman/processors/erb_template_processor.rb +2 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +2 -0
- data/lib/brakeman/processors/gem_processor.rb +8 -0
- data/lib/brakeman/processors/haml_template_processor.rb +2 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +2 -6
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +2 -4
- data/lib/brakeman/processors/lib/render_helper.rb +3 -1
- data/lib/brakeman/processors/library_processor.rb +4 -6
- data/lib/brakeman/processors/template_alias_processor.rb +1 -1
- data/lib/brakeman/report.rb +257 -198
- data/lib/brakeman/rescanner.rb +112 -10
- data/lib/brakeman/scanner.rb +3 -4
- data/lib/brakeman/templates/controller_overview.html.erb +18 -0
- data/lib/brakeman/templates/controller_warnings.html.erb +17 -0
- data/lib/brakeman/templates/error_overview.html.erb +14 -0
- data/lib/brakeman/templates/header.html.erb +38 -0
- data/lib/brakeman/templates/model_warnings.html.erb +17 -0
- data/lib/brakeman/templates/overview.html.erb +28 -0
- data/lib/brakeman/templates/security_warnings.html.erb +28 -0
- data/lib/brakeman/templates/template_overview.html.erb +17 -0
- data/lib/brakeman/templates/view_warnings.html.erb +17 -0
- data/lib/brakeman/templates/warning_overview.html.erb +13 -0
- data/lib/brakeman/tracker.rb +1 -1
- data/lib/brakeman/util.rb +24 -4
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning.rb +11 -3
- data/lib/ruby_parser/bm_sexp.rb +5 -11
- metadata +84 -23
data/lib/brakeman/rescanner.rb
CHANGED
@@ -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 =
|
275
|
-
|
276
|
-
|
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
|
-
|
381
|
+
w["Confidence"] = Brakeman::Report::TEXT_CONFIDENCE[w["Confidence"]]
|
280
382
|
|
281
|
-
|
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
|
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -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]
|
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>
|
data/lib/brakeman/tracker.rb
CHANGED
@@ -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
|
-
|
92
|
-
|
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
|