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
data/bin/brakeman CHANGED
@@ -7,7 +7,13 @@ require 'brakeman/options'
7
7
  require 'brakeman/version'
8
8
 
9
9
  #Parse options
10
- options, parser = Brakeman::Options.parse! ARGV
10
+ begin
11
+ options, parser = Brakeman::Options.parse! ARGV
12
+ rescue OptionParser::ParseError => e
13
+ $stderr.puts e.message.capitalize
14
+ $stderr.puts "Please see `brakeman --help` for valid options"
15
+ exit -1
16
+ end
11
17
 
12
18
  #Exit early for these options
13
19
  if options[:list_checks]
@@ -46,10 +52,18 @@ trap("INT") do
46
52
  exit!
47
53
  end
48
54
 
49
- #Run scan and output a report
50
- tracker = Brakeman.run options.merge(:print_report => true, :quiet => options[:quiet])
55
+ if options[:previous_results_json]
56
+ vulns = Brakeman.compare options
57
+ puts JSON.pretty_generate(vulns)
58
+ else
59
+ #Run scan and output a report
60
+ tracker = Brakeman.run options.merge(:print_report => true, :quiet => options[:quiet])
51
61
 
52
- #Return error code if --exit-on-warn is used and warnings were found
53
- if options[:exit_on_warn] and not tracker.checks.all_warnings.empty?
54
- exit Brakeman::Warnings_Found_Exit_Code
62
+ #Return error code if --exit-on-warn is used and warnings were found
63
+ if options[:exit_on_warn] and not tracker.checks.all_warnings.empty?
64
+ exit Brakeman::Warnings_Found_Exit_Code
65
+ end
55
66
  end
67
+
68
+
69
+
data/lib/brakeman.rb CHANGED
@@ -23,6 +23,7 @@ module Brakeman
23
23
  # * :config_file - configuration file
24
24
  # * :escape_html - escape HTML by default (automatic)
25
25
  # * :exit_on_warn - return false if warnings found, true otherwise. Not recommended for library use (default: false)
26
+ # * :highlight_user_input - highlight user input in reported warnings (default: true)
26
27
  # * :html_style - path to CSS file
27
28
  # * :ignore_model_output - consider models safe (default: false)
28
29
  # * :message_limit - limit length of messages
@@ -51,7 +52,6 @@ module Brakeman
51
52
  if @quiet
52
53
  options[:report_progress] = false
53
54
  end
54
-
55
55
  scan options
56
56
  end
57
57
 
@@ -114,6 +114,7 @@ module Brakeman
114
114
  :min_confidence => 2,
115
115
  :combine_locations => true,
116
116
  :collapse_mass_assignment => true,
117
+ :highlight_user_input => true,
117
118
  :ignore_redirect_to_model => true,
118
119
  :ignore_model_output => false,
119
120
  :message_limit => 100,
@@ -310,4 +311,23 @@ module Brakeman
310
311
  def self.debug message
311
312
  $stderr.puts message if @debug
312
313
  end
314
+
315
+ # Compare JSON ouptut from a previous scan and return the diff of the two scans
316
+ def self.compare options
317
+ require 'json'
318
+ require 'brakeman/differ'
319
+ raise ArgumentError.new("Comparison file doesn't exist") unless File.exists? options[:previous_results_json]
320
+
321
+ begin
322
+ previous_results = JSON.parse(File.read(options[:previous_results_json]), :symbolize_names =>true)[:warnings]
323
+ rescue JSON::ParserError
324
+ self.notify "Error parsing comparison file: #{options[:previous_results_json]}"
325
+ exit!
326
+ end
327
+
328
+ tracker = run(options)
329
+ new_results = JSON.parse(tracker.report.to_json, :symbolize_names =>true)[:warnings]
330
+
331
+ Brakeman::Differ.new(new_results, previous_results).diff
332
+ end
313
333
  end
@@ -1,4 +1,5 @@
1
1
  require 'thread'
2
+ require 'brakeman/differ'
2
3
 
3
4
  #Collects up results from running different checks.
4
5
  #
@@ -64,13 +65,7 @@ class Brakeman::Checks
64
65
  def diff other_checks
65
66
  my_warnings = self.all_warnings
66
67
  other_warnings = other_checks.all_warnings
67
-
68
- diff = {}
69
-
70
- diff[:fixed] = other_warnings - my_warnings
71
- diff[:new] = my_warnings - other_warnings
72
-
73
- diff
68
+ Brakeman::Differ.new(my_warnings, other_warnings).diff
74
69
  end
75
70
 
76
71
  #Return an array of all warnings found.
@@ -12,6 +12,8 @@ class Brakeman::BaseCheck < SexpProcessor
12
12
 
13
13
  CONFIDENCE = { :high => 0, :med => 1, :low => 2 }
14
14
 
15
+ Match = Struct.new(:type, :match)
16
+
15
17
  #Initialize Check with Checks.
16
18
  def initialize tracker
17
19
  super()
@@ -66,13 +68,13 @@ class Brakeman::BaseCheck < SexpProcessor
66
68
  process exp[3]
67
69
 
68
70
  if params? exp[1]
69
- @has_user_input = :params
71
+ @has_user_input = Match.new(:params, exp)
70
72
  elsif cookies? exp[1]
71
- @has_user_input = :cookies
73
+ @has_user_input = Match.new(:cookies, exp)
72
74
  elsif request_env? exp[1]
73
- @has_user_input = :request
75
+ @has_user_input = Match.new(:request, exp)
74
76
  elsif sexp? exp[1] and model_name? exp[1][1]
75
- @has_user_input = :model
77
+ @has_user_input = Match.new(:model, exp)
76
78
  end
77
79
 
78
80
  exp
@@ -92,13 +94,13 @@ class Brakeman::BaseCheck < SexpProcessor
92
94
 
93
95
  #Note that params are included in current expression
94
96
  def process_params exp
95
- @has_user_input = :params
97
+ @has_user_input = Match.new(:params, exp)
96
98
  exp
97
99
  end
98
100
 
99
101
  #Note that cookies are included in current expression
100
102
  def process_cookies exp
101
- @has_user_input = :cookies
103
+ @has_user_input = Match.new(:cookies, exp)
102
104
  exp
103
105
  end
104
106
 
@@ -206,19 +208,26 @@ class Brakeman::BaseCheck < SexpProcessor
206
208
 
207
209
  #Does not actually process string interpolation, but notes that it occurred.
208
210
  def process_string_interp exp
209
- @string_interp = true
211
+ @string_interp = Match.new(:interp, exp)
210
212
  exp
211
213
  end
212
214
 
213
215
  #Checks if an expression contains string interpolation.
216
+ #
217
+ #Returns Match with :interp type if found.
214
218
  def include_interp? exp
215
219
  @string_interp = false
216
220
  process exp
217
221
  @string_interp
218
222
  end
219
223
 
220
- #Checks if _exp_ includes parameters or cookies, but this only works
221
- #with the base process_default.
224
+ #Checks if _exp_ includes user input in the form of cookies, parameters,
225
+ #request environment, or model attributes.
226
+ #
227
+ #If found, returns a struct containing a type (:cookies, :params, :request, :model) and
228
+ #the matching expression (Match#type and Match#match).
229
+ #
230
+ #Returns false otherwise.
222
231
  def include_user_input? exp
223
232
  @has_user_input = false
224
233
  process exp
@@ -227,24 +236,24 @@ class Brakeman::BaseCheck < SexpProcessor
227
236
 
228
237
  #This is used to check for user input being used directly.
229
238
  #
230
- #Returns false if none is found, otherwise it returns an array
231
- #where the first element is the type of user input
232
- #(either :params or :cookies) and the second element is the matching
233
- #expression
239
+ ##If found, returns a struct containing a type (:cookies, :params, :request) and
240
+ #the matching expression (Match#type and Match#match).
241
+ #
242
+ #Returns false otherwise.
234
243
  def has_immediate_user_input? exp
235
244
  if exp.nil?
236
245
  false
237
246
  elsif params? exp
238
- return :params, exp
247
+ return Match.new(:params, exp)
239
248
  elsif cookies? exp
240
- return :cookies, exp
249
+ return Match.new(:cookies, exp)
241
250
  elsif call? exp
242
251
  if params? exp[1]
243
- return :params, exp
252
+ return Match.new(:params, exp)
244
253
  elsif cookies? exp[1]
245
- return :cookies, exp
254
+ return Match.new(:cookies, exp)
246
255
  elsif request_env? exp[1]
247
- return :request, exp
256
+ return Match.new(:request, exp)
248
257
  else
249
258
  false
250
259
  end
@@ -253,10 +262,8 @@ class Brakeman::BaseCheck < SexpProcessor
253
262
  when :string_interp
254
263
  exp.each do |e|
255
264
  if sexp? e
256
- type, match = has_immediate_user_input?(e)
257
- if type
258
- return type, match
259
- end
265
+ match = has_immediate_user_input?(e)
266
+ return match if match
260
267
  end
261
268
  end
262
269
  false
@@ -265,10 +272,8 @@ class Brakeman::BaseCheck < SexpProcessor
265
272
  if exp[1].node_type == :rlist
266
273
  exp[1].each do |e|
267
274
  if sexp? e
268
- type, match = has_immediate_user_input?(e)
269
- if type
270
- return type, match
271
- end
275
+ match = has_immediate_user_input?(e)
276
+ return match if match
272
277
  end
273
278
  end
274
279
  false
@@ -38,12 +38,6 @@ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
38
38
 
39
39
  return false if args.nil? or not hash? args
40
40
 
41
- hash_iterate(args) do |k, v|
42
- if symbol? k and k[1] == :password
43
- return v
44
- end
45
- end
46
-
47
- nil
41
+ hash_access(args, :password)
48
42
  end
49
43
  end
@@ -83,11 +83,10 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
83
83
  out = exp[1][3][1]
84
84
  end
85
85
 
86
- type, match = has_immediate_user_input? out
87
-
88
- if type
86
+ if input = has_immediate_user_input?(out)
89
87
  add_result exp
90
- case type
88
+
89
+ case input.type
91
90
  when :params
92
91
  message = "Unescaped parameter value"
93
92
  when :cookies
@@ -101,8 +100,8 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
101
100
  warn :template => @current_template,
102
101
  :warning_type => "Cross Site Scripting",
103
102
  :message => message,
104
- :line => match.line,
105
- :code => match,
103
+ :line => input.match.line,
104
+ :code => input.match,
106
105
  :confidence => CONFIDENCE[:high]
107
106
 
108
107
  elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(out)
@@ -161,29 +160,35 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
161
160
  actually_process_call exp
162
161
  message = nil
163
162
 
164
- if @matched == :model and not tracker.options[:ignore_model_output]
165
- message = "Unescaped model attribute"
166
- elsif @matched == :params
167
- message = "Unescaped parameter value"
168
- elsif @matched == :cookies
169
- message = "Unescaped cookie value"
170
- end
171
-
172
- if message and not duplicate? exp
173
- add_result exp
174
-
175
- if exp[1].nil? and @known_dangerous.include? exp[2]
176
- confidence = CONFIDENCE[:high]
177
- else
178
- confidence = CONFIDENCE[:low]
163
+ if @matched
164
+ case @matched.type
165
+ when :model
166
+ unless tracker.options[:ignore_model_output]
167
+ message = "Unescaped model attribute"
168
+ end
169
+ when :params
170
+ message = "Unescaped parameter value"
171
+ when :cookies
172
+ message = "Unescaped cookie value"
179
173
  end
180
174
 
181
- warn :template => @current_template,
182
- :warning_type => "Cross Site Scripting",
183
- :message => message,
184
- :line => exp.line,
185
- :code => exp,
186
- :confidence => confidence
175
+ if message and not duplicate? exp
176
+ add_result exp
177
+
178
+ if exp[1].nil? and @known_dangerous.include? exp[2]
179
+ confidence = CONFIDENCE[:high]
180
+ else
181
+ confidence = CONFIDENCE[:low]
182
+ end
183
+
184
+ warn :template => @current_template,
185
+ :warning_type => "Cross Site Scripting",
186
+ :message => message,
187
+ :line => exp.line,
188
+ :code => exp,
189
+ :user_input => @matched.match,
190
+ :confidence => confidence
191
+ end
187
192
  end
188
193
 
189
194
  @mark = @matched = false
@@ -204,7 +209,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
204
209
 
205
210
  #Ignore safe items
206
211
  if (target.nil? and (@ignore_methods.include? method or method.to_s =~ IGNORE_LIKE)) or
207
- (@matched == :model and IGNORE_MODEL_METHODS.include? method) or
212
+ (@matched and @matched.type == :model and IGNORE_MODEL_METHODS.include? method) or
208
213
  (target == HAML_HELPERS and method == :html_escape) or
209
214
  ((target == URI or target == CGI) and method == :escape) or
210
215
  (target == XML_HELPER and method == :escape_xml) or
@@ -214,11 +219,11 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
214
219
  #exp[0] = :ignore #should not be necessary
215
220
  @matched = false
216
221
  elsif sexp? exp[1] and model_name? exp[1][1]
217
- @matched = :model
222
+ @matched = Match.new(:model, exp)
218
223
  elsif cookies? exp
219
- @matched = :cookies
224
+ @matched = Match.new(:cookies, exp)
220
225
  elsif @inspect_arguments and params? exp
221
- @matched = :params
226
+ @matched = Match.new(:params, exp)
222
227
  elsif @inspect_arguments
223
228
  process args
224
229
  end
@@ -226,13 +231,13 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
226
231
 
227
232
  #Note that params have been found
228
233
  def process_params exp
229
- @matched = :params
234
+ @matched = Match.new(:params, exp)
230
235
  exp
231
236
  end
232
237
 
233
238
  #Note that cookies have been found
234
239
  def process_cookies exp
235
- @matched = :cookies
240
+ @matched = Match.new(:cookies, exp)
236
241
  exp
237
242
  end
238
243
 
@@ -20,11 +20,12 @@ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
20
20
 
21
21
  #Warns if eval includes user input
22
22
  def process_result result
23
- if include_user_input? result[:call][-1]
23
+ if input = include_user_input?(result[:call][-1])
24
24
  warn :result => result,
25
25
  :warning_type => "Dangerous Eval",
26
26
  :message => "User input in eval",
27
27
  :code => result[:call],
28
+ :user_input => input.match,
28
29
  :confidence => CONFIDENCE[:high]
29
30
  end
30
31
  end
@@ -54,6 +54,7 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
54
54
  :message => "Possible command injection",
55
55
  :line => call.line,
56
56
  :code => call,
57
+ :user_input => failure.match,
57
58
  :confidence => confidence
58
59
  end
59
60
  end
@@ -75,16 +76,19 @@ class Brakeman::CheckExecute < Brakeman::BaseCheck
75
76
 
76
77
  exp = result[:call]
77
78
 
78
- if include_user_input? exp
79
+ if input = include_user_input?(exp)
79
80
  confidence = CONFIDENCE[:high]
81
+ user_input = input.match
80
82
  else
81
83
  confidence = CONFIDENCE[:med]
84
+ user_input = nil
82
85
  end
83
86
 
84
87
  warning = { :warning_type => "Command Injection",
85
88
  :message => "Possible command injection",
86
89
  :line => exp.line,
87
90
  :code => exp,
91
+ :user_input => user_input,
88
92
  :confidence => confidence }
89
93
 
90
94
  if result[:location][0] == :template
@@ -28,13 +28,14 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
28
28
 
29
29
  file_name = call[3][1]
30
30
 
31
- if check = include_user_input?(file_name)
31
+ if input = include_user_input?(file_name)
32
32
  unless duplicate? result
33
33
  add_result result
34
34
 
35
- if check == :params
35
+ case input.type
36
+ when :params
36
37
  message = "Parameter"
37
- elsif check == :cookies
38
+ when :cookies
38
39
  message = "Cookie"
39
40
  else
40
41
  message = "User input"
@@ -47,7 +48,8 @@ class Brakeman::CheckFileAccess < Brakeman::BaseCheck
47
48
  :message => message,
48
49
  :confidence => CONFIDENCE[:high],
49
50
  :line => call.line,
50
- :code => call
51
+ :code => call,
52
+ :user_input => input.match
51
53
  end
52
54
  end
53
55
  end
@@ -60,10 +60,9 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
60
60
 
61
61
  def check_argument result, exp
62
62
  arg = process exp
63
- type, match = has_immediate_user_input? arg
64
63
 
65
- if type
66
- case type
64
+ if input = has_immediate_user_input?(arg)
65
+ case input.type
67
66
  when :params
68
67
  message = "Unescaped parameter value in link_to"
69
68
  when :cookies
@@ -76,7 +75,9 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
76
75
  warn :result => result,
77
76
  :warning_type => "Cross Site Scripting",
78
77
  :message => message,
78
+ :user_input => input.match,
79
79
  :confidence => CONFIDENCE[:high]
80
+
80
81
  elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(arg)
81
82
  method = match[2]
82
83
 
@@ -92,13 +93,14 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
92
93
  warn :result => result,
93
94
  :warning_type => "Cross Site Scripting",
94
95
  :message => "Unescaped model attribute in link_to",
96
+ :user_input => match,
95
97
  :confidence => confidence
96
98
  end
97
99
 
98
100
  elsif @matched
99
- if @matched == :model and not tracker.options[:ignore_model_output]
101
+ if @matched.type == :model and not tracker.options[:ignore_model_output]
100
102
  message = "Unescaped model attribute in link_to"
101
- elsif @matched == :params
103
+ elsif @matched.type == :params
102
104
  message = "Unescaped parameter value in link_to"
103
105
  end
104
106
 
@@ -108,6 +110,7 @@ class Brakeman::CheckLinkTo < Brakeman::CheckCrossSiteScripting
108
110
  warn :result => result,
109
111
  :warning_type => "Cross Site Scripting",
110
112
  :message => message,
113
+ :user_input => @matched.match,
111
114
  :confidence => CONFIDENCE[:med]
112
115
  end
113
116
  end