brakeman 3.1.4 → 3.1.5

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