brakeman-min 3.1.4 → 3.1.5.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES +30 -0
  3. data/README.md +3 -2
  4. data/lib/brakeman.rb +4 -4
  5. data/lib/brakeman/app_tree.rb +58 -5
  6. data/lib/brakeman/call_index.rb +22 -31
  7. data/lib/brakeman/checks.rb +59 -73
  8. data/lib/brakeman/checks/base_check.rb +13 -5
  9. data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
  10. data/lib/brakeman/checks/check_cross_site_scripting.rb +12 -6
  11. data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
  12. data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
  13. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
  14. data/lib/brakeman/checks/check_render.rb +33 -3
  15. data/lib/brakeman/checks/check_route_dos.rb +42 -0
  16. data/lib/brakeman/checks/check_sanitize_methods.rb +26 -4
  17. data/lib/brakeman/checks/check_sql.rb +8 -6
  18. data/lib/brakeman/checks/check_strip_tags.rb +27 -2
  19. data/lib/brakeman/options.rb +8 -2
  20. data/lib/brakeman/processors/alias_processor.rb +14 -1
  21. data/lib/brakeman/processors/base_processor.rb +8 -0
  22. data/lib/brakeman/processors/controller_processor.rb +2 -2
  23. data/lib/brakeman/processors/erb_template_processor.rb +1 -1
  24. data/lib/brakeman/processors/erubis_template_processor.rb +1 -1
  25. data/lib/brakeman/processors/haml_template_processor.rb +2 -1
  26. data/lib/brakeman/processors/lib/basic_processor.rb +16 -0
  27. data/lib/brakeman/processors/lib/find_all_calls.rb +4 -2
  28. data/lib/brakeman/processors/lib/find_call.rb +1 -1
  29. data/lib/brakeman/processors/lib/render_path.rb +2 -1
  30. data/lib/brakeman/processors/lib/route_helper.rb +4 -0
  31. data/lib/brakeman/processors/model_processor.rb +2 -2
  32. data/lib/brakeman/report/ignore/config.rb +3 -3
  33. data/lib/brakeman/report/report_csv.rb +1 -2
  34. data/lib/brakeman/report/report_json.rb +1 -4
  35. data/lib/brakeman/scanner.rb +7 -2
  36. data/lib/brakeman/tracker.rb +21 -0
  37. data/lib/brakeman/tracker/config.rb +6 -0
  38. data/lib/brakeman/util.rb +4 -3
  39. data/lib/brakeman/version.rb +1 -1
  40. data/lib/brakeman/warning.rb +2 -2
  41. data/lib/brakeman/warning_codes.rb +9 -0
  42. data/lib/ruby_parser/bm_sexp.rb +23 -23
  43. metadata +13 -30
  44. data/lib/brakeman/report/initializers/faster_csv.rb +0 -7
  45. data/lib/brakeman/report/initializers/multi_json.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 91f158b5a015d51042b31791edad0d8a79847a6a
4
- data.tar.gz: a18ce85d815f421ae093cc2a46b97b067fd244f1
3
+ metadata.gz: f7ed8514bb16d1d30d4f7b640dfbab79a6e8d034
4
+ data.tar.gz: 460ed07d68c8a3af9eb3b2f5034310b29bc4bda5
5
5
  SHA512:
6
- metadata.gz: a9a77a39c6fa4af234c92912517447cb712100a2fb88de86d8151e3ceb1cc666b29b9b885b44e8c14344a0924a7bbce593773519b42d14f53d4aa06b405b6c6d
7
- data.tar.gz: 516fb05b82d87ede5efb094d2efc8270df6d1da8f9228817c43182914f35718bce9e42d8fe88d23d4dcec8ded23586f089eadea1b4cbf25dd6b3f83ea7b1cc66
6
+ metadata.gz: 6c986dcecf527e256507f89e60d93fb72c02f6c534cf04ca30412e05a07a8f5364928d3b1886b0787efdfbf2952d057d6e7dc45db4f89e9fe437fa7aafeeddf9
7
+ data.tar.gz: 795321abc425629d277e8babc82d7d79e63ffe2bd0ab9da57f6e7451c3da1e6acfcd566932c78cd6f8c3c91ed69b787b93373a11e81d3dd6f6f9d97769eb3c1f
data/CHANGES CHANGED
@@ -1,3 +1,33 @@
1
+ # 3.2.0.pre1
2
+
3
+ * Support calls using `&.` operator
4
+ * Update ruby_parser dependency to 3.8.1
5
+ * Remove `fastercsv` dependency
6
+ * Fix finding calls with `targets: nil`
7
+ * Remove `multi-json` dependecy
8
+ * Handle CoffeeScript in HAML
9
+ * Avoid render warnings about params[:action]/params[:controller]
10
+ * Index calls in class bodies but outside methods
11
+
12
+ # 3.1.5
13
+
14
+ * Fix CodeClimate construction of --only-files (Will Fleming)
15
+ * Add check for denial of service via routes (CVE-2015-7581)
16
+ * Warn about RCE with `render params` (CVE-2016-0752)
17
+ * Add check for `strip_tags` XSS (CVE-2015-7579)
18
+ * Add check for `sanitize` XSS (CVE-2015-7578/80)
19
+ * Add check for `reject_if` proc bypass (CVE-2015-7577)
20
+ * Add check for mime-type denial of service (CVE-2016-0751)
21
+ * Add check for basic auth timing attack (CVE-2015-7576)
22
+ * Add initial Rails 5 support
23
+ * Check for implict integer comparison in dynamic finders
24
+ * Support directories better in --only-files and --skip-files (Patrick Toomey)
25
+ * Avoid warning about `permit` in SQL
26
+ * Handle guards using `detect`
27
+ * Avoid warning on user input in comparisons
28
+ * Handle module names with self methods
29
+ * Add session manipulation documentation
30
+
1
31
  # 3.1.4
2
32
 
3
33
  * Emit brakeman's native fingerprints for Code Climate engine (Noah Davis)
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![Build Status](https://travis-ci.org/presidentbeef/brakeman.svg?branch=master)](https://travis-ci.org/presidentbeef/brakeman)
4
4
  [![Code Climate](https://codeclimate.com/github/presidentbeef/brakeman/badges/gpa.svg)](https://codeclimate.com/github/presidentbeef/brakeman)
5
5
  [![Test Coverage](https://codeclimate.com/github/presidentbeef/brakeman/badges/coverage.svg)](https://codeclimate.com/github/presidentbeef/brakeman/coverage)
6
+ [![Gitter](https://badges.gitter.im/presidentbeef/brakeman.svg)](https://gitter.im/presidentbeef/brakeman)
6
7
 
7
8
  # Brakeman
8
9
 
@@ -82,9 +83,9 @@ By default, Brakeman will return 0 as an exit code unless something went very wr
82
83
 
83
84
  brakeman -z
84
85
 
85
- To skip certain files that Brakeman may have trouble parsing, use:
86
+ To skip certain files or directories that Brakeman may have trouble parsing, use:
86
87
 
87
- brakeman --skip-files file1,file2,etc
88
+ brakeman --skip-files file1,/path1/,path2/
88
89
 
89
90
  To compare results of a scan with a previous scan, use the JSON output option and then:
90
91
 
@@ -407,20 +407,20 @@ module Brakeman
407
407
 
408
408
  # Compare JSON ouptut from a previous scan and return the diff of the two scans
409
409
  def self.compare options
410
- require 'multi_json'
410
+ require 'json'
411
411
  require 'brakeman/differ'
412
412
  raise ArgumentError.new("Comparison file doesn't exist") unless File.exist? options[:previous_results_json]
413
413
 
414
414
  begin
415
- previous_results = MultiJson.load(File.read(options[:previous_results_json]), :symbolize_keys => true)[:warnings]
416
- rescue MultiJson::DecodeError
415
+ previous_results = JSON.parse(File.read(options[:previous_results_json]), :symbolize_names => true)[:warnings]
416
+ rescue JSON::ParserError
417
417
  self.notify "Error parsing comparison file: #{options[:previous_results_json]}"
418
418
  exit!
419
419
  end
420
420
 
421
421
  tracker = run(options)
422
422
 
423
- new_results = MultiJson.load(tracker.report.to_json, :symbolize_keys => true)[:warnings]
423
+ new_results = JSON.parse(tracker.report.to_json, :symbolize_names => true)[:warnings]
424
424
 
425
425
  Brakeman::Differ.new(new_results, previous_results).diff
426
426
  end
@@ -1,3 +1,5 @@
1
+ require 'pathname'
2
+
1
3
  module Brakeman
2
4
  class AppTree
3
5
  VIEW_EXTENSIONS = %w[html.erb html.haml rhtml js.erb html.slim].join(",")
@@ -10,15 +12,45 @@ module Brakeman
10
12
  # Convert files into Regexp for matching
11
13
  init_options = {}
12
14
  if options[:skip_files]
13
- init_options[:skip_files] = Regexp.new("(?:" << options[:skip_files].map { |f| Regexp.escape f }.join("|") << ")$")
15
+ init_options[:skip_files] = regex_for_paths(options[:skip_files])
14
16
  end
17
+
15
18
  if options[:only_files]
16
- init_options[:only_files] = Regexp.new("(?:" << options[:only_files].map { |f| Regexp.escape f }.join("|") << ")")
19
+ init_options[:only_files] = regex_for_paths(options[:only_files])
17
20
  end
18
21
  init_options[:additional_libs_path] = options[:additional_libs_path]
19
22
  new(root, init_options)
20
23
  end
21
24
 
25
+ # Accepts an array of filenames and paths with the following format and
26
+ # returns a Regexp to match them:
27
+ # * "path1/file1.rb" - Matches a specific filename in the project directory.
28
+ # * "path1/" - Matches any path that conatains "path1" in the project directory.
29
+ # * "/path1/ - Matches any path that is rooted at "path1" in the project directory.
30
+ #
31
+ def self.regex_for_paths(paths)
32
+ path_regexes = paths.map do |f|
33
+ # If path ends in a file separator then we assume it is a path rather
34
+ # than a filename.
35
+ if f.end_with?(File::SEPARATOR)
36
+ # If path starts with a file separator then we assume that they
37
+ # want the project relative path to start with this path prefix.
38
+ if f.start_with?(File::SEPARATOR)
39
+ "\\A#{Regexp.escape f}"
40
+ # If it ends in a file separator, but does not begin with a file
41
+ # separator then we assume the path can match any path component in
42
+ # the project.
43
+ else
44
+ Regexp.escape f
45
+ end
46
+ else
47
+ "#{Regexp.escape f}\\z"
48
+ end
49
+ end
50
+ Regexp.new("(?:" << path_regexes.join("|") << ")")
51
+ end
52
+ private_class_method(:regex_for_paths)
53
+
22
54
  def initialize(root, init_options = {})
23
55
  @root = root
24
56
  @skip_files = init_options[:skip_files]
@@ -96,13 +128,34 @@ module Brakeman
96
128
 
97
129
  def select_only_files(paths)
98
130
  return paths unless @only_files
99
- paths.select { |f| @only_files.match f }
131
+ project_root = Pathname.new(@root)
132
+ paths.select do |path|
133
+ absolute_path = Pathname.new(path)
134
+ # relative root never has a leading separator. But, we use a leading
135
+ # separator in a @skip_files entry to imply that a directory is
136
+ # "absolute" with respect to the project directory.
137
+ project_relative_path = File.join(
138
+ File::SEPARATOR,
139
+ absolute_path.relative_path_from(project_root).to_s
140
+ )
141
+ @only_files.match(project_relative_path)
142
+ end
100
143
  end
101
144
 
102
145
  def reject_skipped_files(paths)
103
146
  return paths unless @skip_files
104
- paths.reject { |f| @skip_files.match f }
147
+ project_root = Pathname.new(@root)
148
+ paths.reject do |path|
149
+ absolute_path = Pathname.new(path)
150
+ # relative root never has a leading separator. But, we use a leading
151
+ # separator in a @skip_files entry to imply that a directory is
152
+ # "absolute" with respect to the project directory.
153
+ project_relative_path = File.join(
154
+ File::SEPARATOR,
155
+ absolute_path.relative_path_from(project_root).to_s
156
+ )
157
+ @skip_files.match(project_relative_path)
158
+ end
105
159
  end
106
-
107
160
  end
108
161
  end
@@ -5,8 +5,8 @@ class Brakeman::CallIndex
5
5
 
6
6
  #Initialize index with calls from FindAllCalls
7
7
  def initialize calls
8
- @calls_by_method = Hash.new
9
- @calls_by_target = Hash.new
8
+ @calls_by_method = Hash.new { |h, k| h[k] = [] }
9
+ @calls_by_target = Hash.new { |h, k| h[k] = [] }
10
10
 
11
11
  index_calls calls
12
12
  end
@@ -45,7 +45,7 @@ class Brakeman::CallIndex
45
45
 
46
46
  #Find calls with no explicit target
47
47
  #with either :target => nil or :target => false
48
- elsif options.key? :target and not target and method
48
+ elsif (options.key? :target or options.key? :targets) and not target and method
49
49
  calls = calls_by_method method
50
50
  calls = filter_by_target calls, nil
51
51
 
@@ -66,44 +66,35 @@ class Brakeman::CallIndex
66
66
  end
67
67
 
68
68
  def remove_template_indexes template_name = nil
69
- @calls_by_method.each do |name, calls|
70
- calls.delete_if do |call|
71
- from_template call, template_name
72
- end
73
- end
74
-
75
- @calls_by_target.each do |name, calls|
76
- calls.delete_if do |call|
77
- from_template call, template_name
69
+ [@calls_by_method, @calls_by_target].each do |calls_by|
70
+ calls_by.each do |name, calls|
71
+ calls.delete_if do |call|
72
+ from_template call, template_name
73
+ end
78
74
  end
79
75
  end
80
76
  end
81
77
 
82
78
  def remove_indexes_by_class classes
83
- @calls_by_method.each do |name, calls|
84
- calls.delete_if do |call|
85
- call[:location][:type] == :class and classes.include? call[:location][:class]
86
- end
87
- end
88
-
89
- @calls_by_target.each do |name, calls|
90
- calls.delete_if do |call|
91
- call[:location][:type] == :class and classes.include? call[:location][:class]
79
+ [@calls_by_method, @calls_by_target].each do |calls_by|
80
+ calls_by.each do |name, calls|
81
+ calls.delete_if do |call|
82
+ call[:location][:type] == :class and classes.include? call[:location][:class]
83
+ end
92
84
  end
93
85
  end
94
86
  end
95
87
 
96
88
  def index_calls calls
97
89
  calls.each do |call|
98
- @calls_by_method[call[:method]] ||= []
99
90
  @calls_by_method[call[:method]] << call
100
91
 
101
- if not call[:target].is_a? Sexp
102
- @calls_by_target[call[:target]] ||= []
103
- @calls_by_target[call[:target]] << call
104
- elsif call[:target].node_type == :params or call[:target].node_type == :session
105
- @calls_by_target[call[:target].node_type] ||= []
106
- @calls_by_target[call[:target].node_type] << call
92
+ target = call[:target]
93
+
94
+ if not target.is_a? Sexp
95
+ @calls_by_target[target] << call
96
+ elsif target.node_type == :params or target.node_type == :session
97
+ @calls_by_target[target.node_type] << call
107
98
  end
108
99
  end
109
100
  end
@@ -115,7 +106,7 @@ class Brakeman::CallIndex
115
106
  method = options[:method] || options[:methods]
116
107
 
117
108
  calls = calls_by_method method
118
-
109
+
119
110
  return [] if calls.nil?
120
111
 
121
112
  calls = filter_by_chain calls, target
@@ -145,7 +136,7 @@ class Brakeman::CallIndex
145
136
  elsif method.is_a? Regexp
146
137
  calls_by_methods_regex method
147
138
  else
148
- @calls_by_method[method.to_sym] || []
139
+ @calls_by_method[method.to_sym]
149
140
  end
150
141
  end
151
142
 
@@ -169,7 +160,7 @@ class Brakeman::CallIndex
169
160
  end
170
161
 
171
162
  def calls_with_no_target
172
- @calls_by_target[nil] || []
163
+ @calls_by_target[nil]
173
164
  end
174
165
 
175
166
  def filter calls, key, value
@@ -93,91 +93,49 @@ class Brakeman::Checks
93
93
  #Run all the checks on the given Tracker.
94
94
  #Returns a new instance of Checks with the results.
95
95
  def self.run_checks(app_tree, tracker)
96
- if tracker.options[:parallel_checks]
97
- self.run_checks_parallel(app_tree, tracker)
98
- else
99
- self.run_checks_sequential(app_tree, tracker)
100
- end
101
- end
102
-
103
- #Run checks sequentially
104
- def self.run_checks_sequential(app_tree, tracker)
96
+ checks = self.checks_to_run(tracker)
105
97
  check_runner = self.new :min_confidence => tracker.options[:min_confidence]
106
-
107
- self.checks_to_run(tracker).each do |c|
108
- check_name = get_check_name c
109
-
110
- #Run or don't run check based on options
111
- unless tracker.options[:skip_checks].include? check_name or
112
- (tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
113
-
114
- Brakeman.notify " - #{check_name}"
115
-
116
- check = c.new(app_tree, tracker)
117
-
118
- begin
119
- check.run_check
120
- rescue => e
121
- tracker.error e
122
- end
123
-
124
- check.warnings.each do |w|
125
- check_runner.add_warning w
126
- end
127
-
128
- #Maintain list of which checks were run
129
- #mainly for reporting purposes
130
- check_runner.checks_run << check_name[5..-1]
131
- end
132
- end
133
-
134
- check_runner
98
+ self.actually_run_checks(checks, check_runner, app_tree, tracker)
135
99
  end
136
100
 
137
- #Run checks in parallel threads
138
- def self.run_checks_parallel(app_tree, tracker)
139
- threads = []
101
+ def self.actually_run_checks(checks, check_runner, app_tree, tracker)
102
+ threads = [] # Results for parallel
103
+ results = [] # Results for sequential
104
+ parallel = tracker.options[:parallel_checks]
140
105
  error_mutex = Mutex.new
141
106
 
142
- check_runner = self.new :min_confidence => tracker.options[:min_confidence]
143
-
144
- self.checks_to_run(tracker).each do |c|
107
+ checks.each do |c|
145
108
  check_name = get_check_name c
109
+ Brakeman.notify " - #{check_name}"
146
110
 
147
- #Run or don't run check based on options
148
- unless tracker.options[:skip_checks].include? check_name or
149
- (tracker.options[:run_checks] and not tracker.options[:run_checks].include? check_name)
150
-
151
- Brakeman.notify " - #{check_name}"
152
-
111
+ if parallel
153
112
  threads << Thread.new do
154
- check = c.new(app_tree, tracker)
155
-
156
- begin
157
- check.run_check
158
- rescue => e
159
- error_mutex.synchronize do
160
- tracker.error e
161
- end
162
- end
163
-
164
- check.warnings
113
+ self.run_a_check(c, error_mutex, app_tree, tracker)
165
114
  end
166
-
167
- #Maintain list of which checks were run
168
- #mainly for reporting purposes
169
- check_runner.checks_run << check_name[5..-1]
115
+ else
116
+ results << self.run_a_check(c, error_mutex, app_tree, tracker)
170
117
  end
118
+
119
+ #Maintain list of which checks were run
120
+ #mainly for reporting purposes
121
+ check_runner.checks_run << check_name[5..-1]
171
122
  end
172
123
 
173
124
  threads.each { |t| t.join }
174
125
 
175
126
  Brakeman.notify "Checks finished, collecting results..."
176
127
 
177
- #Collect results
178
- threads.each do |thread|
179
- thread.value.each do |warning|
180
- check_runner.add_warning warning
128
+ if parallel
129
+ threads.each do |thread|
130
+ thread.value.each do |warning|
131
+ check_runner.add_warning warning
132
+ end
133
+ end
134
+ else
135
+ results.each do |warnings|
136
+ warnings.each do |warning|
137
+ check_runner.add_warning warning
138
+ end
181
139
  end
182
140
  end
183
141
 
@@ -191,11 +149,39 @@ class Brakeman::Checks
191
149
  end
192
150
 
193
151
  def self.checks_to_run tracker
194
- if tracker.options[:run_all_checks] or tracker.options[:run_checks]
195
- @checks + @optional_checks
196
- else
197
- @checks
152
+ to_run = if tracker.options[:run_all_checks] or tracker.options[:run_checks]
153
+ @checks + @optional_checks
154
+ else
155
+ @checks
156
+ end
157
+
158
+ self.filter_checks to_run, tracker
159
+ end
160
+
161
+ def self.filter_checks checks, tracker
162
+ skipped = tracker.options[:skip_checks]
163
+ explicit = tracker.options[:run_checks]
164
+
165
+ checks.reject do |c|
166
+ check_name = self.get_check_name(c)
167
+
168
+ skipped.include? check_name or
169
+ (explicit and not explicit.include? check_name)
170
+ end
171
+ end
172
+
173
+ def self.run_a_check klass, mutex, app_tree, tracker
174
+ check = klass.new(app_tree, tracker)
175
+
176
+ begin
177
+ check.run_check
178
+ rescue => e
179
+ mutex.synchronize do
180
+ tracker.error e
181
+ end
198
182
  end
183
+
184
+ check.warnings
199
185
  end
200
186
  end
201
187