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