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,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
|