brakeman 0.9.2 → 1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/bin/brakeman +20 -5
  2. data/lib/brakeman.rb +54 -14
  3. data/lib/brakeman/call_index.rb +204 -0
  4. data/lib/{checks.rb → brakeman/checks.rb} +27 -12
  5. data/lib/{checks → brakeman/checks}/base_check.rb +36 -15
  6. data/lib/{checks → brakeman/checks}/check_basic_auth.rb +3 -3
  7. data/lib/{checks → brakeman/checks}/check_cross_site_scripting.rb +22 -19
  8. data/lib/{checks → brakeman/checks}/check_default_routes.rb +7 -5
  9. data/lib/{checks → brakeman/checks}/check_escape_function.rb +3 -4
  10. data/lib/{checks → brakeman/checks}/check_evaluation.rb +8 -6
  11. data/lib/{checks → brakeman/checks}/check_execute.rb +14 -14
  12. data/lib/brakeman/checks/check_file_access.rb +52 -0
  13. data/lib/{checks → brakeman/checks}/check_filter_skipping.rb +3 -4
  14. data/lib/{checks → brakeman/checks}/check_forgery_setting.rb +3 -3
  15. data/lib/{checks → brakeman/checks}/check_mail_to.rb +7 -6
  16. data/lib/{checks → brakeman/checks}/check_mass_assignment.rb +27 -7
  17. data/lib/{checks → brakeman/checks}/check_model_attributes.rb +7 -7
  18. data/lib/{checks → brakeman/checks}/check_nested_attributes.rb +3 -4
  19. data/lib/{checks → brakeman/checks}/check_quote_table_name.rb +6 -5
  20. data/lib/{checks → brakeman/checks}/check_redirect.rb +31 -15
  21. data/lib/{checks → brakeman/checks}/check_render.rb +5 -3
  22. data/lib/{checks → brakeman/checks}/check_response_splitting.rb +3 -4
  23. data/lib/brakeman/checks/check_send_file.rb +17 -0
  24. data/lib/{checks → brakeman/checks}/check_session_settings.rb +16 -12
  25. data/lib/{checks → brakeman/checks}/check_sql.rb +29 -17
  26. data/lib/{checks → brakeman/checks}/check_strip_tags.rb +6 -5
  27. data/lib/{checks → brakeman/checks}/check_translate_bug.rb +7 -6
  28. data/lib/{checks → brakeman/checks}/check_validation_regex.rb +3 -3
  29. data/lib/{checks → brakeman/checks}/check_without_protection.rb +8 -8
  30. data/lib/brakeman/processor.rb +93 -0
  31. data/lib/{processors → brakeman/processors}/alias_processor.rb +9 -7
  32. data/lib/{processors → brakeman/processors}/base_processor.rb +5 -5
  33. data/lib/brakeman/processors/config_processor.rb +14 -0
  34. data/lib/{processors → brakeman/processors}/controller_alias_processor.rb +8 -8
  35. data/lib/{processors → brakeman/processors}/controller_processor.rb +6 -6
  36. data/lib/{processors → brakeman/processors}/erb_template_processor.rb +2 -2
  37. data/lib/{processors → brakeman/processors}/erubis_template_processor.rb +2 -3
  38. data/lib/{processors → brakeman/processors}/gem_processor.rb +3 -2
  39. data/lib/{processors → brakeman/processors}/haml_template_processor.rb +3 -3
  40. data/lib/brakeman/processors/lib/find_all_calls.rb +105 -0
  41. data/lib/{processors → brakeman/processors}/lib/find_call.rb +4 -4
  42. data/lib/{processors → brakeman/processors}/lib/processor_helper.rb +1 -1
  43. data/lib/{processors → brakeman/processors}/lib/rails2_config_processor.rb +10 -13
  44. data/lib/{processors → brakeman/processors}/lib/rails2_route_processor.rb +6 -5
  45. data/lib/{processors → brakeman/processors}/lib/rails3_config_processor.rb +7 -10
  46. data/lib/{processors → brakeman/processors}/lib/rails3_route_processor.rb +13 -5
  47. data/lib/{processors → brakeman/processors}/lib/render_helper.rb +12 -4
  48. data/lib/{processors → brakeman/processors}/lib/route_helper.rb +1 -1
  49. data/lib/{processors → brakeman/processors}/library_processor.rb +4 -4
  50. data/lib/{processors → brakeman/processors}/model_processor.rb +3 -3
  51. data/lib/{processors → brakeman/processors}/output_processor.rb +5 -6
  52. data/lib/{processors → brakeman/processors}/params_processor.rb +1 -2
  53. data/lib/brakeman/processors/route_processor.rb +17 -0
  54. data/lib/{processors → brakeman/processors}/template_alias_processor.rb +5 -5
  55. data/lib/{processors → brakeman/processors}/template_processor.rb +2 -2
  56. data/lib/{report.rb → brakeman/report.rb} +54 -48
  57. data/lib/{scanner.rb → brakeman/scanner.rb} +26 -17
  58. data/lib/{tracker.rb → brakeman/tracker.rb} +47 -38
  59. data/lib/{util.rb → brakeman/util.rb} +16 -1
  60. data/lib/brakeman/version.rb +3 -0
  61. data/lib/{warning.rb → brakeman/warning.rb} +9 -9
  62. metadata +81 -69
  63. data/lib/checks/check_file_access.rb +0 -48
  64. data/lib/checks/check_send_file.rb +0 -15
  65. data/lib/format/style.css +0 -105
  66. data/lib/processor.rb +0 -91
  67. data/lib/processors/config_processor.rb +0 -5
  68. data/lib/processors/lib/find_model_call.rb +0 -43
  69. data/lib/processors/route_processor.rb +0 -11
  70. data/lib/version.rb +0 -1
@@ -4,14 +4,16 @@ require 'set'
4
4
 
5
5
  $:.unshift "#{File.expand_path(File.dirname(__FILE__))}/../lib"
6
6
 
7
- require 'version'
8
7
  require 'brakeman'
8
+ require 'brakeman/version'
9
9
 
10
10
  trap("INT") do
11
11
  $stderr.puts "\nInterrupted - exiting."
12
12
  exit!
13
13
  end
14
14
 
15
+ Brakeman::Warnings_Found_Exit_Code = 3
16
+
15
17
  #Parse command line options
16
18
  options = {}
17
19
 
@@ -28,10 +30,13 @@ OptionParser.new do |opts|
28
30
 
29
31
  opts.on "-q", "--quiet", "Suppress informational messages" do
30
32
  options[:quiet] = true
31
- $VERBOSE = nil
32
33
  end
33
34
 
34
- opts.on "-3", "--rails3", "[Experimental] Rails 3 support" do
35
+ opts.on( "-z", "--exit-on-warn", "Exit code is non-zero if warnings found") do |s|
36
+ options[:exit_on_warn] = s
37
+ end
38
+
39
+ opts.on "-3", "--rails3", "[Experimental] Force Rails 3 mode" do
35
40
  options[:rails3] = true
36
41
  end
37
42
 
@@ -101,7 +106,7 @@ OptionParser.new do |opts|
101
106
  options[:output_format] = ("to_" << type.to_s).to_sym
102
107
  end
103
108
 
104
- opts.on "--css-file CSSFile" do |file|
109
+ opts.on "--css-file CSSFile", "Specify CSS to use for HTML output" do |file|
105
110
  options[:html_style] = File.expand_path file
106
111
  end
107
112
 
@@ -154,10 +159,20 @@ OptionParser.new do |opts|
154
159
  options[:list_checks] = true
155
160
  end
156
161
 
162
+ opts.on "-v", "--version", "Show Brakeman version" do
163
+ puts "brakeman #{Brakeman::Version}"
164
+ exit
165
+ end
166
+
157
167
  opts.on_tail "-h", "--help", "Display this message" do
158
168
  puts opts
159
169
  exit
160
170
  end
161
171
  end.parse!(ARGV)
162
172
 
163
- Brakeman.run options
173
+ clean = Brakeman.run options
174
+
175
+ if options[:exit_on_warn] && !clean
176
+ exit Brakeman::Warnings_Found_Exit_Code
177
+ end
178
+
@@ -1,8 +1,38 @@
1
+ require 'rubygems'
1
2
  require 'yaml'
2
-
3
- OPTIONS = {}
3
+ require 'set'
4
4
 
5
5
  module Brakeman
6
+
7
+ #Run Brakeman scan. Returns Tracker object.
8
+ #
9
+ #Options:
10
+ #
11
+ # * :app_path - path to root of Rails app (required)
12
+ # * :assume_all_routes - assume all methods are routes (default: false)
13
+ # * :check_arguments - check arguments of methods (default: true)
14
+ # * :collapse_mass_assignment - report unprotected models in single warning (default: true)
15
+ # * :combine_locations - combine warning locations (default: true)
16
+ # * :config_file - configuration file
17
+ # * :create_config - output configuration file
18
+ # * :escape_html - escape HTML by default (automatic)
19
+ # * :exit_on_warn - return error exit code on warnings (default: false)
20
+ # * :html_style - path to CSS file
21
+ # * :ignore_model_output - consider models safe (default: false)
22
+ # * :list_checks - list all checks (does not run scan)
23
+ # * :message_limit - limit length of messages
24
+ # * :min_confidence - minimum confidence (0-2, 0 is highest)
25
+ # * :output_file - file for output
26
+ # * :output_format - format for output (:to_s, :to_tabs, :to_csv, :to_html)
27
+ # * :parallel_checks - run checks in parallel (default: true)
28
+ # * :quiet - suppress most messages (default: false)
29
+ # * :rails3 - force Rails 3 mode (automatic)
30
+ # * :report_routes - show found routes on controllers (default: false)
31
+ # * :run_checks - array of checks to run (run all if not specified)
32
+ # * :safe_methods - array of methods to consider safe
33
+ # * :skip_libs - do not process lib/ directory (default: false)
34
+ # * :skip_checks - checks not to run (run all if not specified)
35
+ #
6
36
  def self.run options
7
37
  if options[:list_checks]
8
38
  list_checks
@@ -14,6 +44,10 @@ module Brakeman
14
44
  exit
15
45
  end
16
46
 
47
+ if options[:quiet]
48
+ $VERBOSE = nil
49
+ end
50
+
17
51
  scan set_options(options)
18
52
  end
19
53
 
@@ -82,7 +116,7 @@ module Brakeman
82
116
  :ignore_model_output => false,
83
117
  :message_limit => 100,
84
118
  :parallel_checks => true,
85
- :html_style => "#{File.expand_path(File.dirname(__FILE__))}/../lib/format/style.css"
119
+ :html_style => "#{File.expand_path(File.dirname(__FILE__))}/brakeman/format/style.css"
86
120
  }
87
121
  end
88
122
 
@@ -118,7 +152,7 @@ module Brakeman
118
152
  end
119
153
 
120
154
  def self.list_checks
121
- require 'scanner'
155
+ require 'brakeman/scanner'
122
156
  $stderr.puts "Available Checks:"
123
157
  $stderr.puts "-" * 30
124
158
  $stderr.puts Checks.checks.map { |c| c.to_s }.sort.join "\n"
@@ -151,20 +185,17 @@ module Brakeman
151
185
  end
152
186
 
153
187
  def self.scan options
154
- OPTIONS.clear
155
- OPTIONS.merge! options
156
-
157
188
  #Load scanner
158
189
  warn "Loading scanner..."
159
190
 
160
191
  begin
161
- require 'scanner'
192
+ require 'brakeman/scanner'
162
193
  rescue LoadError
163
194
  abort "Cannot find lib/ directory."
164
195
  end
165
196
 
166
197
  #Start scanning
167
- scanner = Scanner.new options[:app_path]
198
+ scanner = Scanner.new options
168
199
 
169
200
  warn "[Notice] Using Ruby #{RUBY_VERSION}. Please make sure this matches the one used to run your Rails application."
170
201
 
@@ -175,13 +206,22 @@ module Brakeman
175
206
  tracker.run_checks
176
207
 
177
208
  warn "Generating report..."
178
- if OPTIONS[:output_file]
179
- File.open OPTIONS[:output_file], "w" do |f|
180
- f.puts tracker.report.send(OPTIONS[:output_format])
209
+ if options[:output_file]
210
+ File.open options[:output_file], "w" do |f|
211
+ f.puts tracker.report.send(options[:output_format])
181
212
  end
182
- warn "Report saved in '#{OPTIONS[:output_file]}'"
213
+ warn "Report saved in '#{options[:output_file]}'"
183
214
  else
184
- puts tracker.report.send(OPTIONS[:output_format])
215
+ puts tracker.report.send(options[:output_format])
185
216
  end
217
+
218
+ if options[:exit_on_warn]
219
+ tracker.checks.all_warnings.each do |warning|
220
+ next if warning.confidence > options[:min_confidence]
221
+ return false
222
+ end
223
+ end
224
+ return true
225
+
186
226
  end
187
227
  end
@@ -0,0 +1,204 @@
1
+ require 'set'
2
+
3
+ #Stores call sites to look up later.
4
+ class Brakeman::CallIndex
5
+
6
+ #Initialize index with calls from FindAllCalls
7
+ def initialize calls
8
+ @calls_by_method = Hash.new { |h,k| h[k] = [] }
9
+ @calls_by_target = Hash.new { |h,k| h[k] = [] }
10
+ @methods = Set.new
11
+ @targets = Set.new
12
+
13
+ index_calls calls
14
+ end
15
+
16
+ #Find calls matching specified option hash.
17
+ #
18
+ #Options:
19
+ #
20
+ # * :target - symbol, array of symbols, or regular expression to match target(s)
21
+ # * :method - symbol, array of symbols, or regular expression to match method(s)
22
+ # * :chained - boolean, whether or not to match against a whole method chain (false by default)
23
+ # * :nested - boolean, whether or not to match against a method call that is a target itself (false by default)
24
+ def find_calls options
25
+ target = options[:target] || options[:targets]
26
+ method = options[:method] || options[:methods]
27
+ nested = options[:nested]
28
+
29
+ if options[:chained]
30
+ return find_chain options
31
+ #Find by narrowest category
32
+ elsif target and method and target.is_a? Array and method.is_a? Array
33
+ if target.length > method.length
34
+ calls = filter_by_target calls_by_methods(method), target
35
+ else
36
+ calls = calls_by_targets(target)
37
+ calls = filter_by_method calls, method
38
+ end
39
+
40
+ #Find by target, then by methods, if provided
41
+ elsif target
42
+ calls = calls_by_target target
43
+
44
+ if calls and method
45
+ calls = filter_by_method calls, method
46
+ end
47
+
48
+ #Find calls with no explicit target
49
+ #with either :target => nil or :target => false
50
+ elsif options.key? :target and not target and method
51
+ calls = calls_by_method method
52
+ calls = filter_by_target calls, nil
53
+
54
+ #Find calls by method
55
+ elsif method
56
+ calls = calls_by_method method
57
+ else
58
+ warn "Invalid arguments to CallCache#find_calls: #{options.inspect}"
59
+ end
60
+
61
+ return [] if calls.nil?
62
+
63
+ #Remove calls that are actually targets of other calls
64
+ #Unless those are explicitly desired
65
+ calls = filter_nested calls unless nested
66
+
67
+ calls
68
+ end
69
+
70
+ private
71
+
72
+ def index_calls calls
73
+ calls.each do |call|
74
+ @methods << call[:method].to_s
75
+ @targets << call[:target].to_s
76
+ @calls_by_method[call[:method]] << call
77
+ @calls_by_target[call[:target]] << call
78
+ end
79
+ end
80
+
81
+ def find_chain options
82
+ target = options[:target] || options[:targets]
83
+ method = options[:method] || options[:methods]
84
+
85
+ calls = calls_by_method method
86
+
87
+ return [] if calls.nil?
88
+
89
+ calls = filter_by_chain calls, target
90
+ end
91
+
92
+ def calls_by_target target
93
+ if target.is_a? Array
94
+ calls_by_targets target
95
+ elsif target.is_a? Regexp
96
+ targets = @targets.select do |t|
97
+ t.match target
98
+ end
99
+
100
+ if targets.empty?
101
+ []
102
+ elsif targets.length > 1
103
+ calls_by_targets targets
104
+ else
105
+ calls_by_target[targets.first]
106
+ end
107
+ else
108
+ @calls_by_target[target]
109
+ end
110
+ end
111
+
112
+ def calls_by_targets targets
113
+ calls = []
114
+
115
+ targets.each do |target|
116
+ calls.concat @calls_by_target[target] if @calls_by_target.key? target
117
+ end
118
+
119
+ calls
120
+ end
121
+
122
+ def calls_by_method method
123
+ if method.is_a? Array
124
+ calls_by_methods method
125
+ elsif method.is_a? Regexp
126
+ methods = @methods.select do |m|
127
+ m.match method
128
+ end
129
+
130
+ if methods.empty?
131
+ []
132
+ elsif methods.length > 1
133
+ calls_by_methods methods
134
+ else
135
+ @calls_by_method[methods.first.to_sym]
136
+ end
137
+ else
138
+ @calls_by_method[method.to_sym]
139
+ end
140
+ end
141
+
142
+ def calls_by_methods methods
143
+ methods = methods.map { |m| m.to_sym }
144
+ calls = []
145
+
146
+ methods.each do |method|
147
+ calls.concat @calls_by_method[method] if @calls_by_method.key? method
148
+ end
149
+
150
+ calls
151
+ end
152
+
153
+ def calls_with_no_target
154
+ @calls_by_target[nil]
155
+ end
156
+
157
+ def filter calls, key, value
158
+ if value.is_a? Array
159
+ values = Set.new value
160
+
161
+ calls.select do |call|
162
+ values.include? call[key]
163
+ end
164
+ elsif value.is_a? Regexp
165
+ calls.select do |call|
166
+ call[key].to_s.match value
167
+ end
168
+ else
169
+ calls.select do |call|
170
+ call[key] == value
171
+ end
172
+ end
173
+ end
174
+
175
+ def filter_by_method calls, method
176
+ filter calls, :method, method
177
+ end
178
+
179
+ def filter_by_target calls, target
180
+ filter calls, :target, target
181
+ end
182
+
183
+ def filter_nested calls
184
+ filter calls, :nested, false
185
+ end
186
+
187
+ def filter_by_chain calls, target
188
+ if target.is_a? Array
189
+ targets = Set.new target
190
+
191
+ calls.select do |call|
192
+ targets.include? call[:chain].first
193
+ end
194
+ elsif target.is_a? Regexp
195
+ calls.select do |call|
196
+ call[:chain].first.to_s.match target
197
+ end
198
+ else
199
+ calls.select do |call|
200
+ call[:chain].first == target
201
+ end
202
+ end
203
+ end
204
+ end
@@ -5,7 +5,7 @@ require 'thread'
5
5
  #Checks can be added with +Check.add(check_class)+
6
6
  #
7
7
  #All .rb files in checks/ will be loaded.
8
- class Checks
8
+ class Brakeman::Checks
9
9
  @checks = []
10
10
 
11
11
  attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
@@ -46,10 +46,15 @@ class Checks
46
46
  end
47
47
  end
48
48
 
49
+ #Return an array of all warnings found.
50
+ def all_warnings
51
+ @warnings + @template_warnings + @controller_warnings + @model_warnings
52
+ end
53
+
49
54
  #Run all the checks on the given Tracker.
50
55
  #Returns a new instance of Checks with the results.
51
56
  def self.run_checks tracker
52
- if OPTIONS[:parallel_checks]
57
+ if tracker.options[:parallel_checks]
53
58
  self.run_checks_parallel tracker
54
59
  else
55
60
  self.run_checks_sequential tracker
@@ -61,11 +66,13 @@ class Checks
61
66
  check_runner = self.new
62
67
 
63
68
  @checks.each do |c|
69
+ check_name = get_check_name c
70
+
64
71
  #Run or don't run check based on options
65
- unless OPTIONS[:skip_checks].include? c.to_s or
66
- (OPTIONS[:run_checks] and not OPTIONS[:run_checks].include? c.to_s)
72
+ unless tracker.options[:skip_checks].include? check_name or
73
+ (tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
67
74
 
68
- warn " - #{c}"
75
+ warn " - #{check_name}"
69
76
 
70
77
  check = c.new(tracker)
71
78
  check.run_check
@@ -76,7 +83,7 @@ class Checks
76
83
 
77
84
  #Maintain list of which checks were run
78
85
  #mainly for reporting purposes
79
- check_runner.checks_run << c.to_s[5..-1]
86
+ check_runner.checks_run << check_name[5..-1]
80
87
  end
81
88
  end
82
89
 
@@ -90,11 +97,13 @@ class Checks
90
97
  check_runner = self.new
91
98
 
92
99
  @checks.each do |c|
100
+ check_name = get_check_name c
101
+
93
102
  #Run or don't run check based on options
94
- unless OPTIONS[:skip_checks].include? c.to_s or
95
- (OPTIONS[:run_checks] and not OPTIONS[:run_checks].include? c.to_s)
103
+ unless tracker.options[:skip_checks].include? check_name or
104
+ (tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
96
105
 
97
- warn " - #{c}"
106
+ warn " - #{check_name}"
98
107
 
99
108
  threads << Thread.new do
100
109
  begin
@@ -102,14 +111,14 @@ class Checks
102
111
  check.run_check
103
112
  check.warnings
104
113
  rescue Exception => e
105
- warn "[#{c.to_s}] #{e}"
114
+ warn "[#{check_name}] #{e}"
106
115
  []
107
116
  end
108
117
  end
109
118
 
110
119
  #Maintain list of which checks were run
111
120
  #mainly for reporting purposes
112
- check_runner.checks_run << c.to_s[5..-1]
121
+ check_runner.checks_run << check_name[5..-1]
113
122
  end
114
123
  end
115
124
 
@@ -126,9 +135,15 @@ class Checks
126
135
 
127
136
  check_runner
128
137
  end
138
+
139
+ private
140
+
141
+ def self.get_check_name check_class
142
+ check_class.to_s.split("::").last
143
+ end
129
144
  end
130
145
 
131
146
  #Load all files in checks/ directory
132
147
  Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
133
- require f.match(/(checks\/.*)\.rb$/)[0]
148
+ require f.match(/(brakeman\/checks\/.*)\.rb$/)[0]
134
149
  end