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,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