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,31 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ #Check for filter skipping vulnerability
4
+ #http://groups.google.com/group/rubyonrails-security/browse_thread/thread/3420ac71aed312d6
5
+ class Railroader::CheckFilterSkipping < Railroader::BaseCheck
6
+ Railroader::Checks.add self
7
+
8
+ @description = "Checks for versions 3.0-3.0.9 which had a vulnerability in filters"
9
+
10
+ def run_check
11
+ if version_between?('3.0.0', '3.0.9') and uses_arbitrary_actions?
12
+
13
+ warn :warning_type => "Default Routes",
14
+ :warning_code => :CVE_2011_2929,
15
+ :message => "Versions before 3.0.10 have a vulnerability which allows filters to be bypassed: CVE-2011-2929",
16
+ :confidence => :high,
17
+ :gem_info => gemfile_or_environment,
18
+ :link_path => "https://groups.google.com/d/topic/rubyonrails-security/NCCsca7TEtY/discussion"
19
+ end
20
+ end
21
+
22
+ def uses_arbitrary_actions?
23
+ tracker.routes.each do |_name, actions|
24
+ if actions.include? :allow_all_actions
25
+ return true
26
+ end
27
+ end
28
+
29
+ false
30
+ end
31
+ end
@@ -0,0 +1,81 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ #Checks that +protect_from_forgery+ is set in the ApplicationController.
4
+ #
5
+ #Also warns for CSRF weakness in certain versions of Rails:
6
+ #http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2d95a3cc23e03665
7
+ class Railroader::CheckForgerySetting < Railroader::BaseCheck
8
+ Railroader::Checks.add self
9
+
10
+ @description = "Verifies that protect_from_forgery is enabled in direct subclasses of ActionController::Base"
11
+
12
+ def run_check
13
+ return if tracker.config.default_protect_from_forgery?
14
+
15
+ tracker.controllers
16
+ .select { |_, controller| controller.parent == :"ActionController::Base" }
17
+ .each do |name, controller|
18
+ if controller and not controller.protect_from_forgery?
19
+ csrf_warning :controller => name,
20
+ :warning_code => :csrf_protection_missing,
21
+ :message => "'protect_from_forgery' should be called in #{name}",
22
+ :file => controller.file,
23
+ :line => controller.top_line
24
+ elsif version_between? "4.0.0", "100.0.0" and forgery_opts = controller.options[:protect_from_forgery]
25
+ unless forgery_opts.is_a?(Array) and sexp?(forgery_opts.first) and
26
+ access_arg = hash_access(forgery_opts.first.first_arg, :with) and symbol? access_arg and
27
+ access_arg.value == :exception
28
+
29
+ args = {
30
+ :controller => name,
31
+ :warning_type => "Cross-Site Request Forgery",
32
+ :warning_code => :csrf_not_protected_by_raising_exception,
33
+ :message => "protect_from_forgery should be configured with 'with: :exception'",
34
+ :confidence => :medium,
35
+ :file => controller.file
36
+ }
37
+
38
+ args.merge!(:code => forgery_opts.first) if forgery_opts.is_a?(Array)
39
+
40
+ csrf_warning args
41
+ end
42
+
43
+ end
44
+
45
+ if controller.options[:protect_from_forgery]
46
+ check_cve_2011_0447
47
+ end
48
+ end
49
+ end
50
+
51
+ def csrf_warning opts
52
+ opts = {
53
+ :controller => :ApplicationController,
54
+ :warning_type => "Cross-Site Request Forgery",
55
+ :confidence => :high
56
+ }.merge opts
57
+
58
+ warn opts
59
+ end
60
+
61
+ def check_cve_2011_0447
62
+ @warned_cve_2011_0447 ||= false
63
+ return if @warned_cve_2011_0447
64
+
65
+ if version_between? "2.1.0", "2.3.10"
66
+ new_version = "2.3.11"
67
+ elsif version_between? "3.0.0", "3.0.3"
68
+ new_version = "3.0.4"
69
+ else
70
+ return
71
+ end
72
+
73
+ @warned_cve_2011_0447 = true # only warn once
74
+
75
+ csrf_warning :warning_code => :CVE_2011_0447,
76
+ :message => "CSRF protection is flawed in unpatched versions of Rails #{rails_version} (CVE-2011-0447). Upgrade to #{new_version} or apply patches as needed",
77
+ :gem_info => gemfile_or_environment,
78
+ :file => nil,
79
+ :link_path => "https://groups.google.com/d/topic/rubyonrails-security/LZWjzCPgNmU/discussion"
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckHeaderDoS < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for header DoS (CVE-2013-6414)"
7
+
8
+ def run_check
9
+ if (version_between? "3.0.0", "3.2.15" or version_between? "4.0.0", "4.0.1") and not has_workaround?
10
+ message = "Rails #{rails_version} has a denial of service vulnerability (CVE-2013-6414). Upgrade to Rails version "
11
+
12
+ if version_between? "3.0.0", "3.2.15"
13
+ message << "3.2.16"
14
+ else
15
+ message << "4.0.2"
16
+ end
17
+
18
+ warn :warning_type => "Denial of Service",
19
+ :warning_code => :CVE_2013_6414,
20
+ :message => message,
21
+ :confidence => :medium,
22
+ :gem_info => gemfile_or_environment,
23
+ :link_path => "https://groups.google.com/d/msg/ruby-security-ann/A-ebV4WxzKg/KNPTbX8XAQUJ"
24
+ end
25
+ end
26
+
27
+ def has_workaround?
28
+ tracker.check_initializers(:ActiveSupport, :on_load).any? and
29
+ tracker.check_initializers(:"ActionView::LookupContext::DetailsKey", :class_eval).any?
30
+ end
31
+ end
@@ -0,0 +1,48 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckI18nXSS < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for i18n XSS (CVE-2013-4491)"
7
+
8
+ def run_check
9
+ if (version_between? "3.0.6", "3.2.15" or version_between? "4.0.0", "4.0.1") and not has_workaround?
10
+ message = "Rails #{rails_version} has an XSS vulnerability in i18n (CVE-2013-4491). Upgrade to Rails version "
11
+ i18n_gem = tracker.config.gem_version :i18n
12
+
13
+ if version_between? "3.0.6", "3.1.99" and version_before i18n_gem, "0.5.1"
14
+ message << "3.2.16 or i18n 0.5.1"
15
+ elsif version_between? "3.2.0", "4.0.1" and version_before i18n_gem, "0.6.6"
16
+ message << "4.0.2 or i18n 0.6.6"
17
+ else
18
+ return
19
+ end
20
+
21
+ warn :warning_type => "Cross-Site Scripting",
22
+ :warning_code => :CVE_2013_4491,
23
+ :message => message,
24
+ :confidence => :medium,
25
+ :gem_info => gemfile_or_environment(:i18n),
26
+ :link_path => "https://groups.google.com/d/msg/ruby-security-ann/pLrh6DUw998/bLFEyIO4k_EJ"
27
+ end
28
+ end
29
+
30
+ def version_before gem_version, target
31
+ return true unless gem_version
32
+ gem_version.split('.').map(&:to_i).zip(target.split('.').map(&:to_i)).each do |gv, t|
33
+ if gv < t
34
+ return true
35
+ elsif gv > t
36
+ return false
37
+ end
38
+ end
39
+
40
+ false
41
+ end
42
+
43
+ def has_workaround?
44
+ tracker.check_initializers(:I18n, :const_defined?).any? do |match|
45
+ match.last.first_arg == s(:lit, :MissingTranslation)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,36 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckJRubyXML < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for versions with JRuby XML parsing backend"
7
+
8
+ def run_check
9
+ return unless RUBY_PLATFORM == "java"
10
+
11
+ fix_version = case
12
+ when version_between?('3.0.0', '3.0.99')
13
+ '3.2.13'
14
+ when version_between?('3.1.0', '3.1.11')
15
+ '3.1.12'
16
+ when version_between?('3.2.0', '3.2.12')
17
+ '3.2.13'
18
+ else
19
+ return
20
+ end
21
+
22
+ #Check for workaround
23
+ tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).each do |result|
24
+ arg = result.call.first_arg
25
+
26
+ return if string? arg and arg.value == "REXML"
27
+ end
28
+
29
+ warn :warning_type => "File Access",
30
+ :warning_code => :CVE_2013_1856,
31
+ :message => "Rails #{rails_version} with JRuby has a vulnerability in XML parser: upgrade to #{fix_version} or patch",
32
+ :confidence => :high,
33
+ :gem_info => gemfile_or_environment,
34
+ :link => "https://groups.google.com/d/msg/rubyonrails-security/KZwsQbYsOiI/5kUV7dSCJGwJ"
35
+ end
36
+ end
@@ -0,0 +1,47 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckJSONEncoding < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for missing JSON encoding (CVE-2015-3226)"
7
+
8
+ def run_check
9
+ if (version_between? "4.1.0", "4.1.10" or version_between? "4.2.0", "4.2.1") and not has_workaround?
10
+ message = "Rails #{rails_version} does not encode JSON keys (CVE-2015-3226). Upgrade to Rails version "
11
+
12
+ if version_between? "4.1.0", "4.1.10"
13
+ message << "4.1.11"
14
+ else
15
+ message << "4.2.2"
16
+ end
17
+
18
+ if tracker.find_call(:methods => [:to_json, :encode]).any?
19
+ confidence = :high
20
+ else
21
+ confidence = :medium
22
+ end
23
+
24
+ warn :warning_type => "Cross-Site Scripting",
25
+ :warning_code => :CVE_2015_3226,
26
+ :message => message,
27
+ :confidence => confidence,
28
+ :gem_info => gemfile_or_environment,
29
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/7VlB_pck3hU/3QZrGIaQW6cJ"
30
+ end
31
+ end
32
+
33
+ def has_workaround?
34
+ workaround = s(:module, :ActiveSupport,
35
+ s(:module, :JSON,
36
+ s(:module, :Encoding,
37
+ s(:call, nil, :private),
38
+ s(:class, :EscapedString, nil,
39
+ s(:defn, :to_s,
40
+ s(:args),
41
+ s(:self))))))
42
+
43
+ tracker.initializers.any? do |_name, initializer|
44
+ initializer == workaround
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,107 @@
1
+ require 'railroader/checks/base_check'
2
+
3
+ class Railroader::CheckJSONParsing < Railroader::BaseCheck
4
+ Railroader::Checks.add self
5
+
6
+ @description = "Checks for JSON parsing vulnerabilities CVE-2013-0333 and CVE-2013-0269"
7
+
8
+ def run_check
9
+ check_cve_2013_0333
10
+ check_cve_2013_0269
11
+ end
12
+
13
+ def check_cve_2013_0333
14
+ return unless version_between? "0.0.0", "2.3.15" or version_between? "3.0.0", "3.0.19"
15
+
16
+ unless uses_yajl? or uses_gem_backend?
17
+ new_version = if version_between? "0.0.0", "2.3.14"
18
+ "2.3.16"
19
+ elsif version_between? "3.0.0", "3.0.19"
20
+ "3.0.20"
21
+ end
22
+
23
+ message = "Rails #{rails_version} has a serious JSON parsing vulnerability: upgrade to #{new_version} or patch"
24
+ if uses_yajl?
25
+ gem_info = gemfile_or_environment(:yajl)
26
+ else
27
+ gem_info = gemfile_or_environment
28
+ end
29
+
30
+ warn :warning_type => "Remote Code Execution",
31
+ :warning_code => :CVE_2013_0333,
32
+ :message => message,
33
+ :confidence => :high,
34
+ :gem_info => gem_info,
35
+ :link_path => "https://groups.google.com/d/topic/rubyonrails-security/1h2DR63ViGo/discussion"
36
+ end
37
+ end
38
+
39
+ #Check if `yajl` is included in Gemfile
40
+ def uses_yajl?
41
+ tracker.config.has_gem? :yajl
42
+ end
43
+
44
+ #Check for `ActiveSupport::JSON.backend = "JSONGem"`
45
+ def uses_gem_backend?
46
+ matches = tracker.check_initializers(:'ActiveSupport::JSON', :backend=)
47
+
48
+ unless matches.empty?
49
+ json_gem = s(:str, "JSONGem")
50
+
51
+ matches.each do |result|
52
+ if result.call.first_arg == json_gem
53
+ return true
54
+ end
55
+ end
56
+ end
57
+
58
+ false
59
+ end
60
+
61
+ def check_cve_2013_0269
62
+ [:json, :json_pure].each do |name|
63
+ gem_hash = tracker.config.get_gem name
64
+ check_json_version name, gem_hash[:version] if gem_hash and gem_hash[:version]
65
+ end
66
+ end
67
+
68
+ def check_json_version name, version
69
+ return if version >= "1.7.7" or
70
+ (version >= "1.6.8" and version < "1.7.0") or
71
+ (version >= "1.5.5" and version < "1.6.0")
72
+
73
+ warning_type = "Denial of Service"
74
+ confidence = :medium
75
+ message = "#{name} gem version #{version} has a symbol creation vulnerablity: upgrade to "
76
+
77
+ if version >= "1.7.0"
78
+ confidence = :high
79
+ warning_type = "Remote Code Execution"
80
+ message = "#{name} gem version #{version} has a remote code vulnerablity: upgrade to 1.7.7"
81
+ elsif version >= "1.6.0"
82
+ message << "1.6.8"
83
+ elsif version >= "1.5.0"
84
+ message << "1.5.5"
85
+ else
86
+ confidence = :weak
87
+ message << "1.5.5"
88
+ end
89
+
90
+ if confidence == :medium and uses_json_parse?
91
+ confidence = :high
92
+ end
93
+
94
+ warn :warning_type => warning_type,
95
+ :warning_code => :CVE_2013_0269,
96
+ :message => message,
97
+ :confidence => confidence,
98
+ :gem_info => gemfile_or_environment(name),
99
+ :link => "https://groups.google.com/d/topic/rubyonrails-security/4_YvCpLzL58/discussion"
100
+ end
101
+
102
+ def uses_json_parse?
103
+ return @uses_json_parse unless @uses_json_parse.nil?
104
+
105
+ not tracker.find_call(:target => :JSON, :method => :parse).empty?
106
+ end
107
+ end
@@ -0,0 +1,132 @@
1
+ require 'railroader/checks/check_cross_site_scripting'
2
+
3
+ #Checks for calls to link_to in versions of Ruby where link_to did not
4
+ #escape the first argument.
5
+ #
6
+ #See https://rails.lighthouseapp.com/projects/8994/tickets/3518-link_to-doesnt-escape-its-input
7
+ class Railroader::CheckLinkTo < Railroader::CheckCrossSiteScripting
8
+ Railroader::Checks.add self
9
+
10
+ @description = "Checks for XSS in link_to in versions before 3.0"
11
+
12
+ def run_check
13
+ return unless version_between?("2.0.0", "2.9.9") and not tracker.config.escape_html?
14
+
15
+ @ignore_methods = Set[:button_to, :check_box, :escapeHTML, :escape_once,
16
+ :field_field, :fields_for, :h, :hidden_field,
17
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
18
+ :mail_to, :radio_button, :select,
19
+ :submit_tag, :text_area, :text_field,
20
+ :text_field_tag, :url_encode, :u, :url_for,
21
+ :will_paginate].merge tracker.options[:safe_methods]
22
+
23
+ @known_dangerous = []
24
+ #Ideally, I think this should also check to see if people are setting
25
+ #:escape => false
26
+ @models = tracker.models.keys
27
+ @inspect_arguments = tracker.options[:check_arguments]
28
+
29
+ tracker.find_call(:target => false, :method => :link_to).each {|call| process_result call}
30
+ end
31
+
32
+ def process_result result
33
+ return if duplicate? result
34
+
35
+ #Have to make a copy of this, otherwise it will be changed to
36
+ #an ignored method call by the code above.
37
+ call = result[:call] = result[:call].dup
38
+
39
+ first_arg = call.first_arg
40
+ second_arg = call.second_arg
41
+
42
+ @matched = false
43
+
44
+ #Skip if no arguments(?) or first argument is a hash
45
+ return if first_arg.nil? or hash? first_arg
46
+
47
+ if version_between? "2.0.0", "2.2.99"
48
+ check_argument result, first_arg
49
+
50
+ if second_arg and not hash? second_arg
51
+ check_argument result, second_arg
52
+ end
53
+ elsif second_arg
54
+ #Only check first argument if there is a second argument
55
+ #in Rails 2.3.x
56
+ check_argument result, first_arg
57
+ end
58
+ end
59
+
60
+ # Check the argument for possible xss exploits
61
+ def check_argument result, exp
62
+ argument = process(exp)
63
+ !check_user_input(result, argument) && !check_method(result, argument) && !check_matched(result, @matched)
64
+ end
65
+
66
+ # Check we should warn about the user input
67
+ def check_user_input(result, argument)
68
+ input = has_immediate_user_input?(argument)
69
+ return false unless input
70
+
71
+ message = "Unescaped #{friendly_type_of input} in link_to"
72
+
73
+ warn_xss(result, message, input, :high)
74
+ end
75
+
76
+ # Check if we should warn about the specified method
77
+ def check_method(result, argument)
78
+ return false if tracker.options[:ignore_model_output]
79
+ match = has_immediate_model?(argument)
80
+ return false unless match
81
+ method = match.method
82
+ return false if IGNORE_MODEL_METHODS.include? method
83
+
84
+ confidence = :medium
85
+ confidence = :high if likely_model_attribute? match
86
+ warn_xss(result, "Unescaped model attribute in link_to", match, confidence)
87
+ end
88
+
89
+ # Check if we should warn about the matched result
90
+ def check_matched(result, matched = nil)
91
+ return false unless matched
92
+ return false if matched.type == :model and tracker.options[:ignore_model_output]
93
+
94
+ message = "Unescaped #{friendly_type_of matched} in link_to"
95
+
96
+ warn_xss(result, message, @matched, :medium)
97
+ end
98
+
99
+ # Create a warn for this xss
100
+ def warn_xss(result, message, user_input, confidence)
101
+ add_result(result)
102
+ warn :result => result,
103
+ :warning_type => "Cross-Site Scripting",
104
+ :warning_code => :xss_link_to,
105
+ :message => message,
106
+ :user_input => user_input,
107
+ :confidence => confidence,
108
+ :link_path => "link_to"
109
+
110
+ true
111
+ end
112
+
113
+ def process_call exp
114
+ @mark = true
115
+ actually_process_call exp
116
+ exp
117
+ end
118
+
119
+ def actually_process_call exp
120
+ return if @matched
121
+
122
+ target = exp.target
123
+ target = process target.dup if sexp? target
124
+
125
+ #Bare records create links to the model resource,
126
+ #not a string that could have injection
127
+ #TODO: Needs test? I think this is broken?
128
+ return exp if model_name? target and context == [:call, :arglist]
129
+
130
+ super
131
+ end
132
+ end
@@ -0,0 +1,146 @@
1
+ require 'railroader/checks/check_cross_site_scripting'
2
+
3
+ #Checks for calls to link_to which pass in potentially hazardous data
4
+ #to the second argument. While this argument must be html_safe to not break
5
+ #the html, it must also be url safe as determined by calling a
6
+ #:url_safe_method. This prevents attacks such as javascript:evil() or
7
+ #data:<encoded XSS> which is html_safe, but not safe as an href
8
+ #Props to Nick Green for the idea.
9
+ class Railroader::CheckLinkToHref < Railroader::CheckLinkTo
10
+ Railroader::Checks.add self
11
+
12
+ @description = "Checks to see if values used for hrefs are sanitized using a :url_safe_method to protect against javascript:/data: XSS"
13
+
14
+ def run_check
15
+ @ignore_methods = Set[:button_to, :check_box,
16
+ :field_field, :fields_for, :hidden_field,
17
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
18
+ :mail_to, :polymorphic_url, :radio_button, :select, :slice,
19
+ :submit_tag, :text_area, :text_field,
20
+ :text_field_tag, :url_encode, :u,
21
+ :will_paginate].merge(tracker.options[:url_safe_methods] || [])
22
+
23
+ @models = tracker.models.keys
24
+ @inspect_arguments = tracker.options[:check_arguments]
25
+
26
+ methods = tracker.find_call :target => false, :method => :link_to
27
+ methods.each do |call|
28
+ process_result call
29
+ end
30
+ end
31
+
32
+ def process_result result
33
+ #Have to make a copy of this, otherwise it will be changed to
34
+ #an ignored method call by the code above.
35
+ call = result[:call] = result[:call].dup
36
+ @matched = false
37
+ url_arg = process call.second_arg
38
+
39
+ if check_argument? url_arg
40
+ url_arg = url_arg.first_arg
41
+ end
42
+
43
+ return if call? url_arg and ignore_call? url_arg.target, url_arg.method
44
+
45
+ if input = has_immediate_user_input?(url_arg)
46
+ message = "Unsafe #{friendly_type_of input} in link_to href"
47
+
48
+ unless duplicate? result or call_on_params? url_arg or ignore_interpolation? url_arg, input.match
49
+ add_result result
50
+ warn :result => result,
51
+ :warning_type => "Cross-Site Scripting",
52
+ :warning_code => :xss_link_to_href,
53
+ :message => message,
54
+ :user_input => input,
55
+ :confidence => :high,
56
+ :link_path => "link_to_href"
57
+ end
58
+ elsif not tracker.options[:ignore_model_output] and input = has_immediate_model?(url_arg)
59
+ return if ignore_model_call? url_arg, input or duplicate? result
60
+ add_result result
61
+
62
+ message = "Potentially unsafe model attribute in link_to href"
63
+
64
+ warn :result => result,
65
+ :warning_type => "Cross-Site Scripting",
66
+ :warning_code => :xss_link_to_href,
67
+ :message => message,
68
+ :user_input => input,
69
+ :confidence => :weak,
70
+ :link_path => "link_to_href"
71
+ end
72
+ end
73
+
74
+ CHECK_INSIDE_METHODS = [:url_for, :h, :sanitize]
75
+
76
+ def check_argument? url_arg
77
+ return unless call? url_arg
78
+
79
+ target = url_arg.target
80
+ method = url_arg.method
81
+
82
+ CHECK_INSIDE_METHODS.include? method or
83
+ cgi_escaped? target, method
84
+ end
85
+
86
+ def ignore_model_call? url_arg, exp
87
+ return true unless call? exp
88
+
89
+ target = exp.target
90
+ method = exp.method
91
+
92
+ return true unless model_find_call? target
93
+
94
+ return true unless method.to_s =~ /url|uri|link|page|site/
95
+
96
+ ignore_call? target, method or
97
+ IGNORE_MODEL_METHODS.include? method or
98
+ ignore_interpolation? url_arg, exp
99
+ end
100
+
101
+ #Ignore situations where the href is an interpolated string
102
+ #with something before the user input
103
+ def ignore_interpolation? arg, suspect
104
+ return unless string_interp? arg
105
+ return true unless arg[1].chomp.empty? # plain string before interpolation
106
+
107
+ first_interp = arg.find_nodes(:evstr).first
108
+ return unless first_interp
109
+
110
+ first_interp[1].deep_each do |e|
111
+ if suspect == e
112
+ return false
113
+ end
114
+ end
115
+
116
+ true
117
+ end
118
+
119
+ def ignore_call? target, method
120
+ decorated_model? method or super
121
+ end
122
+
123
+ def decorated_model? method
124
+ tracker.config.has_gem? :draper and
125
+ method == :decorate
126
+ end
127
+
128
+ def ignored_method? target, method
129
+ @ignore_methods.include? method or
130
+ method.to_s =~ /_path$/ or
131
+ (target.nil? and method.to_s =~ /_url$/)
132
+ end
133
+
134
+ def model_find_call? exp
135
+ return unless call? exp
136
+
137
+ MODEL_METHODS.include? exp.method or
138
+ exp.method.to_s =~ /^find_by_/
139
+ end
140
+
141
+ def call_on_params? exp
142
+ call? exp and
143
+ params? exp.target and
144
+ exp.method != :[]
145
+ end
146
+ end