brakeman-lib 3.3.1
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 +7 -0
- data/CHANGES +872 -0
- data/FEATURES +16 -0
- data/README.md +169 -0
- data/WARNING_TYPES +95 -0
- data/bin/brakeman +89 -0
- data/lib/brakeman.rb +495 -0
- data/lib/brakeman/app_tree.rb +161 -0
- data/lib/brakeman/brakeman.rake +17 -0
- data/lib/brakeman/call_index.rb +219 -0
- data/lib/brakeman/checks.rb +191 -0
- data/lib/brakeman/checks/base_check.rb +518 -0
- data/lib/brakeman/checks/check_basic_auth.rb +88 -0
- data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/brakeman/checks/check_content_tag.rb +160 -0
- data/lib/brakeman/checks/check_create_with.rb +75 -0
- data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
- data/lib/brakeman/checks/check_default_routes.rb +86 -0
- data/lib/brakeman/checks/check_deserialize.rb +57 -0
- data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
- data/lib/brakeman/checks/check_digest_dos.rb +38 -0
- data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
- data/lib/brakeman/checks/check_escape_function.rb +21 -0
- data/lib/brakeman/checks/check_evaluation.rb +36 -0
- data/lib/brakeman/checks/check_execute.rb +167 -0
- data/lib/brakeman/checks/check_file_access.rb +63 -0
- data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
- data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
- data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
- data/lib/brakeman/checks/check_header_dos.rb +31 -0
- data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
- data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
- data/lib/brakeman/checks/check_json_encoding.rb +47 -0
- data/lib/brakeman/checks/check_json_parsing.rb +107 -0
- data/lib/brakeman/checks/check_link_to.rb +132 -0
- data/lib/brakeman/checks/check_link_to_href.rb +115 -0
- data/lib/brakeman/checks/check_mail_to.rb +49 -0
- data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
- data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
- data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
- data/lib/brakeman/checks/check_model_attributes.rb +119 -0
- data/lib/brakeman/checks/check_model_serialize.rb +67 -0
- data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
- data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
- data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
- data/lib/brakeman/checks/check_redirect.rb +215 -0
- data/lib/brakeman/checks/check_regex_dos.rb +69 -0
- data/lib/brakeman/checks/check_render.rb +92 -0
- data/lib/brakeman/checks/check_render_dos.rb +37 -0
- data/lib/brakeman/checks/check_render_inline.rb +54 -0
- data/lib/brakeman/checks/check_response_splitting.rb +21 -0
- data/lib/brakeman/checks/check_route_dos.rb +42 -0
- data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
- data/lib/brakeman/checks/check_secrets.rb +40 -0
- data/lib/brakeman/checks/check_select_tag.rb +60 -0
- data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
- data/lib/brakeman/checks/check_send.rb +48 -0
- data/lib/brakeman/checks/check_send_file.rb +19 -0
- data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
- data/lib/brakeman/checks/check_session_settings.rb +170 -0
- data/lib/brakeman/checks/check_simple_format.rb +59 -0
- data/lib/brakeman/checks/check_single_quotes.rb +101 -0
- data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
- data/lib/brakeman/checks/check_sql.rb +660 -0
- data/lib/brakeman/checks/check_sql_cves.rb +101 -0
- data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
- data/lib/brakeman/checks/check_strip_tags.rb +89 -0
- data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
- data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/brakeman/checks/check_translate_bug.rb +45 -0
- data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
- data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
- data/lib/brakeman/checks/check_validation_regex.rb +116 -0
- data/lib/brakeman/checks/check_weak_hash.rb +151 -0
- data/lib/brakeman/checks/check_without_protection.rb +80 -0
- data/lib/brakeman/checks/check_xml_dos.rb +51 -0
- data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
- data/lib/brakeman/differ.rb +66 -0
- data/lib/brakeman/file_parser.rb +50 -0
- data/lib/brakeman/format/style.css +133 -0
- data/lib/brakeman/options.rb +301 -0
- data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
- data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
- data/lib/brakeman/parsers/template_parser.rb +89 -0
- data/lib/brakeman/processor.rb +102 -0
- data/lib/brakeman/processors/alias_processor.rb +1013 -0
- data/lib/brakeman/processors/base_processor.rb +277 -0
- data/lib/brakeman/processors/config_processor.rb +14 -0
- data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
- data/lib/brakeman/processors/controller_processor.rb +326 -0
- data/lib/brakeman/processors/erb_template_processor.rb +80 -0
- data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
- data/lib/brakeman/processors/gem_processor.rb +57 -0
- data/lib/brakeman/processors/haml_template_processor.rb +190 -0
- data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
- data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
- data/lib/brakeman/processors/lib/find_call.rb +183 -0
- data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
- data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
- data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/brakeman/processors/lib/render_helper.rb +181 -0
- data/lib/brakeman/processors/lib/render_path.rb +107 -0
- data/lib/brakeman/processors/lib/route_helper.rb +68 -0
- data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
- data/lib/brakeman/processors/library_processor.rb +119 -0
- data/lib/brakeman/processors/model_processor.rb +191 -0
- data/lib/brakeman/processors/output_processor.rb +171 -0
- data/lib/brakeman/processors/route_processor.rb +17 -0
- data/lib/brakeman/processors/slim_template_processor.rb +107 -0
- data/lib/brakeman/processors/template_alias_processor.rb +116 -0
- data/lib/brakeman/processors/template_processor.rb +74 -0
- data/lib/brakeman/report.rb +78 -0
- data/lib/brakeman/report/config/remediation.yml +71 -0
- data/lib/brakeman/report/ignore/config.rb +135 -0
- data/lib/brakeman/report/ignore/interactive.rb +311 -0
- data/lib/brakeman/report/renderer.rb +24 -0
- data/lib/brakeman/report/report_base.rb +286 -0
- data/lib/brakeman/report/report_codeclimate.rb +70 -0
- data/lib/brakeman/report/report_csv.rb +55 -0
- data/lib/brakeman/report/report_hash.rb +23 -0
- data/lib/brakeman/report/report_html.rb +216 -0
- data/lib/brakeman/report/report_json.rb +42 -0
- data/lib/brakeman/report/report_markdown.rb +156 -0
- data/lib/brakeman/report/report_table.rb +107 -0
- data/lib/brakeman/report/report_tabs.rb +17 -0
- data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
- data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
- data/lib/brakeman/report/templates/header.html.erb +58 -0
- data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
- data/lib/brakeman/report/templates/overview.html.erb +38 -0
- data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
- data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
- data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
- data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
- data/lib/brakeman/rescanner.rb +483 -0
- data/lib/brakeman/scanner.rb +317 -0
- data/lib/brakeman/tracker.rb +347 -0
- data/lib/brakeman/tracker/collection.rb +93 -0
- data/lib/brakeman/tracker/config.rb +101 -0
- data/lib/brakeman/tracker/constants.rb +101 -0
- data/lib/brakeman/tracker/controller.rb +161 -0
- data/lib/brakeman/tracker/library.rb +17 -0
- data/lib/brakeman/tracker/model.rb +90 -0
- data/lib/brakeman/tracker/template.rb +33 -0
- data/lib/brakeman/util.rb +481 -0
- data/lib/brakeman/version.rb +3 -0
- data/lib/brakeman/warning.rb +255 -0
- data/lib/brakeman/warning_codes.rb +111 -0
- data/lib/ruby_parser/bm_sexp.rb +610 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- metadata +362 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Reports any calls to +validates_format_of+ which do not use +\A+ and +\z+
|
|
4
|
+
#as anchors in the given regular expression.
|
|
5
|
+
#
|
|
6
|
+
#For example:
|
|
7
|
+
#
|
|
8
|
+
# #Allows anything after new line
|
|
9
|
+
# validates_format_of :user_name, :with => /^\w+$/
|
|
10
|
+
class Brakeman::CheckValidationRegex < Brakeman::BaseCheck
|
|
11
|
+
Brakeman::Checks.add self
|
|
12
|
+
|
|
13
|
+
@description = "Report uses of validates_format_of with improper anchors"
|
|
14
|
+
|
|
15
|
+
WITH = Sexp.new(:lit, :with)
|
|
16
|
+
FORMAT = Sexp.new(:lit, :format)
|
|
17
|
+
|
|
18
|
+
def run_check
|
|
19
|
+
active_record_models.each do |name, model|
|
|
20
|
+
@current_model = name
|
|
21
|
+
format_validations = model.options[:validates_format_of]
|
|
22
|
+
|
|
23
|
+
if format_validations
|
|
24
|
+
format_validations.each do |v|
|
|
25
|
+
process_validates_format_of v
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
validates = model.options[:validates]
|
|
30
|
+
|
|
31
|
+
if validates
|
|
32
|
+
validates.each do |v|
|
|
33
|
+
process_validates v
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#Check validates_format_of
|
|
40
|
+
def process_validates_format_of validator
|
|
41
|
+
if value = hash_access(validator.last, WITH)
|
|
42
|
+
check_regex value, validator
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#Check validates ..., :format => ...
|
|
47
|
+
def process_validates validator
|
|
48
|
+
hash_arg = validator.last
|
|
49
|
+
return unless hash? hash_arg
|
|
50
|
+
|
|
51
|
+
value = hash_access(hash_arg, FORMAT)
|
|
52
|
+
|
|
53
|
+
if hash? value
|
|
54
|
+
value = hash_access(value, WITH)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
if value
|
|
58
|
+
check_regex value, validator
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Match secure regexp without extended option
|
|
63
|
+
SECURE_REGEXP_PATTERN = %r{
|
|
64
|
+
\A
|
|
65
|
+
\\A
|
|
66
|
+
.*
|
|
67
|
+
\\[zZ]
|
|
68
|
+
\z
|
|
69
|
+
}x
|
|
70
|
+
|
|
71
|
+
# Match secure of regexp with extended option
|
|
72
|
+
EXTENDED_SECURE_REGEXP_PATTERN = %r{
|
|
73
|
+
\A
|
|
74
|
+
\s*
|
|
75
|
+
\\A
|
|
76
|
+
.*
|
|
77
|
+
\\[zZ]
|
|
78
|
+
\s*
|
|
79
|
+
\z
|
|
80
|
+
}mx
|
|
81
|
+
|
|
82
|
+
#Issue warning if the regular expression does not use
|
|
83
|
+
#+\A+ and +\z+
|
|
84
|
+
def check_regex value, validator
|
|
85
|
+
return unless regexp? value
|
|
86
|
+
|
|
87
|
+
regex = value.value
|
|
88
|
+
unless secure_regex?(regex)
|
|
89
|
+
warn :model => @current_model,
|
|
90
|
+
:warning_type => "Format Validation",
|
|
91
|
+
:warning_code => :validation_regex,
|
|
92
|
+
:message => "Insufficient validation for '#{get_name validator}' using #{regex.inspect}. Use \\A and \\z as anchors",
|
|
93
|
+
:line => value.line,
|
|
94
|
+
:confidence => CONFIDENCE[:high]
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#Get the name of the attribute being validated.
|
|
99
|
+
def get_name validator
|
|
100
|
+
name = validator[1]
|
|
101
|
+
|
|
102
|
+
if sexp? name
|
|
103
|
+
name.value
|
|
104
|
+
else
|
|
105
|
+
name
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
|
|
111
|
+
def secure_regex?(regex)
|
|
112
|
+
extended_regex = Regexp::EXTENDED == regex.options & Regexp::EXTENDED
|
|
113
|
+
regex_pattern = extended_regex ? EXTENDED_SECURE_REGEXP_PATTERN : SECURE_REGEXP_PATTERN
|
|
114
|
+
regex_pattern =~ regex.source
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Brakeman::CheckWeakHash < Brakeman::BaseCheck
|
|
4
|
+
Brakeman::Checks.add_optional self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for use of weak hashes like MD5"
|
|
7
|
+
|
|
8
|
+
DIGEST_CALLS = [:base64digest, :digest, :hexdigest, :new]
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
tracker.find_call(:targets => [:'Digest::MD5', :'Digest::SHA1', :'OpenSSL::Digest::MD5', :'OpenSSL::Digest::SHA1'], :nested => true).each do |result|
|
|
12
|
+
process_hash_result result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
tracker.find_call(:target => :'Digest::HMAC', :methods => [:new, :hexdigest], :nested => true).each do |result|
|
|
16
|
+
process_hmac_result result
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
tracker.find_call(:targets => [:'OpenSSL::Digest::Digest', :'OpenSSL::Digest'], :method => :new).each do |result|
|
|
20
|
+
process_openssl_result result
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def process_hash_result result
|
|
25
|
+
return if duplicate? result
|
|
26
|
+
add_result result
|
|
27
|
+
|
|
28
|
+
input = nil
|
|
29
|
+
call = result[:call]
|
|
30
|
+
|
|
31
|
+
if DIGEST_CALLS.include? call.method
|
|
32
|
+
if input = user_input_as_arg?(call)
|
|
33
|
+
confidence = CONFIDENCE[:high]
|
|
34
|
+
elsif input = hashing_password?(call)
|
|
35
|
+
confidence = CONFIDENCE[:high]
|
|
36
|
+
else
|
|
37
|
+
confidence = CONFIDENCE[:med]
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
confidence = CONFIDENCE[:med]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
alg = case call.target.last
|
|
45
|
+
when :MD5
|
|
46
|
+
" (MD5)"
|
|
47
|
+
when :SHA1
|
|
48
|
+
" (SHA1)"
|
|
49
|
+
else
|
|
50
|
+
""
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
warn :result => result,
|
|
54
|
+
:warning_type => "Weak Hash",
|
|
55
|
+
:warning_code => :weak_hash_digest,
|
|
56
|
+
:message => "Weak hashing algorithm#{alg} used",
|
|
57
|
+
:confidence => confidence,
|
|
58
|
+
:user_input => input
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def process_hmac_result result
|
|
62
|
+
return if duplicate? result
|
|
63
|
+
add_result result
|
|
64
|
+
|
|
65
|
+
call = result[:call]
|
|
66
|
+
|
|
67
|
+
alg = case call.third_arg.last
|
|
68
|
+
when :MD5
|
|
69
|
+
'MD5'
|
|
70
|
+
when :SHA1
|
|
71
|
+
'SHA1'
|
|
72
|
+
else
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
warn :result => result,
|
|
77
|
+
:warning_type => "Weak Hash",
|
|
78
|
+
:warning_code => :weak_hash_hmac,
|
|
79
|
+
:message => "Weak hashing algorithm (#{alg}) used in HMAC",
|
|
80
|
+
:confidence => CONFIDENCE[:med]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def process_openssl_result result
|
|
84
|
+
return if duplicate? result
|
|
85
|
+
add_result result
|
|
86
|
+
|
|
87
|
+
arg = result[:call].first_arg
|
|
88
|
+
|
|
89
|
+
if string? arg
|
|
90
|
+
alg = arg.value.upcase
|
|
91
|
+
|
|
92
|
+
if alg == 'MD5' or alg == 'SHA1'
|
|
93
|
+
warn :result => result,
|
|
94
|
+
:warning_type => "Weak Hash",
|
|
95
|
+
:warning_code => :weak_hash_digest,
|
|
96
|
+
:message => "Weak hashing algorithm (#{alg}) used",
|
|
97
|
+
:confidence => CONFIDENCE[:med]
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def user_input_as_arg? call
|
|
103
|
+
call.each_arg do |arg|
|
|
104
|
+
if input = include_user_input?(arg)
|
|
105
|
+
return input
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def hashing_password? call
|
|
113
|
+
call.each_arg do |arg|
|
|
114
|
+
@has_password = false
|
|
115
|
+
|
|
116
|
+
process arg
|
|
117
|
+
|
|
118
|
+
if @has_password
|
|
119
|
+
return @has_password
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def process_call exp
|
|
127
|
+
if exp.method == :password
|
|
128
|
+
@has_password = exp
|
|
129
|
+
else
|
|
130
|
+
process_default exp
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
exp
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def process_ivar exp
|
|
137
|
+
if exp.value == :@password
|
|
138
|
+
@has_password = exp
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
exp
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def process_lvar exp
|
|
145
|
+
if exp.value == :password
|
|
146
|
+
@has_password = exp
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
exp
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for bypassing mass assignment protection
|
|
4
|
+
#with without_protection => true
|
|
5
|
+
#
|
|
6
|
+
#Only for Rails 3.1
|
|
7
|
+
class Brakeman::CheckWithoutProtection < Brakeman::BaseCheck
|
|
8
|
+
Brakeman::Checks.add self
|
|
9
|
+
|
|
10
|
+
@description = "Check for mass assignment using without_protection"
|
|
11
|
+
|
|
12
|
+
def run_check
|
|
13
|
+
if version_between? "0.0.0", "3.0.99"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return if active_record_models.empty?
|
|
18
|
+
|
|
19
|
+
Brakeman.debug "Finding all mass assignments"
|
|
20
|
+
calls = tracker.find_call :targets => active_record_models.keys, :methods => [:new,
|
|
21
|
+
:attributes=,
|
|
22
|
+
:update_attributes,
|
|
23
|
+
:update_attributes!,
|
|
24
|
+
:create,
|
|
25
|
+
:create!]
|
|
26
|
+
|
|
27
|
+
Brakeman.debug "Processing all mass assignments"
|
|
28
|
+
calls.each do |result|
|
|
29
|
+
process_result result
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#All results should be Model.new(...) or Model.attributes=() calls
|
|
34
|
+
def process_result res
|
|
35
|
+
call = res[:call]
|
|
36
|
+
last_arg = call.last_arg
|
|
37
|
+
|
|
38
|
+
if hash? last_arg and not call.original_line and not duplicate? res
|
|
39
|
+
|
|
40
|
+
if value = hash_access(last_arg, :without_protection)
|
|
41
|
+
if true? value
|
|
42
|
+
add_result res
|
|
43
|
+
|
|
44
|
+
if input = include_user_input?(call.arglist)
|
|
45
|
+
confidence = CONFIDENCE[:high]
|
|
46
|
+
elsif all_literals? call
|
|
47
|
+
return
|
|
48
|
+
else
|
|
49
|
+
confidence = CONFIDENCE[:med]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
warn :result => res,
|
|
53
|
+
:warning_type => "Mass Assignment",
|
|
54
|
+
:warning_code => :mass_assign_without_protection,
|
|
55
|
+
:message => "Unprotected mass assignment",
|
|
56
|
+
:code => call,
|
|
57
|
+
:user_input => input,
|
|
58
|
+
:confidence => confidence
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def all_literals? call
|
|
66
|
+
call.each_arg do |arg|
|
|
67
|
+
if hash? arg
|
|
68
|
+
hash_iterate arg do |k, v|
|
|
69
|
+
unless node_type? k, :str, :lit, :false, :true and node_type? v, :str, :lit, :false, :true
|
|
70
|
+
return false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
return false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Brakeman::CheckXMLDoS < Brakeman::BaseCheck
|
|
4
|
+
Brakeman::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for XML denial of service (CVE-2015-3227)"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
version = rails_version
|
|
10
|
+
|
|
11
|
+
fix_version = case
|
|
12
|
+
when version_between?("2.0.0", "3.2.21")
|
|
13
|
+
"3.2.22"
|
|
14
|
+
when version_between?("4.1.0", "4.1.10")
|
|
15
|
+
"4.1.11"
|
|
16
|
+
when version_between?("4.2.0", "4.2.1")
|
|
17
|
+
"4.2.2"
|
|
18
|
+
when version_between?("4.0.0", "4.0.99")
|
|
19
|
+
"4.2.2"
|
|
20
|
+
when (version.nil? and tracker.options[:rails3])
|
|
21
|
+
version = "3.x"
|
|
22
|
+
"3.2.22"
|
|
23
|
+
when (version.nil? and tracker.options[:rails4])
|
|
24
|
+
version = "4.x"
|
|
25
|
+
"4.2.2"
|
|
26
|
+
else
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return if has_workaround?
|
|
31
|
+
|
|
32
|
+
message = "Rails #{version} is vulnerable to denial of service via XML parsing (CVE-2015-3227). Upgrade to Rails version #{fix_version}"
|
|
33
|
+
|
|
34
|
+
warn :warning_type => "Denial of Service",
|
|
35
|
+
:warning_code => :CVE_2015_3227,
|
|
36
|
+
:message => message,
|
|
37
|
+
:confidence => CONFIDENCE[:med],
|
|
38
|
+
:gem_info => gemfile_or_environment,
|
|
39
|
+
:link_path => "https://groups.google.com/d/msg/rubyonrails-security/bahr2JLnxvk/x4EocXnHPp8J"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def has_workaround?
|
|
43
|
+
tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).any? do |match|
|
|
44
|
+
arg = match.call.first_arg
|
|
45
|
+
if string? arg
|
|
46
|
+
value = arg.value
|
|
47
|
+
value == 'Nokogiri' or value == 'LibXML'
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require 'brakeman/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Brakeman::CheckYAMLParsing < Brakeman::BaseCheck
|
|
4
|
+
Brakeman::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for YAML parsing vulnerabilities (CVE-2013-0156)"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
return unless version_between? "0.0.0", "2.3.14" or
|
|
10
|
+
version_between? "3.0.0", "3.0.18" or
|
|
11
|
+
version_between? "3.1.0", "3.1.9" or
|
|
12
|
+
version_between? "3.2.0", "3.2.10"
|
|
13
|
+
|
|
14
|
+
unless disabled_xml_parser? or disabled_xml_dangerous_types?
|
|
15
|
+
new_version = if version_between? "0.0.0", "2.3.14"
|
|
16
|
+
"2.3.15"
|
|
17
|
+
elsif version_between? "3.0.0", "3.0.18"
|
|
18
|
+
"3.0.19"
|
|
19
|
+
elsif version_between? "3.1.0", "3.1.9"
|
|
20
|
+
"3.1.10"
|
|
21
|
+
elsif version_between? "3.2.0", "3.2.10"
|
|
22
|
+
"3.2.11"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
message = "Rails #{rails_version} has a remote code execution vulnerability: upgrade to #{new_version} or disable XML parsing"
|
|
26
|
+
|
|
27
|
+
warn :warning_type => "Remote Code Execution",
|
|
28
|
+
:warning_code => :CVE_2013_0156,
|
|
29
|
+
:message => message,
|
|
30
|
+
:confidence => CONFIDENCE[:high],
|
|
31
|
+
:gem_info => gemfile_or_environment,
|
|
32
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#Warn if app accepts YAML
|
|
36
|
+
if version_between?("0.0.0", "2.3.14") and enabled_yaml_parser?
|
|
37
|
+
message = "Parsing YAML request parameters enables remote code execution: disable YAML parser"
|
|
38
|
+
|
|
39
|
+
warn :warning_type => "Remote Code Execution",
|
|
40
|
+
:warning_code => :CVE_2013_0156,
|
|
41
|
+
:message => message,
|
|
42
|
+
:confidence => CONFIDENCE[:high],
|
|
43
|
+
:gem_info => gemfile_or_environment,
|
|
44
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def disabled_xml_parser?
|
|
49
|
+
if version_between? "0.0.0", "2.3.14"
|
|
50
|
+
#Look for ActionController::Base.param_parsers.delete(Mime::XML)
|
|
51
|
+
params_parser = s(:call,
|
|
52
|
+
s(:colon2, s(:const, :ActionController), :Base),
|
|
53
|
+
:param_parsers)
|
|
54
|
+
|
|
55
|
+
matches = tracker.check_initializers(params_parser, :delete)
|
|
56
|
+
else
|
|
57
|
+
#Look for ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML)
|
|
58
|
+
matches = tracker.check_initializers(:"ActionDispatch::ParamsParser::DEFAULT_PARSERS", :delete)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless matches.empty?
|
|
62
|
+
mime_xml = s(:colon2, s(:const, :Mime), :XML)
|
|
63
|
+
|
|
64
|
+
matches.each do |result|
|
|
65
|
+
if result.call.first_arg == mime_xml
|
|
66
|
+
return true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#Look for ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
|
75
|
+
#in Rails 2.x apps
|
|
76
|
+
def enabled_yaml_parser?
|
|
77
|
+
param_parsers = s(:call,
|
|
78
|
+
s(:colon2, s(:const, :ActionController), :Base),
|
|
79
|
+
:param_parsers)
|
|
80
|
+
|
|
81
|
+
matches = tracker.check_initializers(param_parsers, :[]=)
|
|
82
|
+
|
|
83
|
+
mime_yaml = s(:colon2, s(:const, :Mime), :YAML)
|
|
84
|
+
|
|
85
|
+
matches.each do |result|
|
|
86
|
+
if result.call.first_arg == mime_yaml and
|
|
87
|
+
symbol? result.call.second_arg and
|
|
88
|
+
result.call.second_arg.value == :yaml
|
|
89
|
+
|
|
90
|
+
return true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def disabled_xml_dangerous_types?
|
|
98
|
+
if version_between? "0.0.0", "2.3.14"
|
|
99
|
+
matches = tracker.check_initializers(:"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", :delete)
|
|
100
|
+
else
|
|
101
|
+
matches = tracker.check_initializers(:"ActiveSupport::XmlMini::PARSING", :delete)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
symbols_off = false
|
|
105
|
+
yaml_off = false
|
|
106
|
+
|
|
107
|
+
matches.each do |result|
|
|
108
|
+
arg = result.call.first_arg
|
|
109
|
+
|
|
110
|
+
if string? arg
|
|
111
|
+
if arg.value == "yaml"
|
|
112
|
+
yaml_off = true
|
|
113
|
+
elsif arg.value == "symbol"
|
|
114
|
+
symbols_off = true
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
symbols_off and yaml_off
|
|
120
|
+
end
|
|
121
|
+
end
|