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 +4 -4
- data/CHANGES +19 -0
- data/README.md +3 -2
- data/lib/brakeman/app_tree.rb +58 -5
- data/lib/brakeman/checks/base_check.rb +13 -5
- data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +54 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +10 -4
- data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
- data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
- data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/brakeman/checks/check_render.rb +24 -2
- data/lib/brakeman/checks/check_route_dos.rb +42 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +26 -4
- data/lib/brakeman/checks/check_sql.rb +5 -3
- data/lib/brakeman/checks/check_strip_tags.rb +27 -2
- data/lib/brakeman/options.rb +8 -2
- data/lib/brakeman/processors/alias_processor.rb +13 -0
- data/lib/brakeman/processors/controller_processor.rb +2 -2
- data/lib/brakeman/processors/lib/route_helper.rb +4 -0
- data/lib/brakeman/processors/model_processor.rb +2 -2
- data/lib/brakeman/scanner.rb +7 -2
- data/lib/brakeman/tracker/config.rb +6 -0
- data/lib/brakeman/version.rb +1 -1
- data/lib/brakeman/warning_codes.rb +9 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 449a20c68813d646031a349e4ef3144c387462f1
|
4
|
+
data.tar.gz: 65f2ed189a51bfc5e9b3d7bbe0d04d7fdfc8ae39
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
|
data/lib/brakeman/app_tree.rb
CHANGED
@@ -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] =
|
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] =
|
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
|
-
|
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
|
-
|
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
|
-
|
75
|
-
|
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
|
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
|
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
|
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", "
|
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
|
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
|
-
|
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
|
-
|
25
|
-
|
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", "
|
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
|
-
|
268
|
-
|
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
|
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
|
data/lib/brakeman/options.rb
CHANGED
@@ -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,
|
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,
|
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]
|
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
|
@@ -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]
|
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
|
data/lib/brakeman/scanner.rb
CHANGED
@@ -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
|
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
|
data/lib/brakeman/version.rb
CHANGED
@@ -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
|
+
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:
|
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
|