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.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. 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