railroader 4.3.4
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.md +1091 -0
- data/FEATURES +16 -0
- data/README.md +174 -0
- data/bin/railroader +8 -0
- data/lib/railroader/app_tree.rb +191 -0
- data/lib/railroader/call_index.rb +219 -0
- data/lib/railroader/checks/base_check.rb +505 -0
- data/lib/railroader/checks/check_basic_auth.rb +88 -0
- data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
- data/lib/railroader/checks/check_content_tag.rb +200 -0
- data/lib/railroader/checks/check_create_with.rb +74 -0
- data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
- data/lib/railroader/checks/check_default_routes.rb +86 -0
- data/lib/railroader/checks/check_deserialize.rb +56 -0
- data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
- data/lib/railroader/checks/check_digest_dos.rb +38 -0
- data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
- data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
- data/lib/railroader/checks/check_escape_function.rb +21 -0
- data/lib/railroader/checks/check_evaluation.rb +35 -0
- data/lib/railroader/checks/check_execute.rb +189 -0
- data/lib/railroader/checks/check_file_access.rb +71 -0
- data/lib/railroader/checks/check_file_disclosure.rb +35 -0
- data/lib/railroader/checks/check_filter_skipping.rb +31 -0
- data/lib/railroader/checks/check_forgery_setting.rb +81 -0
- data/lib/railroader/checks/check_header_dos.rb +31 -0
- data/lib/railroader/checks/check_i18n_xss.rb +48 -0
- data/lib/railroader/checks/check_jruby_xml.rb +36 -0
- data/lib/railroader/checks/check_json_encoding.rb +47 -0
- data/lib/railroader/checks/check_json_parsing.rb +107 -0
- data/lib/railroader/checks/check_link_to.rb +132 -0
- data/lib/railroader/checks/check_link_to_href.rb +146 -0
- data/lib/railroader/checks/check_mail_to.rb +49 -0
- data/lib/railroader/checks/check_mass_assignment.rb +196 -0
- data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
- data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
- data/lib/railroader/checks/check_model_attributes.rb +119 -0
- data/lib/railroader/checks/check_model_serialize.rb +67 -0
- data/lib/railroader/checks/check_nested_attributes.rb +38 -0
- data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
- data/lib/railroader/checks/check_number_to_currency.rb +74 -0
- data/lib/railroader/checks/check_permit_attributes.rb +43 -0
- data/lib/railroader/checks/check_quote_table_name.rb +40 -0
- data/lib/railroader/checks/check_redirect.rb +256 -0
- data/lib/railroader/checks/check_regex_dos.rb +68 -0
- data/lib/railroader/checks/check_render.rb +97 -0
- data/lib/railroader/checks/check_render_dos.rb +37 -0
- data/lib/railroader/checks/check_render_inline.rb +53 -0
- data/lib/railroader/checks/check_response_splitting.rb +21 -0
- data/lib/railroader/checks/check_route_dos.rb +42 -0
- data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
- data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
- data/lib/railroader/checks/check_secrets.rb +40 -0
- data/lib/railroader/checks/check_select_tag.rb +59 -0
- data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
- data/lib/railroader/checks/check_send.rb +47 -0
- data/lib/railroader/checks/check_send_file.rb +19 -0
- data/lib/railroader/checks/check_session_manipulation.rb +35 -0
- data/lib/railroader/checks/check_session_settings.rb +176 -0
- data/lib/railroader/checks/check_simple_format.rb +58 -0
- data/lib/railroader/checks/check_single_quotes.rb +101 -0
- data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
- data/lib/railroader/checks/check_sql.rb +700 -0
- data/lib/railroader/checks/check_sql_cves.rb +106 -0
- data/lib/railroader/checks/check_ssl_verify.rb +48 -0
- data/lib/railroader/checks/check_strip_tags.rb +89 -0
- data/lib/railroader/checks/check_symbol_dos.rb +71 -0
- data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
- data/lib/railroader/checks/check_translate_bug.rb +45 -0
- data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
- data/lib/railroader/checks/check_unscoped_find.rb +57 -0
- data/lib/railroader/checks/check_validation_regex.rb +116 -0
- data/lib/railroader/checks/check_weak_hash.rb +148 -0
- data/lib/railroader/checks/check_without_protection.rb +80 -0
- data/lib/railroader/checks/check_xml_dos.rb +45 -0
- data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
- data/lib/railroader/checks.rb +209 -0
- data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
- data/lib/railroader/commandline.rb +179 -0
- data/lib/railroader/differ.rb +66 -0
- data/lib/railroader/file_parser.rb +54 -0
- data/lib/railroader/format/style.css +133 -0
- data/lib/railroader/options.rb +339 -0
- data/lib/railroader/parsers/rails2_erubis.rb +6 -0
- data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
- data/lib/railroader/parsers/rails3_erubis.rb +81 -0
- data/lib/railroader/parsers/template_parser.rb +108 -0
- data/lib/railroader/processor.rb +102 -0
- data/lib/railroader/processors/alias_processor.rb +1229 -0
- data/lib/railroader/processors/base_processor.rb +295 -0
- data/lib/railroader/processors/config_processor.rb +14 -0
- data/lib/railroader/processors/controller_alias_processor.rb +278 -0
- data/lib/railroader/processors/controller_processor.rb +249 -0
- data/lib/railroader/processors/erb_template_processor.rb +77 -0
- data/lib/railroader/processors/erubis_template_processor.rb +92 -0
- data/lib/railroader/processors/gem_processor.rb +64 -0
- data/lib/railroader/processors/haml_template_processor.rb +191 -0
- data/lib/railroader/processors/lib/basic_processor.rb +37 -0
- data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
- data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
- data/lib/railroader/processors/lib/find_call.rb +183 -0
- data/lib/railroader/processors/lib/find_return_value.rb +166 -0
- data/lib/railroader/processors/lib/module_helper.rb +111 -0
- data/lib/railroader/processors/lib/processor_helper.rb +88 -0
- data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
- data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
- data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
- data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
- data/lib/railroader/processors/lib/render_helper.rb +181 -0
- data/lib/railroader/processors/lib/render_path.rb +107 -0
- data/lib/railroader/processors/lib/route_helper.rb +68 -0
- data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
- data/lib/railroader/processors/library_processor.rb +74 -0
- data/lib/railroader/processors/model_processor.rb +91 -0
- data/lib/railroader/processors/output_processor.rb +144 -0
- data/lib/railroader/processors/route_processor.rb +17 -0
- data/lib/railroader/processors/slim_template_processor.rb +111 -0
- data/lib/railroader/processors/template_alias_processor.rb +118 -0
- data/lib/railroader/processors/template_processor.rb +85 -0
- data/lib/railroader/report/config/remediation.yml +71 -0
- data/lib/railroader/report/ignore/config.rb +153 -0
- data/lib/railroader/report/ignore/interactive.rb +362 -0
- data/lib/railroader/report/pager.rb +112 -0
- data/lib/railroader/report/renderer.rb +24 -0
- data/lib/railroader/report/report_base.rb +292 -0
- data/lib/railroader/report/report_codeclimate.rb +79 -0
- data/lib/railroader/report/report_csv.rb +55 -0
- data/lib/railroader/report/report_hash.rb +23 -0
- data/lib/railroader/report/report_html.rb +216 -0
- data/lib/railroader/report/report_json.rb +45 -0
- data/lib/railroader/report/report_markdown.rb +107 -0
- data/lib/railroader/report/report_table.rb +117 -0
- data/lib/railroader/report/report_tabs.rb +17 -0
- data/lib/railroader/report/report_text.rb +198 -0
- data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
- data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/error_overview.html.erb +29 -0
- data/lib/railroader/report/templates/header.html.erb +58 -0
- data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
- data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
- data/lib/railroader/report/templates/overview.html.erb +38 -0
- data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
- data/lib/railroader/report/templates/template_overview.html.erb +21 -0
- data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
- data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
- data/lib/railroader/report.rb +88 -0
- data/lib/railroader/rescanner.rb +483 -0
- data/lib/railroader/scanner.rb +321 -0
- data/lib/railroader/tracker/collection.rb +93 -0
- data/lib/railroader/tracker/config.rb +154 -0
- data/lib/railroader/tracker/constants.rb +171 -0
- data/lib/railroader/tracker/controller.rb +161 -0
- data/lib/railroader/tracker/library.rb +17 -0
- data/lib/railroader/tracker/model.rb +90 -0
- data/lib/railroader/tracker/template.rb +33 -0
- data/lib/railroader/tracker.rb +362 -0
- data/lib/railroader/util.rb +503 -0
- data/lib/railroader/version.rb +3 -0
- data/lib/railroader/warning.rb +294 -0
- data/lib/railroader/warning_codes.rb +117 -0
- data/lib/railroader.rb +544 -0
- data/lib/ruby_parser/bm_sexp.rb +626 -0
- data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
- metadata +386 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for cross-site scripting vulnerability in mail_to :encode => :javascript
|
|
4
|
+
#with certain versions of Rails (< 2.3.11 or < 3.0.4).
|
|
5
|
+
#
|
|
6
|
+
#http://groups.google.com/group/rubyonrails-security/browse_thread/thread/f02a48ede8315f81
|
|
7
|
+
class Railroader::CheckMailTo < Railroader::BaseCheck
|
|
8
|
+
Railroader::Checks.add self
|
|
9
|
+
|
|
10
|
+
@description = "Checks for mail_to XSS vulnerability in certain versions"
|
|
11
|
+
|
|
12
|
+
def run_check
|
|
13
|
+
if (version_between? "2.3.0", "2.3.10" or version_between? "3.0.0", "3.0.3") and result = mail_to_javascript?
|
|
14
|
+
message = "Vulnerability in mail_to using javascript encoding (CVE-2011-0446). Upgrade to Rails version "
|
|
15
|
+
|
|
16
|
+
if version_between? "2.3.0", "2.3.10"
|
|
17
|
+
message << "2.3.11"
|
|
18
|
+
else
|
|
19
|
+
message << "3.0.4"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
warn :result => result,
|
|
23
|
+
:warning_type => "Mail Link",
|
|
24
|
+
:warning_code => :CVE_2011_0446,
|
|
25
|
+
:message => message,
|
|
26
|
+
:confidence => :high,
|
|
27
|
+
:gem_info => gemfile_or_environment,
|
|
28
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/8CpI7egxX4E/discussion"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#Check for javascript encoding of mail_to address
|
|
33
|
+
# mail_to email, name, :encode => :javascript
|
|
34
|
+
def mail_to_javascript?
|
|
35
|
+
Railroader.debug "Checking calls to mail_to for javascript encoding"
|
|
36
|
+
|
|
37
|
+
tracker.find_call(:target => false, :method => :mail_to).each do |result|
|
|
38
|
+
result[:call].each_arg do |arg|
|
|
39
|
+
if hash? arg
|
|
40
|
+
if option = hash_access(arg, :encode)
|
|
41
|
+
return result if symbol? option and option.value == :javascript
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
require 'set'
|
|
3
|
+
|
|
4
|
+
#Checks for mass assignments to models.
|
|
5
|
+
#
|
|
6
|
+
#See http://guides.rubyonrails.org/security.html#mass-assignment for details
|
|
7
|
+
class Railroader::CheckMassAssignment < Railroader::BaseCheck
|
|
8
|
+
Railroader::Checks.add self
|
|
9
|
+
|
|
10
|
+
@description = "Finds instances of mass assignment"
|
|
11
|
+
|
|
12
|
+
def initialize(*)
|
|
13
|
+
super
|
|
14
|
+
@mass_assign_calls = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run_check
|
|
18
|
+
check_mass_assignment
|
|
19
|
+
check_permit!
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def find_mass_assign_calls
|
|
23
|
+
return @mass_assign_calls if @mass_assign_calls
|
|
24
|
+
|
|
25
|
+
models = []
|
|
26
|
+
tracker.models.each do |name, m|
|
|
27
|
+
if m.is_a? Hash
|
|
28
|
+
p m
|
|
29
|
+
end
|
|
30
|
+
if m.unprotected_model?
|
|
31
|
+
models << name
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
return [] if models.empty?
|
|
36
|
+
|
|
37
|
+
Railroader.debug "Finding possible mass assignment calls on #{models.length} models"
|
|
38
|
+
@mass_assign_calls = tracker.find_call :chained => true, :targets => models, :methods => [:new,
|
|
39
|
+
:attributes=,
|
|
40
|
+
:update_attributes,
|
|
41
|
+
:update_attributes!,
|
|
42
|
+
:create,
|
|
43
|
+
:create!,
|
|
44
|
+
:build,
|
|
45
|
+
:first_or_create,
|
|
46
|
+
:first_or_create!,
|
|
47
|
+
:first_or_initialize!,
|
|
48
|
+
:assign_attributes,
|
|
49
|
+
:update
|
|
50
|
+
]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def check_mass_assignment
|
|
54
|
+
return if mass_assign_disabled?
|
|
55
|
+
|
|
56
|
+
Railroader.debug "Processing possible mass assignment calls"
|
|
57
|
+
find_mass_assign_calls.each do |result|
|
|
58
|
+
process_result result
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#All results should be Model.new(...) or Model.attributes=() calls
|
|
63
|
+
def process_result res
|
|
64
|
+
call = res[:call]
|
|
65
|
+
|
|
66
|
+
check = check_call call
|
|
67
|
+
|
|
68
|
+
if check and original? res
|
|
69
|
+
|
|
70
|
+
model = tracker.models[res[:chain].first]
|
|
71
|
+
|
|
72
|
+
attr_protected = (model and model.attr_protected)
|
|
73
|
+
|
|
74
|
+
if attr_protected and tracker.options[:ignore_attr_protected]
|
|
75
|
+
return
|
|
76
|
+
elsif input = include_user_input?(call.arglist)
|
|
77
|
+
first_arg = call.first_arg
|
|
78
|
+
|
|
79
|
+
if call? first_arg and (first_arg.method == :slice or first_arg.method == :only)
|
|
80
|
+
return
|
|
81
|
+
elsif not node_type? first_arg, :hash
|
|
82
|
+
if attr_protected
|
|
83
|
+
confidence = :medium
|
|
84
|
+
else
|
|
85
|
+
confidence = :high
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
return
|
|
89
|
+
end
|
|
90
|
+
elsif node_type? call.first_arg, :lit, :str
|
|
91
|
+
return
|
|
92
|
+
else
|
|
93
|
+
confidence = :weak
|
|
94
|
+
input = nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
warn :result => res,
|
|
98
|
+
:warning_type => "Mass Assignment",
|
|
99
|
+
:warning_code => :mass_assign_call,
|
|
100
|
+
:message => "Unprotected mass assignment",
|
|
101
|
+
:code => call,
|
|
102
|
+
:user_input => input,
|
|
103
|
+
:confidence => confidence
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
res
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#Want to ignore calls to Model.new that have no arguments
|
|
110
|
+
def check_call call
|
|
111
|
+
process_call_args call
|
|
112
|
+
|
|
113
|
+
if call.method == :update
|
|
114
|
+
arg = call.second_arg
|
|
115
|
+
else
|
|
116
|
+
arg = call.first_arg
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
if arg.nil? #empty new()
|
|
120
|
+
false
|
|
121
|
+
elsif hash? arg and not include_user_input? arg
|
|
122
|
+
false
|
|
123
|
+
elsif all_literal_args? call
|
|
124
|
+
false
|
|
125
|
+
else
|
|
126
|
+
true
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
LITERALS = Set[:lit, :true, :false, :nil, :string]
|
|
131
|
+
|
|
132
|
+
def all_literal_args? exp
|
|
133
|
+
if call? exp
|
|
134
|
+
exp.each_arg do |arg|
|
|
135
|
+
return false unless literal? arg
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
true
|
|
139
|
+
else
|
|
140
|
+
exp.all? do |arg|
|
|
141
|
+
literal? arg
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def literal? exp
|
|
148
|
+
if sexp? exp
|
|
149
|
+
if exp.node_type == :hash
|
|
150
|
+
all_literal_args? exp
|
|
151
|
+
else
|
|
152
|
+
LITERALS.include? exp.node_type
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
true
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Look for and warn about uses of Parameters#permit! for mass assignment
|
|
160
|
+
def check_permit!
|
|
161
|
+
tracker.find_call(:method => :permit!).each do |result|
|
|
162
|
+
if params? result[:call].target and not result[:chain].include? :slice
|
|
163
|
+
warn_on_permit! result
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Look for actual use of params in mass assignment to avoid
|
|
169
|
+
# warning about uses of Parameters#permit! without any mass assignment
|
|
170
|
+
# or when mass assignment is restricted by model instead.
|
|
171
|
+
def subsequent_mass_assignment? result
|
|
172
|
+
location = result[:location]
|
|
173
|
+
line = result[:call].line
|
|
174
|
+
find_mass_assign_calls.any? do |call|
|
|
175
|
+
call[:location] == location and
|
|
176
|
+
params? call[:call].first_arg and
|
|
177
|
+
call[:call].line >= line
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def warn_on_permit! result
|
|
182
|
+
return unless original? result
|
|
183
|
+
|
|
184
|
+
confidence = if subsequent_mass_assignment? result
|
|
185
|
+
:high
|
|
186
|
+
else
|
|
187
|
+
:medium
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
warn :result => result,
|
|
191
|
+
:warning_type => "Mass Assignment",
|
|
192
|
+
:warning_code => :mass_assign_permit!,
|
|
193
|
+
:message => "Parameters should be whitelisted for mass assignment",
|
|
194
|
+
:confidence => confidence
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckMimeTypeDoS < Railroader::BaseCheck
|
|
4
|
+
Railroader::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 => :medium,
|
|
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,55 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
# Author: Paul Deardorff (themetric)
|
|
4
|
+
# Checks models to see if important foreign keys
|
|
5
|
+
# or attributes are exposed as attr_accessible when
|
|
6
|
+
# they probably shouldn't be.
|
|
7
|
+
|
|
8
|
+
class Railroader::CheckModelAttrAccessible < Railroader::BaseCheck
|
|
9
|
+
Railroader::Checks.add self
|
|
10
|
+
|
|
11
|
+
@description = "Reports models which have dangerous attributes defined under the attr_accessible whitelist."
|
|
12
|
+
|
|
13
|
+
SUSP_ATTRS = [
|
|
14
|
+
[:admin, :high], # Very dangerous unless some Rails authorization used
|
|
15
|
+
[:role, :medium],
|
|
16
|
+
[:banned, :medium],
|
|
17
|
+
[:account_id, :high],
|
|
18
|
+
[/\S*_id(s?)\z/, :weak] # All other foreign keys have weak/low confidence
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
def run_check
|
|
22
|
+
check_models do |name, model|
|
|
23
|
+
model.attr_accessible.each do |attribute|
|
|
24
|
+
next if role_limited? model, attribute
|
|
25
|
+
|
|
26
|
+
SUSP_ATTRS.each do |susp_attr, confidence|
|
|
27
|
+
if susp_attr.is_a?(Regexp) and susp_attr =~ attribute.to_s or susp_attr == attribute
|
|
28
|
+
warn :model => name,
|
|
29
|
+
:file => model.file,
|
|
30
|
+
:warning_type => "Mass Assignment",
|
|
31
|
+
:warning_code => :dangerous_attr_accessible,
|
|
32
|
+
:message => "Potentially dangerous attribute available for mass assignment",
|
|
33
|
+
:confidence => confidence,
|
|
34
|
+
:code => Sexp.new(:lit, attribute)
|
|
35
|
+
break # Prevent from matching single attr multiple times
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def role_limited? model, attribute
|
|
43
|
+
role_accessible = model.role_accessible
|
|
44
|
+
return if role_accessible.nil?
|
|
45
|
+
role_accessible.include? attribute
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def check_models
|
|
49
|
+
tracker.models.each do |name, model|
|
|
50
|
+
if !model.attr_accessible.nil?
|
|
51
|
+
yield name, model
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check if mass assignment is used with models
|
|
4
|
+
#which inherit from ActiveRecord::Base.
|
|
5
|
+
#
|
|
6
|
+
#If tracker.options[:collapse_mass_assignment] is +true+ (default), all models
|
|
7
|
+
#which do not use attr_accessible will be reported in a single warning
|
|
8
|
+
class Railroader::CheckModelAttributes < Railroader::BaseCheck
|
|
9
|
+
Railroader::Checks.add self
|
|
10
|
+
|
|
11
|
+
@description = "Reports models which do not use attr_restricted and warns on models that use attr_protected"
|
|
12
|
+
|
|
13
|
+
def run_check
|
|
14
|
+
return if mass_assign_disabled?
|
|
15
|
+
|
|
16
|
+
#Roll warnings into one warning for all models
|
|
17
|
+
if tracker.options[:collapse_mass_assignment]
|
|
18
|
+
no_accessible_names = []
|
|
19
|
+
protected_names = []
|
|
20
|
+
|
|
21
|
+
check_models do |name, model|
|
|
22
|
+
if model.attr_protected.nil?
|
|
23
|
+
no_accessible_names << name.to_s
|
|
24
|
+
elsif not tracker.options[:ignore_attr_protected]
|
|
25
|
+
protected_names << name.to_s
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
unless no_accessible_names.empty?
|
|
30
|
+
warn :model => no_accessible_names.sort.join(", "),
|
|
31
|
+
:warning_type => "Attribute Restriction",
|
|
32
|
+
:warning_code => :no_attr_accessible,
|
|
33
|
+
:message => "Mass assignment is not restricted using attr_accessible",
|
|
34
|
+
:confidence => :high
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
unless protected_names.empty?
|
|
38
|
+
message, confidence, link = check_for_attr_protected_bypass
|
|
39
|
+
|
|
40
|
+
if link
|
|
41
|
+
warning_code = :CVE_2013_0276
|
|
42
|
+
else
|
|
43
|
+
warning_code = :attr_protected_used
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
warn :model => protected_names.sort.join(", "),
|
|
47
|
+
:warning_type => "Attribute Restriction",
|
|
48
|
+
:warning_code => warning_code,
|
|
49
|
+
:message => message,
|
|
50
|
+
:confidence => confidence,
|
|
51
|
+
:link => link
|
|
52
|
+
end
|
|
53
|
+
else #Output one warning per model
|
|
54
|
+
|
|
55
|
+
check_models do |name, model|
|
|
56
|
+
if model.attr_protected.nil?
|
|
57
|
+
warn :model => name,
|
|
58
|
+
:file => model.file,
|
|
59
|
+
:line => model.top_line,
|
|
60
|
+
:warning_type => "Attribute Restriction",
|
|
61
|
+
:warning_code => :no_attr_accessible,
|
|
62
|
+
:message => "Mass assignment is not restricted using attr_accessible",
|
|
63
|
+
:confidence => :high
|
|
64
|
+
elsif not tracker.options[:ignore_attr_protected]
|
|
65
|
+
message, confidence, link = check_for_attr_protected_bypass
|
|
66
|
+
|
|
67
|
+
if link
|
|
68
|
+
warning_code = :CVE_2013_0276
|
|
69
|
+
else
|
|
70
|
+
warning_code = :attr_protected_used
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
warn :model => name,
|
|
74
|
+
:file => model.file,
|
|
75
|
+
:line => model.attr_protected.first.line,
|
|
76
|
+
:warning_type => "Attribute Restriction",
|
|
77
|
+
:warning_code => warning_code,
|
|
78
|
+
:message => message,
|
|
79
|
+
:confidence => confidence
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def check_models
|
|
86
|
+
tracker.models.each do |name, model|
|
|
87
|
+
if model.unprotected_model?
|
|
88
|
+
yield name, model
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def check_for_attr_protected_bypass
|
|
94
|
+
upgrade_version = case
|
|
95
|
+
when version_between?("2.0.0", "2.3.16")
|
|
96
|
+
"2.3.17"
|
|
97
|
+
when version_between?("3.0.0", "3.0.99")
|
|
98
|
+
"3.2.11"
|
|
99
|
+
when version_between?("3.1.0", "3.1.10")
|
|
100
|
+
"3.1.11"
|
|
101
|
+
when version_between?("3.2.0", "3.2.11")
|
|
102
|
+
"3.2.12"
|
|
103
|
+
else
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if upgrade_version
|
|
108
|
+
message = "attr_protected is bypassable in #{rails_version}, use attr_accessible or upgrade to #{upgrade_version}"
|
|
109
|
+
confidence = :high
|
|
110
|
+
link = "https://groups.google.com/d/topic/rubyonrails-security/AFBKNY7VSH8/discussion"
|
|
111
|
+
else
|
|
112
|
+
message = "attr_accessible is recommended over attr_protected"
|
|
113
|
+
confidence = :medium
|
|
114
|
+
link = nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
return message, confidence, link
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckModelSerialize < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Report uses of serialize in versions vulnerable to CVE-2013-0277"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
@upgrade_version = case
|
|
10
|
+
when version_between?("2.0.0", "2.3.16")
|
|
11
|
+
"2.3.17"
|
|
12
|
+
when version_between?("3.0.0", "3.0.99")
|
|
13
|
+
"3.2.11"
|
|
14
|
+
else
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
return unless @upgrade_version
|
|
19
|
+
|
|
20
|
+
tracker.models.each do |_name, model|
|
|
21
|
+
check_for_serialize model
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#High confidence warning on serialized, unprotected attributes.
|
|
26
|
+
#Medium confidence warning for serialized, protected attributes.
|
|
27
|
+
def check_for_serialize model
|
|
28
|
+
if serialized_attrs = model.options[:serialize]
|
|
29
|
+
attrs = Set.new
|
|
30
|
+
|
|
31
|
+
serialized_attrs.each do |arglist|
|
|
32
|
+
arglist.each do |arg|
|
|
33
|
+
attrs << arg if symbol? arg
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if unsafe_attrs = model.attr_accessible
|
|
38
|
+
attrs.delete_if { |attr| not unsafe_attrs.include? attr.value }
|
|
39
|
+
elsif protected_attrs = model.attr_protected
|
|
40
|
+
safe_attrs = Set.new
|
|
41
|
+
|
|
42
|
+
protected_attrs.each do |arglist|
|
|
43
|
+
arglist.each do |arg|
|
|
44
|
+
safe_attrs << arg if symbol? arg
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
attrs.delete_if { |attr| safe_attrs.include? attr }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if attrs.empty?
|
|
52
|
+
confidence = :medium
|
|
53
|
+
else
|
|
54
|
+
confidence = :high
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
warn :model => model.name,
|
|
58
|
+
:warning_type => "Remote Code Execution",
|
|
59
|
+
:warning_code => :CVE_2013_0277,
|
|
60
|
+
:message => "Serialized attributes are vulnerable in Rails #{rails_version}, upgrade to #{@upgrade_version} or patch.",
|
|
61
|
+
:confidence => confidence,
|
|
62
|
+
:link => "https://groups.google.com/d/topic/rubyonrails-security/KtmwSbEpzrU/discussion",
|
|
63
|
+
:file => model.file,
|
|
64
|
+
:line => model.top_line
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for vulnerability in nested attributes in Rails 2.3.9 and 3.0.0
|
|
4
|
+
#http://groups.google.com/group/rubyonrails-security/browse_thread/thread/f9f913d328dafe0c
|
|
5
|
+
class Railroader::CheckNestedAttributes < Railroader::BaseCheck
|
|
6
|
+
Railroader::Checks.add self
|
|
7
|
+
|
|
8
|
+
@description = "Checks for nested attributes vulnerability in Rails 2.3.9 and 3.0.0"
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
version = rails_version
|
|
12
|
+
|
|
13
|
+
if (version == "2.3.9" or version == "3.0.0") and uses_nested_attributes?
|
|
14
|
+
message = "Vulnerability in nested attributes (CVE-2010-3933). Upgrade to Rails version "
|
|
15
|
+
|
|
16
|
+
if version == "2.3.9"
|
|
17
|
+
message << "2.3.10"
|
|
18
|
+
else
|
|
19
|
+
message << "3.0.1"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
warn :warning_type => "Nested Attributes",
|
|
23
|
+
:warning_code => :CVE_2010_3933,
|
|
24
|
+
:message => message,
|
|
25
|
+
:confidence => :high,
|
|
26
|
+
:gem_info => gemfile_or_environment,
|
|
27
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/-fkT0yja_gw/discussion"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def uses_nested_attributes?
|
|
32
|
+
active_record_models.each do |_name, model|
|
|
33
|
+
return true if model.options[:accepts_nested_attributes_for]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#https://groups.google.com/d/msg/rubyonrails-security/cawsWcQ6c8g/tegZtYdbFQAJ
|
|
4
|
+
class Railroader::CheckNestedAttributesBypass < Railroader::BaseCheck
|
|
5
|
+
Railroader::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 => :medium,
|
|
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
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckNumberToCurrency < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for number helpers XSS vulnerabilities in certain versions"
|
|
7
|
+
|
|
8
|
+
def initialize(*)
|
|
9
|
+
super
|
|
10
|
+
@found_any = false
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run_check
|
|
14
|
+
if version_between? "2.0.0", "2.3.18" or
|
|
15
|
+
version_between? "3.0.0", "3.2.16" or
|
|
16
|
+
version_between? "4.0.0", "4.0.2"
|
|
17
|
+
|
|
18
|
+
return if lts_version? "2.3.18.8"
|
|
19
|
+
|
|
20
|
+
check_number_helper_usage
|
|
21
|
+
generic_warning unless @found_any
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def generic_warning
|
|
26
|
+
message = "Rails #{rails_version} has a vulnerability in number helpers (CVE-2014-0081). Upgrade to Rails version "
|
|
27
|
+
|
|
28
|
+
if version_between? "2.3.0", "3.2.16"
|
|
29
|
+
message << "3.2.17"
|
|
30
|
+
else
|
|
31
|
+
message << "4.0.3"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
warn :warning_type => "Cross-Site Scripting",
|
|
35
|
+
:warning_code => :CVE_2014_0081,
|
|
36
|
+
:message => message,
|
|
37
|
+
:confidence => :medium,
|
|
38
|
+
:gem_info => gemfile_or_environment,
|
|
39
|
+
:link_path => "https://groups.google.com/d/msg/ruby-security-ann/9WiRn2nhfq0/2K2KRB4LwCMJ"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def check_number_helper_usage
|
|
43
|
+
number_methods = [:number_to_currency, :number_to_percentage, :number_to_human]
|
|
44
|
+
tracker.find_call(:target => false, :methods => number_methods).each do |result|
|
|
45
|
+
arg = result[:call].second_arg
|
|
46
|
+
next unless arg
|
|
47
|
+
|
|
48
|
+
if not check_helper_option(result, arg) and hash? arg
|
|
49
|
+
hash_iterate(arg) do |_key, value|
|
|
50
|
+
break if check_helper_option(result, value)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def check_helper_option result, exp
|
|
57
|
+
if match = (has_immediate_user_input? exp or has_immediate_model? exp)
|
|
58
|
+
warn_on_number_helper result, match
|
|
59
|
+
@found_any = true
|
|
60
|
+
else
|
|
61
|
+
false
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def warn_on_number_helper result, match
|
|
66
|
+
warn :result => result,
|
|
67
|
+
:warning_type => "Cross-Site Scripting",
|
|
68
|
+
:warning_code => :CVE_2014_0081_call,
|
|
69
|
+
:message => "Format options in #{result[:call].method} are not safe in Rails #{rails_version}",
|
|
70
|
+
:confidence => :high,
|
|
71
|
+
:link_path => "https://groups.google.com/d/msg/ruby-security-ann/9WiRn2nhfq0/2K2KRB4LwCMJ",
|
|
72
|
+
:user_input => match
|
|
73
|
+
end
|
|
74
|
+
end
|