brakeman 3.1.4 → 3.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2f73f15176bd0a6f4d9dfcc629f2c058b30d837
4
- data.tar.gz: b3eb152ea1d579034ccae53d8c0ae2a5765533af
3
+ metadata.gz: 449a20c68813d646031a349e4ef3144c387462f1
4
+ data.tar.gz: 65f2ed189a51bfc5e9b3d7bbe0d04d7fdfc8ae39
5
5
  SHA512:
6
- metadata.gz: 9b576a52670e2fe3b3ae035d6f4da88c94635da2c20bab6214fdec4301ae19b30d056a8541742e6ef4abaac801214c65a6438ca94588c44d8cf5b3546602125b
7
- data.tar.gz: 1d807d4acc2b35e8aaa6d938a669d953347be9aace422fdd5ce2c9fb4e6b840665b916d34cc52dee2c37d25caab0b6642e1de26b6c37cead2619f14e4cc665a0
6
+ metadata.gz: fe400de4fdfe2d81dc1dfab9e5ff9f0b6f8c53f4c30f868b8e605185e74a907b2932ab3dbe5b793425fd90c5e55fb3f34efdd7d755f04223939ba38d4e5bb3ef
7
+ data.tar.gz: 1a7e2f419f4c8a2fdfc438406700220b2aba13409a5371db909a571d7a51bde96b1a231fbcd12d19fce49b463288b4d65100d35ba7e4879ed8fd04b729a037a7
data/CHANGES CHANGED
@@ -1,3 +1,22 @@
1
+ # 3.1.5
2
+
3
+ * Fix CodeClimate construction of --only-files (Will Fleming)
4
+ * Add check for denial of service via routes (CVE-2015-7581)
5
+ * Warn about RCE with `render params` (CVE-2016-0752)
6
+ * Add check for `strip_tags` XSS (CVE-2015-7579)
7
+ * Add check for `sanitize` XSS (CVE-2015-7578/80)
8
+ * Add check for `reject_if` proc bypass (CVE-2015-7577)
9
+ * Add check for mime-type denial of service (CVE-2016-0751)
10
+ * Add check for basic auth timing attack (CVE-2015-7576)
11
+ * Add initial Rails 5 support
12
+ * Check for implict integer comparison in dynamic finders
13
+ * Support directories better in --only-files and --skip-files (Patrick Toomey)
14
+ * Avoid warning about `permit` in SQL
15
+ * Handle guards using `detect`
16
+ * Avoid warning on user input in comparisons
17
+ * Handle module names with self methods
18
+ * Add session manipulation documentation
19
+
1
20
  # 3.1.4
2
21
 
3
22
  * 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
 
@@ -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
@@ -35,6 +35,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
35
35
  @mass_assign_disabled = nil
36
36
  @has_user_input = nil
37
37
  @safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id]
38
+ @comparison_ops = Set[:==, :!=, :>, :<, :>=, :<=]
38
39
  end
39
40
 
40
41
  #Add result to result list, which is used to check for duplicates
@@ -71,12 +72,14 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
71
72
 
72
73
  #Process calls and check if they include user input
73
74
  def process_call exp
74
- process exp.target if sexp? exp.target
75
- process_call_args exp
75
+ unless @comparison_ops.include? exp.method
76
+ process exp.target if sexp? exp.target
77
+ process_call_args exp
78
+ end
76
79
 
77
80
  target = exp.target
78
81
 
79
- unless @safe_input_attributes.include? exp.method
82
+ unless always_safe_method? exp.method
80
83
  if params? target
81
84
  @has_user_input = Match.new(:params, exp)
82
85
  elsif cookies? target
@@ -123,6 +126,11 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
123
126
 
124
127
  private
125
128
 
129
+ def always_safe_method? meth
130
+ @safe_input_attributes.include? meth or
131
+ @comparison_ops.include? meth
132
+ end
133
+
126
134
  #Report a warning
127
135
  def warn options
128
136
  extra_opts = { :check => self.class.to_s }
@@ -286,7 +294,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
286
294
  def has_immediate_user_input? exp
287
295
  if exp.nil?
288
296
  false
289
- elsif call? exp and not @safe_input_attributes.include? exp.method
297
+ elsif call? exp and not always_safe_method? exp.method
290
298
  if params? exp
291
299
  return Match.new(:params, exp)
292
300
  elsif cookies? exp
@@ -345,7 +353,7 @@ class Brakeman::BaseCheck < Brakeman::SexpProcessor
345
353
  target = exp.target
346
354
  method = exp.method
347
355
 
348
- if @safe_input_attributes.include? method
356
+ if always_safe_method? method
349
357
  false
350
358
  elsif call? target and not method.to_s[-1,1] == "?"
351
359
  if has_immediate_model?(target, out)
@@ -0,0 +1,54 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckBasicAuthTimingAttack < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check for timing attack in basic auth (CVE-2015-7576)"
7
+
8
+ def run_check
9
+ @upgrade = case
10
+ when version_between?("0.0.0", "3.2.22")
11
+ "3.2.22.1"
12
+ when version_between?("4.0.0", "4.1.14")
13
+ "4.1.14.1"
14
+ when version_between?("4.2.0", "4.2.5")
15
+ "4.2.5.1"
16
+ else
17
+ return
18
+ end
19
+
20
+ check_basic_auth_filter
21
+ check_basic_auth_call
22
+ end
23
+
24
+ def check_basic_auth_filter
25
+ controllers = tracker.controllers.select do |name, c|
26
+ c.options[:http_basic_authenticate_with]
27
+ end
28
+
29
+ Hash[controllers].each do |name, controller|
30
+ controller.options[:http_basic_authenticate_with].each do |call|
31
+ warn :controller => name,
32
+ :warning_type => "Timing Attack",
33
+ :warning_code => :CVE_2015_7576,
34
+ :message => "Basic authentication in Rails #{rails_version} is vulnerable to timing attacks. Upgrade to #@upgrade",
35
+ :code => call,
36
+ :confidence => CONFIDENCE[:high],
37
+ :file => controller.file,
38
+ :link => "https://groups.google.com/d/msg/rubyonrails-security/ANv0HDHEC3k/mt7wNGxbFQAJ"
39
+ end
40
+ end
41
+ end
42
+
43
+ def check_basic_auth_call
44
+ # This is relatively unusual, but found in the wild
45
+ tracker.find_call(target: nil, method: :http_basic_authenticate_with).each do |result|
46
+ warn :result => result,
47
+ :warning_type => "Timing Attack",
48
+ :warning_code => :CVE_2015_7576,
49
+ :message => "Basic authentication in Rails #{rails_version} is vulnerable to timing attacks. Upgrade to #@upgrade",
50
+ :confidence => CONFIDENCE[:high],
51
+ :link => "https://groups.google.com/d/msg/rubyonrails-security/ANv0HDHEC3k/mt7wNGxbFQAJ"
52
+ end
53
+ end
54
+ end
@@ -281,7 +281,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
281
281
  end
282
282
 
283
283
  def setup
284
- @ignore_methods = Set[:button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
284
+ @ignore_methods = Set[:==, :!=, :button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
285
285
  :field_field, :fields_for, :h, :hidden_field,
286
286
  :hidden_field, :hidden_field_tag, :image_tag, :label,
287
287
  :link_to, :mail_to, :radio_button, :select,
@@ -300,17 +300,23 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
300
300
  @ignore_methods << :auto_link
301
301
  end
302
302
 
303
- if version_between? "2.0.0", "2.3.14"
303
+ if version_between? "2.0.0", "2.3.14" or tracker.config.gem_version(:'rails-html-sanitizer') == '1.0.2'
304
304
  @known_dangerous << :strip_tags
305
305
  end
306
306
 
307
+ if tracker.config.has_gem? :'rails-html-sanitizer' and
308
+ version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer')
309
+
310
+ @known_dangerous << :sanitize
311
+ end
312
+
307
313
  json_escape_on = false
308
314
  initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
309
315
  initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
310
316
 
311
317
  if tracker.config.escape_html_entities_in_json?
312
318
  json_escape_on = true
313
- elsif version_between? "4.0.0", "5.0.0"
319
+ elsif version_between? "4.0.0", "9.9.9"
314
320
  json_escape_on = true
315
321
  end
316
322
 
@@ -370,7 +376,7 @@ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
370
376
  end
371
377
 
372
378
  def safe_input_attribute? target, method
373
- target and @safe_input_attributes.include? method
379
+ target and always_safe_method? method
374
380
  end
375
381
 
376
382
  def boolean_method? method
@@ -0,0 +1,49 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #This check looks for regexes that include user input.
4
+ class Brakeman::CheckDynamicFinders < Brakeman::BaseCheck
5
+ Brakeman::Checks.add self
6
+
7
+ @description = "Check unsafe usage of find_by_*"
8
+
9
+ def run_check
10
+ if tracker.config.has_gem? :mysql and version_between? '2.0.0', '4.1.99'
11
+ tracker.find_call(:method => /^find_by_/).each do |result|
12
+ process_result result
13
+ end
14
+ end
15
+ end
16
+
17
+ def process_result result
18
+ return if duplicate? result or result[:call].original_line
19
+ add_result result
20
+
21
+ call = result[:call]
22
+
23
+ if potentially_dangerous? call.method
24
+ call.each_arg do |arg|
25
+ if params? arg and not safe_call? arg
26
+ warn :result => result,
27
+ :warning_type => "SQL Injection",
28
+ :warning_code => :sql_injection_dynamic_finder,
29
+ :message => "MySQL integer conversion may cause 0 to match any string",
30
+ :confidence => CONFIDENCE[:med],
31
+ :user_input => arg
32
+
33
+ break
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def safe_call? arg
40
+ return false unless call? arg
41
+
42
+ meth = arg.method
43
+ meth == :to_s or meth == :to_i
44
+ end
45
+
46
+ def potentially_dangerous? method_name
47
+ method_name.match /^find_by_.*(token|guid|password|api_key|activation|code|private|reset)/
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckMimeTypeDoS < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for mime type denial of service (CVE-2016-0751)"
7
+
8
+ def run_check
9
+ fix_version = case
10
+ when version_between?("3.0.0", "3.2.22")
11
+ "3.2.22.1"
12
+ when version_between?("4.0.0", "4.1.14")
13
+ "4.1.14.1"
14
+ when version_between?("4.2.0", "4.2.5")
15
+ "4.2.5.1"
16
+ else
17
+ return
18
+ end
19
+
20
+ return if has_workaround?
21
+
22
+ message = "Rails #{rails_version} is vulnerable to denial of service via mime type caching (CVE-2016-0751). Upgrade to Rails version #{fix_version}"
23
+
24
+ warn :warning_type => "Denial of Service",
25
+ :warning_code => :CVE_2016_0751,
26
+ :message => message,
27
+ :confidence => CONFIDENCE[:med],
28
+ :gem_info => gemfile_or_environment,
29
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/9oLY_FCzvoc/w9oI9XxbFQAJ"
30
+ end
31
+
32
+ def has_workaround?
33
+ tracker.check_initializers(:Mime, :const_set).any? do |match|
34
+ arg = match.call.first_arg
35
+
36
+ symbol? arg and arg.value == :LOOKUP
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,58 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #https://groups.google.com/d/msg/rubyonrails-security/cawsWcQ6c8g/tegZtYdbFQAJ
4
+ class Brakeman::CheckNestedAttributesBypass < Brakeman::BaseCheck
5
+ Brakeman::Checks.add self
6
+
7
+ @description = "Checks for nested attributes vulnerability (CVE-2015-7577)"
8
+
9
+ def run_check
10
+ if version_between? "3.1.0", "3.2.22" or
11
+ version_between? "4.0.0", "4.1.14" or
12
+ version_between? "4.2.0", "4.2.5"
13
+
14
+ unless workaround?
15
+ check_nested_attributes
16
+ end
17
+ end
18
+ end
19
+
20
+ def check_nested_attributes
21
+ active_record_models.each do |name, model|
22
+ if opts = model.options[:accepts_nested_attributes_for]
23
+ opts.each do |args|
24
+ if args.any? { |a| allow_destroy? a } and args.any? { |a| reject_if? a }
25
+ warn_about_nested_attributes name, model, args
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def warn_about_nested_attributes name, model, args
33
+ message = "Rails #{rails_version} does not call :reject_if option when :allow_destroy is false (CVE-2015-7577)"
34
+
35
+ warn :model => name,
36
+ :warning_type => "Nested Attributes",
37
+ :warning_code => :CVE_2015_7577,
38
+ :message => message,
39
+ :file => model.file,
40
+ :line => args.line,
41
+ :confidence => CONFIDENCE[:med],
42
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/cawsWcQ6c8g/tegZtYdbFQAJ"
43
+ end
44
+
45
+ def allow_destroy? arg
46
+ hash? arg and
47
+ false? hash_access(arg, :allow_destroy)
48
+ end
49
+
50
+ def reject_if? arg
51
+ hash? arg and
52
+ hash_access(arg, :reject_if)
53
+ end
54
+
55
+ def workaround?
56
+ tracker.check_initializers([], :will_be_destroyed?).any?
57
+ end
58
+ end
@@ -4,7 +4,7 @@ require 'brakeman/checks/base_check'
4
4
  class Brakeman::CheckRender < Brakeman::BaseCheck
5
5
  Brakeman::Checks.add self
6
6
 
7
- @description = "Finds calls to render that might allow file access"
7
+ @description = "Finds calls to render that might allow file access or code execution"
8
8
 
9
9
  def run_check
10
10
  tracker.find_call(:target => nil, :method => :render).each do |result|
@@ -17,7 +17,8 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
17
17
 
18
18
  case result[:call].render_type
19
19
  when :partial, :template, :action, :file
20
- check_for_dynamic_path result
20
+ check_for_rce(result) or
21
+ check_for_dynamic_path(result)
21
22
  when :inline
22
23
  when :js
23
24
  when :json
@@ -59,4 +60,25 @@ class Brakeman::CheckRender < Brakeman::BaseCheck
59
60
  :confidence => confidence
60
61
  end
61
62
  end
63
+
64
+ def check_for_rce result
65
+ return unless version_between? "0.0.0", "3.2.22" or
66
+ version_between? "4.0.0", "4.1.14" or
67
+ version_between? "4.2.0", "4.2.5"
68
+
69
+
70
+ view = result[:call][2]
71
+ if sexp? view and not duplicate? result
72
+ if params? view
73
+ add_result result
74
+
75
+ warn :result => result,
76
+ :warning_type => "Remote Code Execution",
77
+ :warning_code => :dynamic_render_path_rce,
78
+ :message => "Passing query parameters to render() is vulnerable in Rails #{rails_version} (CVE-2016-0752)",
79
+ :user_input => view,
80
+ :confidence => CONFIDENCE[:high]
81
+ end
82
+ end
83
+ end
62
84
  end
@@ -0,0 +1,42 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckRouteDoS < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for route DoS (CVE-2015-7581)"
7
+
8
+ def run_check
9
+ fix_version = case
10
+ when version_between?("4.0.0", "4.1.14")
11
+ "4.1.14.1"
12
+ when version_between?("4.2.0", "4.2.5")
13
+ "4.2.5.1"
14
+ else
15
+ return
16
+ end
17
+
18
+ if controller_wildcards?
19
+ message = "Rails #{rails_version} has a denial of service vulnerability with :controller routes (CVE-2015-7581). Upgrade to Rails #{fix_version}"
20
+
21
+ warn :warning_type => "Denial of Service",
22
+ :warning_code => :CVE_2015_7581,
23
+ :message => message,
24
+ :confidence => CONFIDENCE[:med],
25
+ :gem_info => gemfile_or_environment,
26
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/dthJ5wL69JE/YzPnFelbFQAJ"
27
+ end
28
+ end
29
+
30
+ def controller_wildcards?
31
+ tracker.routes.each do |name, actions|
32
+ if name == :':controllerController'
33
+ # awful hack for routes with :controller in them
34
+ return true
35
+ elsif string? actions and actions.value.include? ":controller"
36
+ return true
37
+ end
38
+ end
39
+
40
+ false
41
+ end
42
+ end
@@ -17,12 +17,17 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
17
17
  '3.1.12'
18
18
  when version_between?('3.2.0', '3.2.12')
19
19
  '3.2.13'
20
- else
21
- return
22
20
  end
23
21
 
24
- check_cve_2013_1855
25
- check_cve_2013_1857
22
+ if @fix_version
23
+ check_cve_2013_1855
24
+ check_cve_2013_1857
25
+ elsif tracker.config.has_gem? :'rails-html-sanitizer' and
26
+ version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer')
27
+
28
+ warn_sanitizer_cve "CVE-2015-7578", "https://groups.google.com/d/msg/rubyonrails-security/uh--W4TDwmI/JbvSRpdbFQAJ"
29
+ warn_sanitizer_cve "CVE-2015-7580", "https://groups.google.com/d/msg/rubyonrails-security/uh--W4TDwmI/m_CVZtdbFQAJ"
30
+ end
26
31
  end
27
32
 
28
33
  def check_cve_2013_1855
@@ -54,4 +59,21 @@ class Brakeman::CheckSanitizeMethods < Brakeman::BaseCheck
54
59
  :link_path => link
55
60
  end
56
61
  end
62
+
63
+ def warn_sanitizer_cve cve, link
64
+ message = "rails-html-sanitizer #{tracker.config.gem_version(:'rails-html-sanitizer')} is vulnerable (#{cve}). Upgrade to 1.0.3"
65
+
66
+ if tracker.find_call(:target => false, :method => :sanitize).any?
67
+ confidence = CONFIDENCE[:high]
68
+ else
69
+ confidence = CONFIDENCE[:med]
70
+ end
71
+
72
+ warn :warning_type => "Cross Site Scripting",
73
+ :warning_code => cve.tr('-', '_').to_sym,
74
+ :message => message,
75
+ :gem_info => gemfile_or_environment,
76
+ :confidence => confidence,
77
+ :link_path => link
78
+ end
57
79
  end
@@ -59,7 +59,7 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
59
59
  call = make_call(nil, :named_scope, args).line(args.line)
60
60
  scope_calls << scope_call_hash(call, name, :named_scope)
61
61
  end
62
- elsif version_between?("3.1.0", "4.9.9")
62
+ elsif version_between?("3.1.0", "9.9.9")
63
63
  ar_scope_calls(:scope) do |name, args|
64
64
  second_arg = args[2]
65
65
  next unless sexp? second_arg
@@ -264,8 +264,10 @@ class Brakeman::CheckSQL < Brakeman::BaseCheck
264
264
  end
265
265
 
266
266
  if request_value? arg
267
- # Model.where(params[:where])
268
- arg
267
+ unless call? arg and params? arg.target and arg.method == :permit
268
+ # Model.where(params[:where])
269
+ arg
270
+ end
269
271
  elsif hash? arg
270
272
  #This is generally going to be a hash of column names and values, which
271
273
  #would escape the values. But the keys _could_ be user input.
@@ -5,16 +5,21 @@ require 'brakeman/checks/base_check'
5
5
  #
6
6
  #Check for uses of strip_tags in Rails versions before 2.3.13 and 3.0.10:
7
7
  #http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2b9130749b74ea12
8
+ #
9
+ #Check for user of strip_tags with rails-html-sanitizer 1.0.2:
10
+ #https://groups.google.com/d/msg/rubyonrails-security/OU9ugTZcbjc/PjEP46pbFQAJ
8
11
  class Brakeman::CheckStripTags < Brakeman::BaseCheck
9
12
  Brakeman::Checks.add self
10
13
 
11
- @description = "Report strip_tags vulnerabilities CVE-2011-2931 and CVE-2012-3465"
14
+ @description = "Report strip_tags vulnerabilities"
12
15
 
13
16
  def run_check
14
17
  if uses_strip_tags?
15
18
  cve_2011_2931
16
19
  cve_2012_3465
17
20
  end
21
+
22
+ cve_2015_7579
18
23
  end
19
24
 
20
25
  def cve_2011_2931
@@ -56,9 +61,29 @@ class Brakeman::CheckStripTags < Brakeman::BaseCheck
56
61
  :link_path => "https://groups.google.com/d/topic/rubyonrails-security/FgVEtBajcTY/discussion"
57
62
  end
58
63
 
64
+ def cve_2015_7579
65
+ if tracker.config.gem_version(:'rails-html-sanitizer') == '1.0.2'
66
+ if uses_strip_tags?
67
+ confidence = CONFIDENCE[:high]
68
+ else
69
+ confidence = CONFIDENCE[:med]
70
+ end
71
+
72
+ message = "rails-html-sanitizer 1.0.2 is vulnerable (CVE-2015-7579). Upgrade to 1.0.3"
73
+
74
+ warn :warning_type => "Cross Site Scripting",
75
+ :warning_code => :CVE_2015_7579,
76
+ :message => message,
77
+ :confidence => confidence,
78
+ :gem_info => gemfile_or_environment,
79
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/OU9ugTZcbjc/PjEP46pbFQAJ"
80
+
81
+ end
82
+ end
83
+
59
84
  def uses_strip_tags?
60
85
  Brakeman.debug "Finding calls to strip_tags()"
61
86
 
62
- not tracker.find_call(:target => false, :method => :strip_tags).empty?
87
+ not tracker.find_call(:target => false, :method => :strip_tags, :nested => true).empty?
63
88
  end
64
89
  end
@@ -52,6 +52,12 @@ module Brakeman::Options
52
52
  options[:rails4] = true
53
53
  end
54
54
 
55
+ opts.on "-5", "--rails5", "Force Rails 5 mode" do
56
+ options[:rails3] = true
57
+ options[:rails4] = true
58
+ options[:rails5] = true
59
+ end
60
+
55
61
  opts.separator ""
56
62
  opts.separator "Scanning options:"
57
63
 
@@ -110,12 +116,12 @@ module Brakeman::Options
110
116
  options[:url_safe_methods].merge methods.map {|e| e.to_sym }
111
117
  end
112
118
 
113
- opts.on "--skip-files file1,file2,etc", Array, "Skip processing of these files" do |files|
119
+ opts.on "--skip-files file1,path2,etc", Array, "Skip processing of these files/directories. Directories are application relative and must end in \"#{File::SEPARATOR}\"" do |files|
114
120
  options[:skip_files] ||= Set.new
115
121
  options[:skip_files].merge files
116
122
  end
117
123
 
118
- opts.on "--only-files file1,file2,etc", Array, "Process only these files" do |files|
124
+ opts.on "--only-files file1,path2,etc", Array, "Process only these files/directories. Directories are application relative and must end in \"#{File::SEPARATOR}\"" do |files|
119
125
  options[:only_files] ||= Set.new
120
126
  options[:only_files].merge files
121
127
  end
@@ -212,6 +212,10 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
212
212
  def process_iter exp
213
213
  @exp_context.push exp
214
214
  exp[1] = process exp.block_call
215
+ if array_detect_all_literals? exp[1]
216
+ return exp.block_call.target[1]
217
+ end
218
+
215
219
  @exp_context.pop
216
220
 
217
221
  env.scope do
@@ -524,6 +528,15 @@ class Brakeman::AliasProcessor < Brakeman::SexpProcessor
524
528
  exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
525
529
  end
526
530
 
531
+ def array_detect_all_literals? exp
532
+ call? exp and
533
+ [:detect, :find].include? exp.method and
534
+ node_type? exp.target, :array and
535
+ exp.target.length > 1 and
536
+ exp.first_arg.nil? and
537
+ exp.target.all? { |e| e.is_a? Symbol or node_type? e, :lit, :str }
538
+ end
539
+
527
540
  #Sets @inside_if = true
528
541
  def process_if exp
529
542
  if @ignore_ifs.nil?
@@ -208,11 +208,11 @@ class Brakeman::ControllerProcessor < Brakeman::BaseProcessor
208
208
  def process_defs exp
209
209
  name = exp.method_name
210
210
 
211
- if exp[1].node_type == :self
211
+ if node_type? exp[1], :self
212
212
  if @current_class
213
213
  target = @current_class.name
214
214
  elsif @current_module
215
- target = @current_module
215
+ target = @current_module.name
216
216
  else
217
217
  target = nil
218
218
  end
@@ -31,6 +31,10 @@ module Brakeman::RouteHelper
31
31
 
32
32
  return unless route.is_a? String or route.is_a? Symbol
33
33
 
34
+ if route.is_a? String and controller.nil? and route.include? ":controller"
35
+ controller = ":controller"
36
+ end
37
+
34
38
  route = route.to_sym
35
39
 
36
40
  if controller
@@ -163,11 +163,11 @@ class Brakeman::ModelProcessor < Brakeman::BaseProcessor
163
163
  return exp unless @current_class
164
164
  name = exp.method_name
165
165
 
166
- if exp[1].node_type == :self
166
+ if node_type? exp[1], :self
167
167
  if @current_class
168
168
  target = @current_class.name
169
169
  elsif @current_module
170
- target = @current_module
170
+ target = @current_module.name
171
171
  else
172
172
  target = nil
173
173
  end
@@ -96,7 +96,7 @@ class Brakeman::Scanner
96
96
  #
97
97
  #Stores parsed information in tracker.config
98
98
  def process_config
99
- if options[:rails3]
99
+ if options[:rails3] or options[:rails4] or options[:rails5]
100
100
  process_config_file "application.rb"
101
101
  process_config_file "environments/production.rb"
102
102
  else
@@ -155,8 +155,13 @@ class Brakeman::Scanner
155
155
  if @app_tree.exists?("script/rails")
156
156
  tracker.options[:rails3] = true
157
157
  Brakeman.notify "[Notice] Detected Rails 3 application"
158
+ elsif @app_tree.exists?("app/channels")
159
+ tracker.options[:rails3] = true
160
+ tracker.options[:rails4] = true
161
+ tracker.options[:rails5] = true
162
+ Brakeman.notify "[Notice] Detected Rails 5 application"
158
163
  elsif not @app_tree.exists?("script")
159
- tracker.options[:rails3] = true # Probably need to do some refactoring
164
+ tracker.options[:rails3] = true
160
165
  tracker.options[:rails4] = true
161
166
  Brakeman.notify "[Notice] Detected Rails 4 application"
162
167
  end
@@ -7,6 +7,7 @@ module Brakeman
7
7
  attr_reader :rails, :tracker
8
8
  attr_accessor :rails_version
9
9
  attr_writer :erubis, :escape_html
10
+ attr_reader :gems
10
11
 
11
12
  def initialize tracker
12
13
  @tracker = tracker
@@ -76,6 +77,11 @@ module Brakeman
76
77
  tracker.options[:rails3] = true
77
78
  tracker.options[:rails4] = true
78
79
  Brakeman.notify "[Notice] Detected Rails 4 application"
80
+ elsif @rails_version.start_with? "5"
81
+ tracker.options[:rails3] = true
82
+ tracker.options[:rails4] = true
83
+ tracker.options[:rails5] = true
84
+ Brakeman.notify "[Notice] Detected Rails 5 application"
79
85
  end
80
86
  end
81
87
  end
@@ -1,3 +1,3 @@
1
1
  module Brakeman
2
- Version = "3.1.4"
2
+ Version = "3.1.5"
3
3
  end
@@ -93,6 +93,15 @@ module Brakeman::WarningCodes
93
93
  :session_key_manipulation => 89,
94
94
  :weak_hash_digest => 90,
95
95
  :weak_hash_hmac => 91,
96
+ :sql_injection_dynamic_finder => 92,
97
+ :CVE_2015_7576 => 93,
98
+ :CVE_2016_0751 => 94,
99
+ :CVE_2015_7577 => 95,
100
+ :CVE_2015_7578 => 96,
101
+ :CVE_2015_7580 => 97,
102
+ :CVE_2015_7579 => 98,
103
+ :dynamic_render_path_rce => 99,
104
+ :CVE_2015_7581 => 100,
96
105
  }
97
106
 
98
107
  def self.code name
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brakeman
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.4
4
+ version: 3.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Collins
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
11
  - brakeman-public_cert.pem
12
- date: 2015-12-22 00:00:00.000000000 Z
12
+ date: 2016-01-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: test-unit
@@ -223,6 +223,7 @@ files:
223
223
  - lib/brakeman/checks.rb
224
224
  - lib/brakeman/checks/base_check.rb
225
225
  - lib/brakeman/checks/check_basic_auth.rb
226
+ - lib/brakeman/checks/check_basic_auth_timing_attack.rb
226
227
  - lib/brakeman/checks/check_content_tag.rb
227
228
  - lib/brakeman/checks/check_create_with.rb
228
229
  - lib/brakeman/checks/check_cross_site_scripting.rb
@@ -230,6 +231,7 @@ files:
230
231
  - lib/brakeman/checks/check_deserialize.rb
231
232
  - lib/brakeman/checks/check_detailed_exceptions.rb
232
233
  - lib/brakeman/checks/check_digest_dos.rb
234
+ - lib/brakeman/checks/check_dynamic_finders.rb
233
235
  - lib/brakeman/checks/check_escape_function.rb
234
236
  - lib/brakeman/checks/check_evaluation.rb
235
237
  - lib/brakeman/checks/check_execute.rb
@@ -246,10 +248,12 @@ files:
246
248
  - lib/brakeman/checks/check_link_to_href.rb
247
249
  - lib/brakeman/checks/check_mail_to.rb
248
250
  - lib/brakeman/checks/check_mass_assignment.rb
251
+ - lib/brakeman/checks/check_mime_type_dos.rb
249
252
  - lib/brakeman/checks/check_model_attr_accessible.rb
250
253
  - lib/brakeman/checks/check_model_attributes.rb
251
254
  - lib/brakeman/checks/check_model_serialize.rb
252
255
  - lib/brakeman/checks/check_nested_attributes.rb
256
+ - lib/brakeman/checks/check_nested_attributes_bypass.rb
253
257
  - lib/brakeman/checks/check_number_to_currency.rb
254
258
  - lib/brakeman/checks/check_quote_table_name.rb
255
259
  - lib/brakeman/checks/check_redirect.rb
@@ -258,6 +262,7 @@ files:
258
262
  - lib/brakeman/checks/check_render_dos.rb
259
263
  - lib/brakeman/checks/check_render_inline.rb
260
264
  - lib/brakeman/checks/check_response_splitting.rb
265
+ - lib/brakeman/checks/check_route_dos.rb
261
266
  - lib/brakeman/checks/check_safe_buffer_manipulation.rb
262
267
  - lib/brakeman/checks/check_sanitize_methods.rb
263
268
  - lib/brakeman/checks/check_select_tag.rb