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,505 @@
1
+ require 'railroader/processors/output_processor'
2
+ require 'railroader/processors/lib/processor_helper'
3
+ require 'railroader/warning'
4
+ require 'railroader/util'
5
+
6
+ #Basis of vulnerability checks.
7
+ class Railroader::BaseCheck < Railroader::SexpProcessor
8
+ include Railroader::ProcessorHelper
9
+ include Railroader::SafeCallHelper
10
+ include Railroader::Util
11
+ attr_reader :tracker, :warnings
12
+
13
+ # This is for legacy support.
14
+ # Use :high, :medium, or :low instead when creating warnings.
15
+ CONFIDENCE = Railroader::Warning::CONFIDENCE
16
+
17
+ Match = Struct.new(:type, :match)
18
+
19
+ class << self
20
+ attr_accessor :name
21
+
22
+ def inherited(subclass)
23
+ subclass.name = subclass.to_s.match(/^Railroader::(.*)$/)[1]
24
+ end
25
+ end
26
+
27
+ #Initialize Check with Checks.
28
+ def initialize(app_tree, tracker)
29
+ super()
30
+ @app_tree = app_tree
31
+ @results = [] #only to check for duplicates
32
+ @warnings = []
33
+ @tracker = tracker
34
+ @string_interp = false
35
+ @current_set = nil
36
+ @current_template = @current_module = @current_class = @current_method = nil
37
+ @active_record_models = nil
38
+ @mass_assign_disabled = nil
39
+ @has_user_input = nil
40
+ @safe_input_attributes = Set[:to_i, :to_f, :arel_table, :id]
41
+ @comparison_ops = Set[:==, :!=, :>, :<, :>=, :<=]
42
+ end
43
+
44
+ #Add result to result list, which is used to check for duplicates
45
+ def add_result result, location = nil
46
+ location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
47
+ location = location[:name] if location.is_a? Hash
48
+ location = location.name if location.is_a? Railroader::Collection
49
+ location = location.to_sym
50
+
51
+ if result.is_a? Hash
52
+ line = result[:call].original_line || result[:call].line
53
+ elsif sexp? result
54
+ line = result.original_line || result.line
55
+ else
56
+ raise ArgumentError
57
+ end
58
+
59
+ @results << [line, location, result]
60
+ end
61
+
62
+ #Default Sexp processing. Iterates over each value in the Sexp
63
+ #and processes them if they are also Sexps.
64
+ def process_default exp
65
+ exp.each do |e|
66
+ process e if sexp? e
67
+ end
68
+
69
+ exp
70
+ end
71
+
72
+ #Process calls and check if they include user input
73
+ def process_call exp
74
+ unless @comparison_ops.include? exp.method
75
+ process exp.target if sexp? exp.target
76
+ process_call_args exp
77
+ end
78
+
79
+ target = exp.target
80
+
81
+ unless always_safe_method? exp.method
82
+ if params? target
83
+ @has_user_input = Match.new(:params, exp)
84
+ elsif cookies? target
85
+ @has_user_input = Match.new(:cookies, exp)
86
+ elsif request_env? target
87
+ @has_user_input = Match.new(:request, exp)
88
+ elsif sexp? target and model_name? target[1] #TODO: Can this be target.target?
89
+ @has_user_input = Match.new(:model, exp)
90
+ end
91
+ end
92
+
93
+ exp
94
+ end
95
+
96
+ def process_if exp
97
+ #This is to ignore user input in condition
98
+ current_user_input = @has_user_input
99
+ process exp.condition
100
+ @has_user_input = current_user_input
101
+
102
+ process exp.then_clause if sexp? exp.then_clause
103
+ process exp.else_clause if sexp? exp.else_clause
104
+
105
+ exp
106
+ end
107
+
108
+ #Note that params are included in current expression
109
+ def process_params exp
110
+ @has_user_input = Match.new(:params, exp)
111
+ exp
112
+ end
113
+
114
+ #Note that cookies are included in current expression
115
+ def process_cookies exp
116
+ @has_user_input = Match.new(:cookies, exp)
117
+ exp
118
+ end
119
+
120
+ #Does not actually process string interpolation, but notes that it occurred.
121
+ def process_dstr exp
122
+ unless @string_interp # don't overwrite existing value
123
+ @string_interp = Match.new(:interp, exp)
124
+ end
125
+
126
+ process_default exp
127
+ end
128
+
129
+ private
130
+
131
+ def always_safe_method? meth
132
+ @safe_input_attributes.include? meth or
133
+ @comparison_ops.include? meth
134
+ end
135
+
136
+ def boolean_method? method
137
+ method[-1] == "?"
138
+ end
139
+
140
+ #Report a warning
141
+ def warn options
142
+ extra_opts = { :check => self.class.to_s }
143
+
144
+ warning = Railroader::Warning.new(options.merge(extra_opts))
145
+ warning.file = file_for warning
146
+ warning.relative_path = relative_path(warning.file)
147
+
148
+ @warnings << warning
149
+ end
150
+
151
+ #Run _exp_ through OutputProcessor to get a nice String.
152
+ def format_output exp
153
+ Railroader::OutputProcessor.new.format(exp).gsub(/\r|\n/, "")
154
+ end
155
+
156
+ #Checks if mass assignment is disabled globally in an initializer.
157
+ def mass_assign_disabled?
158
+ return @mass_assign_disabled unless @mass_assign_disabled.nil?
159
+
160
+ @mass_assign_disabled = false
161
+
162
+ if version_between?("3.1.0", "3.9.9") and
163
+ tracker.config.whitelist_attributes?
164
+
165
+ @mass_assign_disabled = true
166
+ elsif tracker.options[:rails4] && (!tracker.config.has_gem?(:protected_attributes) || tracker.config.whitelist_attributes?)
167
+
168
+ @mass_assign_disabled = true
169
+ else
170
+ #Check for ActiveRecord::Base.send(:attr_accessible, nil)
171
+ tracker.check_initializers(:"ActiveRecord::Base", :attr_accessible).each do |result|
172
+ call = result.call
173
+ if call? call
174
+ if call.first_arg == Sexp.new(:nil)
175
+ @mass_assign_disabled = true
176
+ break
177
+ end
178
+ end
179
+ end
180
+
181
+ unless @mass_assign_disabled
182
+ tracker.check_initializers(:"ActiveRecord::Base", :send).each do |result|
183
+ call = result.call
184
+ if call? call
185
+ if call.first_arg == Sexp.new(:lit, :attr_accessible) and call.second_arg == Sexp.new(:nil)
186
+ @mass_assign_disabled = true
187
+ break
188
+ end
189
+ end
190
+ end
191
+ end
192
+
193
+ unless @mass_assign_disabled
194
+ #Check for
195
+ # class ActiveRecord::Base
196
+ # attr_accessible nil
197
+ # end
198
+ matches = tracker.check_initializers([], :attr_accessible)
199
+
200
+ matches.each do |result|
201
+ if result.module == "ActiveRecord" and result.result_class == :Base
202
+ arg = result.call.first_arg
203
+
204
+ if arg.nil? or node_type? arg, :nil
205
+ @mass_assign_disabled = true
206
+ break
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ #There is a chance someone is using Rails 3.x and the `strong_parameters`
214
+ #gem and still using hack above, so this is a separate check for
215
+ #including ActiveModel::ForbiddenAttributesProtection in
216
+ #ActiveRecord::Base in an initializer.
217
+ if not @mass_assign_disabled and version_between?("3.1.0", "3.9.9") and tracker.config.has_gem? :strong_parameters
218
+ matches = tracker.check_initializers([], :include)
219
+ forbidden_protection = Sexp.new(:colon2, Sexp.new(:const, :ActiveModel), :ForbiddenAttributesProtection)
220
+
221
+ matches.each do |result|
222
+ if call? result.call and result.call.first_arg == forbidden_protection
223
+ @mass_assign_disabled = true
224
+ end
225
+ end
226
+
227
+ unless @mass_assign_disabled
228
+ matches = tracker.check_initializers(:"ActiveRecord::Base", [:send, :include])
229
+
230
+ matches.each do |result|
231
+ call = result.call
232
+ if call? call and (call.first_arg == forbidden_protection or call.second_arg == forbidden_protection)
233
+ @mass_assign_disabled = true
234
+ end
235
+ end
236
+ end
237
+ end
238
+
239
+ @mass_assign_disabled
240
+ end
241
+
242
+ def original? result
243
+ return false if result[:call].original_line or duplicate? result
244
+ add_result result
245
+ true
246
+ end
247
+
248
+ #This is to avoid reporting duplicates. Checks if the result has been
249
+ #reported already from the same line number.
250
+ def duplicate? result, location = nil
251
+ if result.is_a? Hash
252
+ line = result[:call].original_line || result[:call].line
253
+ elsif sexp? result
254
+ line = result.original_line || result.line
255
+ else
256
+ raise ArgumentError
257
+ end
258
+
259
+ location ||= (@current_template && @current_template.name) || @current_class || @current_module || @current_set || result[:location][:class] || result[:location][:template]
260
+
261
+ location = location[:name] if location.is_a? Hash
262
+ location = location.name if location.is_a? Railroader::Collection
263
+ location = location.to_sym
264
+
265
+ @results.each do |r|
266
+ if r[0] == line and r[1] == location
267
+ if tracker.options[:combine_locations]
268
+ return true
269
+ elsif r[2] == result
270
+ return true
271
+ end
272
+ end
273
+ end
274
+
275
+ false
276
+ end
277
+
278
+ #Checks if an expression contains string interpolation.
279
+ #
280
+ #Returns Match with :interp type if found.
281
+ def include_interp? exp
282
+ @string_interp = false
283
+ process exp
284
+ @string_interp
285
+ end
286
+
287
+ #Checks if _exp_ includes user input in the form of cookies, parameters,
288
+ #request environment, or model attributes.
289
+ #
290
+ #If found, returns a struct containing a type (:cookies, :params, :request, :model) and
291
+ #the matching expression (Match#type and Match#match).
292
+ #
293
+ #Returns false otherwise.
294
+ def include_user_input? exp
295
+ @has_user_input = false
296
+ process exp
297
+ @has_user_input
298
+ end
299
+
300
+ #This is used to check for user input being used directly.
301
+ #
302
+ ##If found, returns a struct containing a type (:cookies, :params, :request) and
303
+ #the matching expression (Match#type and Match#match).
304
+ #
305
+ #Returns false otherwise.
306
+ def has_immediate_user_input? exp
307
+ if exp.nil?
308
+ false
309
+ elsif call? exp and not always_safe_method? exp.method
310
+ if params? exp
311
+ return Match.new(:params, exp)
312
+ elsif cookies? exp
313
+ return Match.new(:cookies, exp)
314
+ elsif request_env? exp
315
+ return Match.new(:request, exp)
316
+ else
317
+ has_immediate_user_input? exp.target
318
+ end
319
+ elsif sexp? exp
320
+ case exp.node_type
321
+ when :dstr
322
+ exp.each do |e|
323
+ if sexp? e
324
+ match = has_immediate_user_input?(e)
325
+ return match if match
326
+ end
327
+ end
328
+ false
329
+ when :evstr
330
+ if sexp? exp.value
331
+ if exp.value.node_type == :rlist
332
+ exp.value.each_sexp do |e|
333
+ match = has_immediate_user_input?(e)
334
+ return match if match
335
+ end
336
+ false
337
+ else
338
+ has_immediate_user_input? exp.value
339
+ end
340
+ end
341
+ when :format
342
+ has_immediate_user_input? exp.value
343
+ when :if
344
+ (sexp? exp.then_clause and has_immediate_user_input? exp.then_clause) or
345
+ (sexp? exp.else_clause and has_immediate_user_input? exp.else_clause)
346
+ when :or
347
+ has_immediate_user_input? exp.lhs or
348
+ has_immediate_user_input? exp.rhs
349
+ else
350
+ false
351
+ end
352
+ end
353
+ end
354
+
355
+ #Checks for a model attribute at the top level of the
356
+ #expression.
357
+ def has_immediate_model? exp, out = nil
358
+ out = exp if out.nil?
359
+
360
+ if sexp? exp and exp.node_type == :output
361
+ exp = exp.value
362
+ end
363
+
364
+ if call? exp
365
+ target = exp.target
366
+ method = exp.method
367
+
368
+ if always_safe_method? method
369
+ false
370
+ elsif call? target and not method.to_s[-1,1] == "?"
371
+ if has_immediate_model?(target, out)
372
+ exp
373
+ else
374
+ false
375
+ end
376
+ elsif model_name? target
377
+ exp
378
+ else
379
+ false
380
+ end
381
+ elsif sexp? exp
382
+ case exp.node_type
383
+ when :dstr
384
+ exp.each do |e|
385
+ if sexp? e and match = has_immediate_model?(e, out)
386
+ return match
387
+ end
388
+ end
389
+ false
390
+ when :evstr
391
+ if sexp? exp.value
392
+ if exp.value.node_type == :rlist
393
+ exp.value.each_sexp do |e|
394
+ if match = has_immediate_model?(e, out)
395
+ return match
396
+ end
397
+ end
398
+ false
399
+ else
400
+ has_immediate_model? exp.value, out
401
+ end
402
+ end
403
+ when :format
404
+ has_immediate_model? exp.value, out
405
+ when :if
406
+ ((sexp? exp.then_clause and has_immediate_model? exp.then_clause, out) or
407
+ (sexp? exp.else_clause and has_immediate_model? exp.else_clause, out))
408
+ when :or
409
+ has_immediate_model? exp.lhs or
410
+ has_immediate_model? exp.rhs
411
+ else
412
+ false
413
+ end
414
+ end
415
+ end
416
+
417
+ #Checks if +exp+ is a model name.
418
+ #
419
+ #Prior to using this method, either @tracker must be set to
420
+ #the current tracker, or else @models should contain an array of the model
421
+ #names, which is available via tracker.models.keys
422
+ def model_name? exp
423
+ @models ||= @tracker.models.keys
424
+
425
+ if exp.is_a? Symbol
426
+ @models.include? exp
427
+ elsif call? exp and exp.target.nil? and exp.method == :current_user
428
+ true
429
+ elsif sexp? exp
430
+ @models.include? class_name(exp)
431
+ else
432
+ false
433
+ end
434
+ end
435
+
436
+ #Returns true if +target+ is in +exp+
437
+ def include_target? exp, target
438
+ return false unless call? exp
439
+
440
+ exp.each do |e|
441
+ return true if e == target or include_target? e, target
442
+ end
443
+
444
+ false
445
+ end
446
+
447
+ def lts_version? version
448
+ tracker.config.has_gem? :'railslts-version' and
449
+ version_between? version, "2.3.18.99", tracker.config.gem_version(:'railslts-version')
450
+ end
451
+
452
+
453
+ def version_between? low_version, high_version, current_version = nil
454
+ tracker.config.version_between? low_version, high_version, current_version
455
+ end
456
+
457
+ def gemfile_or_environment gem_name = :rails
458
+ if gem_name and info = tracker.config.get_gem(gem_name)
459
+ info
460
+ elsif @app_tree.exists?("Gemfile")
461
+ "Gemfile"
462
+ elsif @app_tree.exists?("gems.rb")
463
+ "gems.rb"
464
+ else
465
+ "config/environment.rb"
466
+ end
467
+ end
468
+
469
+ def self.description
470
+ @description
471
+ end
472
+
473
+ def active_record_models
474
+ return @active_record_models if @active_record_models
475
+
476
+ @active_record_models = {}
477
+
478
+ tracker.models.each do |name, model|
479
+ if model.ancestor? :"ActiveRecord::Base"
480
+ @active_record_models[name] = model
481
+ end
482
+ end
483
+
484
+ @active_record_models
485
+ end
486
+
487
+ def friendly_type_of input_type
488
+ if input_type.is_a? Match
489
+ input_type = input_type.type
490
+ end
491
+
492
+ case input_type
493
+ when :params
494
+ "parameter value"
495
+ when :cookies
496
+ "cookie value"
497
+ when :request
498
+ "request value"
499
+ when :model
500
+ "model attribute"
501
+ else
502
+ "user input"
503
+ end
504
+ end
505
+ end
@@ -0,0 +1,88 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ #Checks if password is stored in controller
4
+ #when using http_basic_authenticate_with
5
+ #
6
+ #Only for Rails >= 3.1
7
+ class Railroader::CheckBasicAuth < Railroader::BaseCheck
8
+ Railroader::Checks.add self
9
+
10
+ @description = "Checks for the use of http_basic_authenticate_with"
11
+
12
+ def run_check
13
+ return if version_between? "0.0.0", "3.0.99"
14
+
15
+ check_basic_auth_filter
16
+ check_basic_auth_request
17
+ end
18
+
19
+ def check_basic_auth_filter
20
+ controllers = tracker.controllers.select do |_name, c|
21
+ c.options[:http_basic_authenticate_with]
22
+ end
23
+
24
+ Hash[controllers].each do |name, controller|
25
+ controller.options[:http_basic_authenticate_with].each do |call|
26
+
27
+ if pass = get_password(call) and string? pass
28
+ warn :controller => name,
29
+ :warning_type => "Basic Auth",
30
+ :warning_code => :basic_auth_password,
31
+ :message => "Basic authentication password stored in source code",
32
+ :code => call,
33
+ :confidence => :high,
34
+ :file => controller.file
35
+ break
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Look for
42
+ # authenticate_or_request_with_http_basic do |username, password|
43
+ # username == "foo" && password == "bar"
44
+ # end
45
+ def check_basic_auth_request
46
+ tracker.find_call(:target => nil, :method => :authenticate_or_request_with_http_basic).each do |result|
47
+ if include_password_literal? result
48
+ warn :result => result,
49
+ :code => @include_password,
50
+ :warning_type => "Basic Auth",
51
+ :warning_code => :basic_auth_password,
52
+ :message => "Basic authentication password stored in source code",
53
+ :confidence => :high
54
+ end
55
+ end
56
+ end
57
+
58
+ # Check if the block of a result contains a comparison of password to string
59
+ def include_password_literal? result
60
+ @password_var = result[:block_args].last
61
+ @include_password = false
62
+ process result[:block]
63
+ @include_password
64
+ end
65
+
66
+ # Looks for :== calls on password var
67
+ def process_call exp
68
+ target = exp.target
69
+
70
+ if node_type?(target, :lvar) and
71
+ target.value == @password_var and
72
+ exp.method == :== and
73
+ string? exp.first_arg
74
+
75
+ @include_password = exp
76
+ end
77
+
78
+ exp
79
+ end
80
+
81
+ def get_password call
82
+ arg = call.first_arg
83
+
84
+ return false if arg.nil? or not hash? arg
85
+
86
+ hash_access(arg, :password)
87
+ end
88
+ end
@@ -0,0 +1,33 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckBasicAuthTimingAttack < Railroader::BaseCheck
4
+ Railroader::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_call
21
+ end
22
+
23
+ def check_basic_auth_call
24
+ tracker.find_call(target: nil, method: :http_basic_authenticate_with).each do |result|
25
+ warn :result => result,
26
+ :warning_type => "Timing Attack",
27
+ :warning_code => :CVE_2015_7576,
28
+ :message => "Basic authentication in Rails #{rails_version} is vulnerable to timing attacks. Upgrade to #@upgrade",
29
+ :confidence => :high,
30
+ :link => "https://groups.google.com/d/msg/rubyonrails-security/ANv0HDHEC3k/mt7wNGxbFQAJ"
31
+ end
32
+ end
33
+ end