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.
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,130 @@
1
+ require 'set'
2
+ require 'multi_json'
3
+
4
+ module Brakeman
5
+ class IgnoreConfig
6
+ attr_reader :shown_warnings, :ignored_warnings
7
+ attr_accessor :file
8
+
9
+ def initialize file, new_warnings
10
+ @file = file
11
+ @new_warnings = new_warnings
12
+ @already_ignored = []
13
+ @ignored_fingerprints = Set.new
14
+ @notes = {}
15
+ @shown_warnings = @ignored_warnings = nil
16
+ end
17
+
18
+ # Populate ignored_warnings and shown_warnings based on ignore
19
+ # configuration
20
+ def filter_ignored
21
+ @shown_warnings = []
22
+ @ignored_warnings = []
23
+
24
+ @new_warnings.each do |w|
25
+ if ignored? w
26
+ @ignored_warnings << w
27
+ else
28
+ @shown_warnings << w
29
+ end
30
+ end
31
+
32
+ @shown_warnings
33
+ end
34
+
35
+ # Remove warning from ignored list
36
+ def unignore warning
37
+ @ignored_fingerprints.delete warning.fingerprint
38
+ @already_ignored.reject! do |w|
39
+ w[:fingerprint] == warning.fingerprint
40
+ end
41
+ end
42
+
43
+ # Determine if warning should be ignored
44
+ def ignored? warning
45
+ @ignored_fingerprints.include? warning.fingerprint
46
+ end
47
+
48
+ def ignore warning
49
+ @ignored_fingerprints << warning.fingerprint
50
+ end
51
+
52
+ # Add note for warning
53
+ def add_note warning, note
54
+ @notes[warning.fingerprint] = note
55
+ end
56
+
57
+ # Retrieve note for warning if it exists. Returns nil if no
58
+ # note is found
59
+ def note_for warning
60
+ if warning.is_a? Warning
61
+ fingerprint = warning.fingerprint
62
+ else
63
+ fingerprint = warning[:fingerprint]
64
+ end
65
+
66
+ @already_ignored.each do |w|
67
+ if fingerprint == w[:fingerprint]
68
+ return w[:note]
69
+ end
70
+ end
71
+
72
+ nil
73
+ end
74
+
75
+ # Read configuration to file
76
+ def read_from_file file = @file
77
+ if File.exist? file
78
+ @already_ignored = MultiJson.load(File.read(file), :symbolize_keys => true)[:ignored_warnings]
79
+ else
80
+ Brakeman.notify "[Notice] Could not find ignore configuration in #{file}"
81
+ @already_ignored = []
82
+ end
83
+
84
+ @already_ignored.each do |w|
85
+ @ignored_fingerprints << w[:fingerprint]
86
+ @notes[w[:fingerprint]] = w[:note]
87
+ end
88
+ end
89
+
90
+ # Save configuration to file
91
+ def save_to_file warnings, file = @file
92
+ warnings = warnings.map do |w|
93
+ if w.is_a? Warning
94
+ w_hash = w.to_hash
95
+ w_hash[:file] = w.relative_path
96
+ w = w_hash
97
+ end
98
+
99
+ w[:note] = @notes[w[:fingerprint]] || ""
100
+ w
101
+ end
102
+
103
+ output = {
104
+ :ignored_warnings => warnings,
105
+ :updated => Time.now.to_s,
106
+ :brakeman_version => Brakeman::Version
107
+ }
108
+
109
+ File.open file, "w" do |f|
110
+ f.puts MultiJson.dump(output, :pretty => true)
111
+ end
112
+ end
113
+
114
+ # Save old ignored warnings and newly ignored ones
115
+ def save_with_old
116
+ warnings = @ignored_warnings.dup
117
+
118
+ # Only add ignored warnings not already ignored
119
+ @already_ignored.each do |w|
120
+ fingerprint = w[:fingerprint]
121
+
122
+ unless @ignored_warnings.find { |w| w.fingerprint == fingerprint }
123
+ warnings << w
124
+ end
125
+ end
126
+
127
+ save_to_file warnings
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,311 @@
1
+ Brakeman.load_dependency 'highline'
2
+
3
+ module Brakeman
4
+ class InteractiveIgnorer
5
+ def initialize file, warnings
6
+ @ignore_config = Brakeman::IgnoreConfig.new(file, warnings)
7
+ @new_warnings = warnings
8
+ @skip_ignored = false
9
+ @skip_rest = false
10
+ @ignore_rest = false
11
+ @quit = false
12
+ @restart = false
13
+ end
14
+
15
+ def start
16
+ file_menu
17
+ initial_menu
18
+
19
+ @ignore_config.filter_ignored
20
+
21
+ unless @quit
22
+ final_menu
23
+ end
24
+
25
+ if @restart
26
+ @restart = false
27
+ start
28
+ end
29
+
30
+ @ignore_config
31
+ end
32
+
33
+ private
34
+
35
+ def file_menu
36
+ loop do
37
+ @ignore_config.file = HighLine.new.ask "Input file: " do |q|
38
+ if @ignore_config.file and not @ignore_config.file.empty?
39
+ q.default = @ignore_config.file
40
+ else
41
+ q.default = "config/brakeman.ignore"
42
+ end
43
+ end
44
+
45
+ if File.exist? @ignore_config.file
46
+ @ignore_config.read_from_file
47
+ return
48
+ else
49
+ if yes_or_no "No such file. Continue with empty config? "
50
+ return
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ def initial_menu
57
+ HighLine.new.choose do |m|
58
+ m.choice "Inspect all warnings" do
59
+ @skip_ignored = false
60
+ process_warnings
61
+ end
62
+
63
+ m.choice "Hide previously ignored warnings" do
64
+ @skip_ignored = true
65
+ process_warnings
66
+ end
67
+
68
+ m.choice "Skip - use current ignore configuration" do
69
+ @quit = true
70
+ @ignore_config.filter_ignored
71
+ end
72
+ end
73
+ end
74
+
75
+ def warning_menu
76
+ HighLine.new.choose do |m|
77
+ m.prompt = "Action: "
78
+ m.layout = :one_line
79
+ m.list_option = ", "
80
+ m.select_by = :name
81
+
82
+ m.choice "i"
83
+ m.choice "n"
84
+ m.choice "k"
85
+ m.choice "u"
86
+ m.choice "a"
87
+ m.choice "s"
88
+ m.choice "q"
89
+ m.choice "?" do
90
+ say <<-HELP
91
+ i - Add warning to ignore list
92
+ n - Add warning to ignore list and add note
93
+ s - Skip this warning (will remain ignored or shown)
94
+ u - Remove this warning from ignore list
95
+ a - Ignore this warning and all remaining warnings
96
+ k - Skip this warning and all remaining warnings
97
+ q - Quit, do not update ignored warnings
98
+ ? - Display this help
99
+ HELP
100
+
101
+ "?"
102
+ end
103
+ end
104
+ end
105
+
106
+ def final_menu
107
+ summarize_changes
108
+
109
+ HighLine.new.choose do |m|
110
+ m.choice "Save changes" do
111
+ save
112
+ end
113
+
114
+ m.choice "Start over" do
115
+ start_over
116
+ end
117
+
118
+ m.choice "Quit, do not save changes" do
119
+ quit
120
+ end
121
+ end
122
+ end
123
+
124
+ def save
125
+ @ignore_config.file = HighLine.new.ask "Output file: " do |q|
126
+ if @ignore_config.file and not @ignore_config.file.empty?
127
+ q.default = @ignore_config.file
128
+ else
129
+ q.default = "config/brakeman.ignore"
130
+ end
131
+ end
132
+
133
+ @ignore_config.save_with_old
134
+ end
135
+
136
+ def start_over
137
+ reset_config
138
+ @restart = true
139
+ end
140
+
141
+ def reset_config
142
+ @ignore_config = Brakeman::IgnoreConfig.new(@ignore_config.file, @new_warnings)
143
+ end
144
+
145
+ def process_warnings
146
+ @new_warnings.each do |w|
147
+ if skip_ignored? w or @skip_rest
148
+ next
149
+ elsif @ignore_rest
150
+ ignore w
151
+ elsif @quit or @restart
152
+ return
153
+ else
154
+ ask_about w
155
+ end
156
+ end
157
+ end
158
+
159
+ def ask_about warning
160
+ pretty_display warning
161
+ warning_action warning_menu, warning
162
+ end
163
+
164
+ def warning_action action, warning
165
+ case action
166
+ when "i"
167
+ ignore warning
168
+ when "n"
169
+ ignore_and_note warning
170
+ when "s"
171
+ # do nothing
172
+ when "u"
173
+ unignore warning
174
+ when "a"
175
+ ignore_rest warning
176
+ when "k"
177
+ skip_rest warning
178
+ when "q"
179
+ quit
180
+ when "?"
181
+ ask_about warning
182
+ else
183
+ raise "Unexpected action"
184
+ end
185
+ end
186
+
187
+ def ignore warning
188
+ @ignore_config.ignore warning
189
+ end
190
+
191
+ def ignore_and_note warning
192
+ note = HighLine.new.ask("Note: ")
193
+ @ignore_config.ignore warning
194
+ @ignore_config.add_note warning, note
195
+ end
196
+
197
+ def unignore warning
198
+ @ignore_config.unignore warning
199
+ end
200
+
201
+ def skip_rest warning
202
+ @skip_rest = true
203
+ end
204
+
205
+ def ignore_rest warning
206
+ ignore warning
207
+ @ignore_rest = true
208
+ end
209
+
210
+ def quit
211
+ reset_config
212
+ @ignore_config.read_from_file
213
+ @ignore_config.filter_ignored
214
+ @quit = true
215
+ end
216
+
217
+ def pretty_display warning
218
+ say "-" * 20
219
+ show_confidence warning
220
+
221
+ label "Category"
222
+ say warning.warning_type
223
+
224
+ label "Message"
225
+ say warning.message
226
+
227
+ if warning.code
228
+ label "Code"
229
+ say warning.format_code
230
+ end
231
+
232
+ if warning.relative_path
233
+ label "File"
234
+ say warning.relative_path
235
+ end
236
+
237
+ if warning.line
238
+ label "Line"
239
+ say warning.line
240
+ end
241
+
242
+ if already_ignored? warning
243
+ show_note warning
244
+ say "Already ignored", :red
245
+ end
246
+
247
+ say ""
248
+ end
249
+
250
+ def already_ignored? warning
251
+ @ignore_config.ignored? warning
252
+ end
253
+
254
+ def skip_ignored? warning
255
+ @skip_ignored and already_ignored? warning
256
+ end
257
+
258
+ def summarize_changes
259
+ say "-" * 20
260
+
261
+ say "Ignoring #{@ignore_config.ignored_warnings.length} warnings", :yellow
262
+ say "Showing #{@ignore_config.shown_warnings.length} warnings", :green
263
+ end
264
+
265
+ def label name
266
+ say "#{name}: ", :green
267
+ end
268
+
269
+ def show_confidence warning
270
+ label "Confidence"
271
+
272
+ case warning.confidence
273
+ when 0
274
+ say "High", :red
275
+ when 1
276
+ say "Medium", :yellow
277
+ when 2
278
+ say "Weak", :cyan
279
+ else
280
+ say "Unknown"
281
+ end
282
+ end
283
+
284
+ def show_note warning
285
+ note = @ignore_config.note_for warning
286
+
287
+ if note
288
+ label "Note"
289
+ say note
290
+ end
291
+ end
292
+
293
+ def say text, color = nil
294
+ text = text.to_s
295
+
296
+ if color
297
+ HighLine.new.say HighLine.new.color(text, color)
298
+ else
299
+ HighLine.new.say text
300
+ end
301
+ end
302
+
303
+ def yes_or_no message
304
+ answer = HighLine.new.ask message do |q|
305
+ q.in = ["y", "n", "yes", "no"]
306
+ end
307
+
308
+ answer.match /^y/i
309
+ end
310
+ end
311
+ end
@@ -1,3 +1,5 @@
1
+ require 'erb'
2
+
1
3
  class Brakeman::Report
2
4
  class Renderer
3
5
  def initialize(template_file, hash = {})
@@ -0,0 +1,279 @@
1
+ require 'set'
2
+ require 'brakeman/util'
3
+ require 'brakeman/version'
4
+ require 'brakeman/report/renderer'
5
+ require 'brakeman/processors/output_processor'
6
+
7
+ # Base class for report formats
8
+ class Brakeman::Report::Base
9
+ include Brakeman::Util
10
+
11
+ attr_reader :tracker, :checks
12
+
13
+ TEXT_CONFIDENCE = [ "High", "Medium", "Weak" ]
14
+
15
+ def initialize app_tree, tracker
16
+ @app_tree = app_tree
17
+ @tracker = tracker
18
+ @checks = tracker.checks
19
+ @ignore_filter = tracker.ignored_filter
20
+ @highlight_user_input = tracker.options[:highlight_user_input]
21
+ @warnings_summary = nil
22
+ end
23
+
24
+ #Generate table of how many warnings of each warning type were reported
25
+ def generate_warning_overview
26
+ types = warnings_summary.keys
27
+ types.delete :high_confidence
28
+ values = types.sort.collect{|warning_type| [warning_type, warnings_summary[warning_type]] }
29
+ locals = {:types => types, :warnings_summary => warnings_summary}
30
+
31
+ render_array('warning_overview', ['Warning Type', 'Total'], values, locals)
32
+ end
33
+
34
+ #Generate table of controllers and routes found for those controllers
35
+ def generate_controllers
36
+ controller_rows = []
37
+
38
+ tracker.controllers.keys.map{|k| k.to_s}.sort.each do |name|
39
+ name = name.to_sym
40
+ c = tracker.controllers[name]
41
+
42
+ if tracker.routes[:allow_all_actions] or tracker.routes[name] == :allow_all_actions
43
+ routes = c[:public].keys.map{|e| e.to_s}.sort.join(", ")
44
+ elsif tracker.routes[name].nil?
45
+ #No routes defined for this controller.
46
+ #This can happen when it is only a parent class
47
+ #for other controllers, for example.
48
+ routes = "[None]"
49
+
50
+ else
51
+ routes = (Set.new(c[:public].keys) & tracker.routes[name.to_sym]).
52
+ to_a.
53
+ map {|e| e.to_s}.
54
+ sort.
55
+ join(", ")
56
+ end
57
+
58
+ if routes == ""
59
+ routes = "[None]"
60
+ end
61
+
62
+ controller_rows << { "Name" => name.to_s,
63
+ "Parent" => c[:parent].to_s,
64
+ "Includes" => c[:includes].join(", "),
65
+ "Routes" => routes
66
+ }
67
+ end
68
+
69
+ cols = ['Name', 'Parent', 'Includes', 'Routes']
70
+
71
+ locals = {:controller_rows => controller_rows}
72
+ values = controller_rows.collect{|row| row.values_at(*cols) }
73
+ render_array('controller_overview', cols, values, locals)
74
+ end
75
+
76
+ #Generate table of errors or return nil if no errors
77
+ def generate_errors
78
+ values = tracker.errors.collect{|error| [error[:error], error[:backtrace][0]]}
79
+ render_array('error_overview', ['Error', 'Location'], values, {:tracker => tracker})
80
+ end
81
+
82
+ def generate_warnings
83
+ render_warnings generic_warnings,
84
+ :warning,
85
+ 'security_warnings',
86
+ ["Confidence", "Class", "Method", "Warning Type", "Message"],
87
+ 'Class'
88
+ end
89
+
90
+ #Generate table of template warnings or return nil if no warnings
91
+ def generate_template_warnings
92
+ render_warnings template_warnings,
93
+ :template,
94
+ 'view_warnings',
95
+ ['Confidence', 'Template', 'Warning Type', 'Message'],
96
+ 'Template'
97
+
98
+ end
99
+
100
+ #Generate table of model warnings or return nil if no warnings
101
+ def generate_model_warnings
102
+ render_warnings model_warnings,
103
+ :model,
104
+ 'model_warnings',
105
+ ['Confidence', 'Model', 'Warning Type', 'Message'],
106
+ 'Model'
107
+ end
108
+
109
+ #Generate table of controller warnings or nil if no warnings
110
+ def generate_controller_warnings
111
+ render_warnings controller_warnings,
112
+ :controller,
113
+ 'controller_warnings',
114
+ ['Confidence', 'Controller', 'Warning Type', 'Message'],
115
+ 'Controller'
116
+ end
117
+
118
+ def generate_ignored_warnings
119
+ render_warnings ignored_warnings,
120
+ :ignored,
121
+ 'ignored_warnings',
122
+ ['Confidence', 'Warning Type', 'File', 'Message'],
123
+ 'Warning Type'
124
+ end
125
+
126
+ def render_warnings warnings, type, template, cols, sort_col
127
+ unless warnings.empty?
128
+ rows = sort(convert_to_rows(warnings, type), sort_col)
129
+
130
+ values = rows.collect { |row| row.values_at(*cols) }
131
+
132
+ locals = { :warnings => rows }
133
+
134
+ render_array(template, cols, values, locals)
135
+ else
136
+ nil
137
+ end
138
+ end
139
+
140
+ def convert_to_rows warnings, type = :warning
141
+ warnings.map do |warning|
142
+ w = warning.to_row type
143
+
144
+ case type
145
+ when :warning
146
+ convert_warning w, warning
147
+ when :template
148
+ convert_template_warning w, warning
149
+ when :model
150
+ convert_model_warning w, warning
151
+ when :controller
152
+ convert_controller_warning w, warning
153
+ when :ignored
154
+ convert_ignored_warning w, warning
155
+ end
156
+ end
157
+ end
158
+
159
+ def convert_warning warning, original
160
+ warning["Confidence"] = TEXT_CONFIDENCE[warning["Confidence"]]
161
+ warning["Message"] = text_message original, warning["Message"]
162
+ warning
163
+ end
164
+
165
+ def convert_template_warning warning, original
166
+ convert_warning warning, original
167
+ end
168
+
169
+ def convert_model_warning warning, original
170
+ convert_warning warning, original
171
+ end
172
+
173
+ def convert_controller_warning warning, original
174
+ convert_warning warning, original
175
+ end
176
+
177
+ def convert_ignored_warning warning, original
178
+ convert_warning warning, original
179
+ end
180
+
181
+ def sort rows, sort_col
182
+ stabilizer = 0
183
+ rows.sort_by do |row|
184
+ stabilizer += 1
185
+
186
+ row.values_at("Confidence", "Warning Type", sort_col) << stabilizer
187
+ end
188
+ end
189
+
190
+ #Return summary of warnings in hash and store in @warnings_summary
191
+ def warnings_summary
192
+ return @warnings_summary if @warnings_summary
193
+
194
+ summary = Hash.new(0)
195
+ high_confidence_warnings = 0
196
+
197
+ [all_warnings].each do |warnings|
198
+ warnings.each do |warning|
199
+ summary[warning.warning_type.to_s] += 1
200
+ high_confidence_warnings += 1 if warning.confidence == 0
201
+ end
202
+ end
203
+
204
+ summary[:high_confidence] = high_confidence_warnings
205
+ @warnings_summary = summary
206
+ end
207
+
208
+ def all_warnings
209
+ if @ignore_filter
210
+ @all_warnings ||= @ignore_filter.shown_warnings
211
+ else
212
+ @all_warnings ||= tracker.checks.all_warnings
213
+ end
214
+ end
215
+
216
+ def filter_warnings warnings
217
+ if @ignore_filter
218
+ warnings.reject do |w|
219
+ @ignore_filter.ignored? w
220
+ end
221
+ else
222
+ warnings
223
+ end
224
+ end
225
+
226
+ def generic_warnings
227
+ filter_warnings tracker.checks.warnings
228
+ end
229
+
230
+ def template_warnings
231
+ filter_warnings tracker.checks.template_warnings
232
+ end
233
+
234
+ def model_warnings
235
+ filter_warnings tracker.checks.model_warnings
236
+ end
237
+
238
+ def controller_warnings
239
+ filter_warnings tracker.checks.controller_warnings
240
+ end
241
+
242
+ def ignored_warnings
243
+ if @ignore_filter
244
+ @ignore_filter.ignored_warnings
245
+ else
246
+ []
247
+ end
248
+ end
249
+
250
+ def number_of_templates tracker
251
+ Set.new(tracker.templates.map {|k,v| v[:name].to_s[/[^.]+/]}).length
252
+ end
253
+
254
+ def warning_file warning, absolute = @tracker.options[:absolute_paths]
255
+ return nil if warning.file.nil?
256
+
257
+ if absolute
258
+ warning.file
259
+ else
260
+ relative_path warning.file
261
+ end
262
+ end
263
+
264
+ def rails_version
265
+ return tracker.config[:rails_version] if tracker.config[:rails_version]
266
+ return "3.x" if tracker.options[:rails3]
267
+ "Unknown"
268
+ end
269
+
270
+ #Escape warning message and highlight user input in text output
271
+ def text_message warning, message
272
+ if @highlight_user_input and warning.user_input
273
+ user_input = warning.format_user_input
274
+ message.gsub(user_input, "+#{user_input}+")
275
+ else
276
+ message
277
+ end
278
+ end
279
+ end