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,86 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Checks if default routes are allowed in routes.rb
|
|
4
|
+
class Railroader::CheckDefaultRoutes < Railroader::BaseCheck
|
|
5
|
+
Railroader::Checks.add self
|
|
6
|
+
|
|
7
|
+
@description = "Checks for default routes"
|
|
8
|
+
|
|
9
|
+
#Checks for :allow_all_actions globally and for individual routes
|
|
10
|
+
#if it is not enabled globally.
|
|
11
|
+
def run_check
|
|
12
|
+
check_for_default_routes
|
|
13
|
+
check_for_action_globs
|
|
14
|
+
check_for_cve_2014_0130
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def check_for_default_routes
|
|
18
|
+
if allow_all_actions?
|
|
19
|
+
#Default routes are enabled globally
|
|
20
|
+
warn :warning_type => "Default Routes",
|
|
21
|
+
:warning_code => :all_default_routes,
|
|
22
|
+
:message => "All public methods in controllers are available as actions in routes.rb",
|
|
23
|
+
:line => tracker.routes[:allow_all_actions].line,
|
|
24
|
+
:confidence => :high,
|
|
25
|
+
:file => "#{tracker.app_path}/config/routes.rb"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def check_for_action_globs
|
|
30
|
+
return if allow_all_actions?
|
|
31
|
+
Railroader.debug "Checking each controller for default routes"
|
|
32
|
+
|
|
33
|
+
tracker.routes.each do |name, actions|
|
|
34
|
+
if actions.is_a? Array and actions[0] == :allow_all_actions
|
|
35
|
+
@actions_allowed_on_controller = true
|
|
36
|
+
if actions[1].is_a? Hash and actions[1][:allow_verb]
|
|
37
|
+
verb = actions[1][:allow_verb]
|
|
38
|
+
else
|
|
39
|
+
verb = "any"
|
|
40
|
+
end
|
|
41
|
+
warn :controller => name,
|
|
42
|
+
:warning_type => "Default Routes",
|
|
43
|
+
:warning_code => :controller_default_routes,
|
|
44
|
+
:message => "Any public method in #{name} can be used as an action for #{verb} requests.",
|
|
45
|
+
:line => actions[2],
|
|
46
|
+
:confidence => :medium,
|
|
47
|
+
:file => "#{tracker.app_path}/config/routes.rb"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def check_for_cve_2014_0130
|
|
53
|
+
case
|
|
54
|
+
when lts_version?("2.3.18.9")
|
|
55
|
+
#TODO: Should support LTS 3.0.20 too
|
|
56
|
+
return
|
|
57
|
+
when version_between?("2.0.0", "2.3.18")
|
|
58
|
+
upgrade = "3.2.18"
|
|
59
|
+
when version_between?("3.0.0", "3.2.17")
|
|
60
|
+
upgrade = "3.2.18"
|
|
61
|
+
when version_between?("4.0.0", "4.0.4")
|
|
62
|
+
upgrade = "4.0.5"
|
|
63
|
+
when version_between?("4.1.0", "4.1.0")
|
|
64
|
+
upgrade = "4.1.1"
|
|
65
|
+
else
|
|
66
|
+
return
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if allow_all_actions? or @actions_allowed_on_controller
|
|
70
|
+
confidence = :high
|
|
71
|
+
else
|
|
72
|
+
confidence = :medium
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
warn :warning_type => "Remote Code Execution",
|
|
76
|
+
:warning_code => :CVE_2014_0130,
|
|
77
|
+
:message => "Rails #{rails_version} with globbing routes is vulnerable to directory traversal and remote code execution. Patch or upgrade to #{upgrade}",
|
|
78
|
+
:confidence => confidence,
|
|
79
|
+
:file => "#{tracker.app_path}/config/routes.rb",
|
|
80
|
+
:link => "http://matasano.com/research/AnatomyOfRailsVuln-CVE-2014-0130.pdf"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def allow_all_actions?
|
|
84
|
+
tracker.routes[:allow_all_actions]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckDeserialize < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for unsafe deserialization of objects"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
check_yaml
|
|
10
|
+
check_csv
|
|
11
|
+
check_marshal
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def check_yaml
|
|
15
|
+
check_methods :YAML, :load, :load_documents, :load_stream, :parse_documents, :parse_stream
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def check_csv
|
|
19
|
+
check_methods :CSV, :load
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def check_marshal
|
|
23
|
+
check_methods :Marshal, :load, :restore
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check_methods target, *methods
|
|
27
|
+
tracker.find_call(:target => target, :methods => methods ).each do |result|
|
|
28
|
+
check_deserialize result, target
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def check_deserialize result, target, arg = nil
|
|
33
|
+
return unless original? result
|
|
34
|
+
|
|
35
|
+
arg ||= result[:call].first_arg
|
|
36
|
+
method = result[:call].method
|
|
37
|
+
|
|
38
|
+
if input = has_immediate_user_input?(arg)
|
|
39
|
+
confidence = :high
|
|
40
|
+
elsif input = include_user_input?(arg)
|
|
41
|
+
confidence = :medium
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if confidence
|
|
45
|
+
message = "#{target}.#{method} called with #{friendly_type_of input}"
|
|
46
|
+
|
|
47
|
+
warn :result => result,
|
|
48
|
+
:warning_type => "Remote Code Execution",
|
|
49
|
+
:warning_code => :unsafe_deserialize,
|
|
50
|
+
:message => message,
|
|
51
|
+
:user_input => input,
|
|
52
|
+
:confidence => confidence,
|
|
53
|
+
:link_path => "unsafe_deserialization"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
# Check for detailed exceptions enabled for production
|
|
4
|
+
class Railroader::CheckDetailedExceptions < Railroader::BaseCheck
|
|
5
|
+
Railroader::Checks.add self
|
|
6
|
+
|
|
7
|
+
LOCAL_REQUEST = s(:call, s(:call, nil, :request), :local?)
|
|
8
|
+
|
|
9
|
+
@description = "Checks for information disclosure displayed via detailed exceptions"
|
|
10
|
+
|
|
11
|
+
def run_check
|
|
12
|
+
check_local_request_config
|
|
13
|
+
check_detailed_exceptions
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def check_local_request_config
|
|
17
|
+
if true? tracker.config.rails[:consider_all_requests_local]
|
|
18
|
+
warn :warning_type => "Information Disclosure",
|
|
19
|
+
:warning_code => :local_request_config,
|
|
20
|
+
:message => "Detailed exceptions are enabled in production",
|
|
21
|
+
:confidence => :high,
|
|
22
|
+
:file => "config/environments/production.rb"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def check_detailed_exceptions
|
|
27
|
+
tracker.controllers.each do |_name, controller|
|
|
28
|
+
controller.methods_public.each do |method_name, definition|
|
|
29
|
+
src = definition[:src]
|
|
30
|
+
body = src.body.last
|
|
31
|
+
next unless body
|
|
32
|
+
|
|
33
|
+
if method_name == :show_detailed_exceptions? and not safe? body
|
|
34
|
+
if true? body
|
|
35
|
+
confidence = :high
|
|
36
|
+
else
|
|
37
|
+
confidence = :medium
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
warn :warning_type => "Information Disclosure",
|
|
41
|
+
:warning_code => :detailed_exceptions,
|
|
42
|
+
:message => "Detailed exceptions may be enabled in 'show_detailed_exceptions?'",
|
|
43
|
+
:confidence => confidence,
|
|
44
|
+
:code => src,
|
|
45
|
+
:file => definition[:file]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def safe? body
|
|
52
|
+
false? body or
|
|
53
|
+
body == LOCAL_REQUEST
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckDigestDoS < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for digest authentication DoS vulnerability"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
message = "Vulnerability in digest authentication (CVE-2012-3424). Upgrade to Rails version "
|
|
10
|
+
|
|
11
|
+
if version_between? "3.0.0", "3.0.15"
|
|
12
|
+
message << "3.0.16"
|
|
13
|
+
elsif version_between? "3.1.0", "3.1.6"
|
|
14
|
+
message << "3.1.7"
|
|
15
|
+
elsif version_between? "3.2.0", "3.2.5"
|
|
16
|
+
message << "3.2.7"
|
|
17
|
+
else
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if with_http_digest?
|
|
22
|
+
confidence = :high
|
|
23
|
+
else
|
|
24
|
+
confidence = :weak
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
warn :warning_type => "Denial of Service",
|
|
28
|
+
:warning_code => :CVE_2012_3424,
|
|
29
|
+
:message => message,
|
|
30
|
+
:confidence => confidence,
|
|
31
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/vxJjrc15qYM/discussion",
|
|
32
|
+
:gem_info => gemfile_or_environment
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def with_http_digest?
|
|
36
|
+
not tracker.find_call(:target => false, :method => [:authenticate_or_request_with_http_digest, :authenticate_with_http_digest]).empty?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckDivideByZero < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add_optional self
|
|
5
|
+
|
|
6
|
+
@description = "Warns on potential division by zero"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
tracker.find_call(:method => :"/").each do |result|
|
|
10
|
+
check_division result
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def check_division result
|
|
15
|
+
return unless original? result
|
|
16
|
+
|
|
17
|
+
call = result[:call]
|
|
18
|
+
|
|
19
|
+
denominator = call.first_arg
|
|
20
|
+
|
|
21
|
+
if number? denominator and denominator.value == 0
|
|
22
|
+
numerator = call.target
|
|
23
|
+
|
|
24
|
+
if number? numerator
|
|
25
|
+
if numerator.value.is_a? Float
|
|
26
|
+
return # 0.0 / 0 is NaN and 1.0 / 0 is Infinity
|
|
27
|
+
else
|
|
28
|
+
confidence = :medium
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
confidence = :weak
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
warn :result => result,
|
|
35
|
+
:warning_type => "Divide by Zero",
|
|
36
|
+
:warning_code => :divide_by_zero,
|
|
37
|
+
:message => "Potential division by zero",
|
|
38
|
+
:confidence => confidence,
|
|
39
|
+
:user_input => denominator
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#This check looks for regexes that include user input.
|
|
4
|
+
class Railroader::CheckDynamicFinders < Railroader::BaseCheck
|
|
5
|
+
Railroader::Checks.add self
|
|
6
|
+
|
|
7
|
+
@description = "Check unsafe usage of find_by_*"
|
|
8
|
+
|
|
9
|
+
def run_check
|
|
10
|
+
if tracker.config.has_gem? :mysql and version_between? '2.0.0', '4.1.99'
|
|
11
|
+
tracker.find_call(:method => /^find_by_/).each do |result|
|
|
12
|
+
process_result result
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process_result result
|
|
18
|
+
return unless original? result
|
|
19
|
+
|
|
20
|
+
call = result[:call]
|
|
21
|
+
|
|
22
|
+
if potentially_dangerous? call.method
|
|
23
|
+
call.each_arg do |arg|
|
|
24
|
+
if params? arg and not safe_call? arg
|
|
25
|
+
warn :result => result,
|
|
26
|
+
:warning_type => "SQL Injection",
|
|
27
|
+
:warning_code => :sql_injection_dynamic_finder,
|
|
28
|
+
:message => "MySQL integer conversion may cause 0 to match any string",
|
|
29
|
+
:confidence => :medium,
|
|
30
|
+
:user_input => arg
|
|
31
|
+
|
|
32
|
+
break
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def safe_call? arg
|
|
39
|
+
return false unless call? arg
|
|
40
|
+
|
|
41
|
+
meth = arg.method
|
|
42
|
+
meth == :to_s or meth == :to_i
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def potentially_dangerous? method_name
|
|
46
|
+
method_name.match /^find_by_.*(token|guid|password|api_key|activation|code|private|reset)/
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for versions with vulnerable html escape method
|
|
4
|
+
#http://groups.google.com/group/rubyonrails-security/browse_thread/thread/56bffb5923ab1195
|
|
5
|
+
class Railroader::CheckEscapeFunction < Railroader::BaseCheck
|
|
6
|
+
Railroader::Checks.add self
|
|
7
|
+
|
|
8
|
+
@description = "Checks for versions before 2.3.14 which have a vulnerable escape method"
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
if version_between?('2.0.0', '2.3.13') and RUBY_VERSION < '1.9.0'
|
|
12
|
+
|
|
13
|
+
warn :warning_type => 'Cross-Site Scripting',
|
|
14
|
+
:warning_code => :CVE_2011_2932,
|
|
15
|
+
:message => 'Versions before 2.3.14 have a vulnerability in escape method when used with Ruby 1.8: CVE-2011-2932',
|
|
16
|
+
:confidence => :high,
|
|
17
|
+
:gem_info => gemfile_or_environment,
|
|
18
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/Vr_7WSOrEZU/discussion"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#This check looks for calls to +eval+, +instance_eval+, etc. which include
|
|
4
|
+
#user input.
|
|
5
|
+
class Railroader::CheckEvaluation < Railroader::BaseCheck
|
|
6
|
+
Railroader::Checks.add self
|
|
7
|
+
|
|
8
|
+
@description = "Searches for evaluation of user input"
|
|
9
|
+
|
|
10
|
+
#Process calls
|
|
11
|
+
def run_check
|
|
12
|
+
Railroader.debug "Finding eval-like calls"
|
|
13
|
+
calls = tracker.find_call :method => [:eval, :instance_eval, :class_eval, :module_eval]
|
|
14
|
+
|
|
15
|
+
Railroader.debug "Processing eval-like calls"
|
|
16
|
+
calls.each do |call|
|
|
17
|
+
process_result call
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#Warns if eval includes user input
|
|
22
|
+
def process_result result
|
|
23
|
+
return unless original? result
|
|
24
|
+
|
|
25
|
+
if input = include_user_input?(result[:call].arglist)
|
|
26
|
+
warn :result => result,
|
|
27
|
+
:warning_type => "Dangerous Eval",
|
|
28
|
+
:warning_code => :code_eval,
|
|
29
|
+
:message => "User input in eval",
|
|
30
|
+
:code => result[:call],
|
|
31
|
+
:user_input => input,
|
|
32
|
+
:confidence => :high
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Checks for string interpolation and parameters in calls to
|
|
4
|
+
#Kernel#system, Kernel#exec, Kernel#syscall, and inside backticks.
|
|
5
|
+
#
|
|
6
|
+
#Examples of command injection vulnerabilities:
|
|
7
|
+
#
|
|
8
|
+
# system("rf -rf #{params[:file]}")
|
|
9
|
+
# exec(params[:command])
|
|
10
|
+
# `unlink #{params[:something}`
|
|
11
|
+
class Railroader::CheckExecute < Railroader::BaseCheck
|
|
12
|
+
Railroader::Checks.add self
|
|
13
|
+
|
|
14
|
+
@description = "Finds instances of possible command injection"
|
|
15
|
+
|
|
16
|
+
SAFE_VALUES = [s(:const, :RAILS_ROOT),
|
|
17
|
+
s(:call, s(:const, :Rails), :root),
|
|
18
|
+
s(:call, s(:const, :Rails), :env),
|
|
19
|
+
s(:call, s(:const, :Process), :pid)]
|
|
20
|
+
|
|
21
|
+
SHELL_ESCAPES = [:escape, :shellescape, :join]
|
|
22
|
+
|
|
23
|
+
SHELLWORDS = s(:const, :Shellwords)
|
|
24
|
+
|
|
25
|
+
#Check models, controllers, and views for command injection.
|
|
26
|
+
def run_check
|
|
27
|
+
Railroader.debug "Finding system calls using ``"
|
|
28
|
+
check_for_backticks tracker
|
|
29
|
+
|
|
30
|
+
check_open_calls
|
|
31
|
+
|
|
32
|
+
Railroader.debug "Finding other system calls"
|
|
33
|
+
calls = tracker.find_call :targets => [:IO, :Open3, :Kernel, :'POSIX::Spawn', :Process, nil],
|
|
34
|
+
:methods => [:capture2, :capture2e, :capture3, :exec, :pipeline, :pipeline_r,
|
|
35
|
+
:pipeline_rw, :pipeline_start, :pipeline_w, :popen, :popen2, :popen2e,
|
|
36
|
+
:popen3, :spawn, :syscall, :system], :nested => true
|
|
37
|
+
|
|
38
|
+
Railroader.debug "Processing system calls"
|
|
39
|
+
calls.each do |result|
|
|
40
|
+
process_result result
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#Processes results from Tracker#find_call.
|
|
45
|
+
def process_result result
|
|
46
|
+
call = result[:call]
|
|
47
|
+
args = call.arglist
|
|
48
|
+
first_arg = call.first_arg
|
|
49
|
+
|
|
50
|
+
case call.method
|
|
51
|
+
when :popen
|
|
52
|
+
unless array? first_arg
|
|
53
|
+
failure = include_user_input?(args) || dangerous_interp?(args)
|
|
54
|
+
end
|
|
55
|
+
when :system, :exec
|
|
56
|
+
failure = include_user_input?(first_arg) || dangerous_interp?(first_arg)
|
|
57
|
+
else
|
|
58
|
+
failure = include_user_input?(args) || dangerous_interp?(args)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if failure and original? result
|
|
62
|
+
|
|
63
|
+
if failure.type == :interp #Not from user input
|
|
64
|
+
confidence = :medium
|
|
65
|
+
else
|
|
66
|
+
confidence = :high
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
warn :result => result,
|
|
70
|
+
:warning_type => "Command Injection",
|
|
71
|
+
:warning_code => :command_injection,
|
|
72
|
+
:message => "Possible command injection",
|
|
73
|
+
:code => call,
|
|
74
|
+
:user_input => failure,
|
|
75
|
+
:confidence => confidence
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def check_open_calls
|
|
80
|
+
tracker.find_call(:targets => [nil, :Kernel], :method => :open).each do |result|
|
|
81
|
+
if match = dangerous_open_arg?(result[:call].first_arg)
|
|
82
|
+
warn :result => result,
|
|
83
|
+
:warning_type => "Command Injection",
|
|
84
|
+
:warning_code => :command_injection,
|
|
85
|
+
:message => "Possible command injection in open()",
|
|
86
|
+
:user_input => match,
|
|
87
|
+
:confidence => :high
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def dangerous_open_arg? exp
|
|
93
|
+
if string_interp? exp
|
|
94
|
+
# Check for input at start of string
|
|
95
|
+
exp[1] == "" and
|
|
96
|
+
node_type? exp[2], :evstr and
|
|
97
|
+
has_immediate_user_input? exp[2]
|
|
98
|
+
else
|
|
99
|
+
has_immediate_user_input? exp
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#Looks for calls using backticks such as
|
|
104
|
+
#
|
|
105
|
+
# `rm -rf #{params[:file]}`
|
|
106
|
+
def check_for_backticks tracker
|
|
107
|
+
tracker.find_call(:target => nil, :method => :`).each do |result|
|
|
108
|
+
process_backticks result
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
#Processes backticks.
|
|
113
|
+
def process_backticks result
|
|
114
|
+
return unless original? result
|
|
115
|
+
|
|
116
|
+
exp = result[:call]
|
|
117
|
+
|
|
118
|
+
if input = include_user_input?(exp)
|
|
119
|
+
confidence = :high
|
|
120
|
+
elsif input = dangerous?(exp)
|
|
121
|
+
confidence = :medium
|
|
122
|
+
else
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
warn :result => result,
|
|
127
|
+
:warning_type => "Command Injection",
|
|
128
|
+
:warning_code => :command_injection,
|
|
129
|
+
:message => "Possible command injection",
|
|
130
|
+
:code => exp,
|
|
131
|
+
:user_input => input,
|
|
132
|
+
:confidence => confidence
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# This method expects a :dstr or :evstr node
|
|
136
|
+
def dangerous? exp
|
|
137
|
+
exp.each_sexp do |e|
|
|
138
|
+
if call? e and e.method == :to_s
|
|
139
|
+
e = e.target
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
next if node_type? e, :lit, :str
|
|
143
|
+
next if SAFE_VALUES.include? e
|
|
144
|
+
next if shell_escape? e
|
|
145
|
+
|
|
146
|
+
if node_type? e, :if
|
|
147
|
+
# If we're in a conditional, evaluate the `then` and `else` clauses to
|
|
148
|
+
# see if they're dangerous.
|
|
149
|
+
if res = dangerous?(e.values[1..-1])
|
|
150
|
+
return res
|
|
151
|
+
end
|
|
152
|
+
elsif node_type? e, :or, :evstr, :dstr
|
|
153
|
+
if res = dangerous?(e)
|
|
154
|
+
return res
|
|
155
|
+
end
|
|
156
|
+
else
|
|
157
|
+
return e
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
false
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def dangerous_interp? exp
|
|
165
|
+
match = include_interp? exp
|
|
166
|
+
return unless match
|
|
167
|
+
interp = match.match
|
|
168
|
+
|
|
169
|
+
interp.each_sexp do |e|
|
|
170
|
+
if res = dangerous?(e)
|
|
171
|
+
return Match.new(:interp, res)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
false
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def shell_escape? exp
|
|
179
|
+
return false unless call? exp
|
|
180
|
+
|
|
181
|
+
if exp.target == SHELLWORDS and SHELL_ESCAPES.include? exp.method
|
|
182
|
+
true
|
|
183
|
+
elsif exp.method == :shelljoin
|
|
184
|
+
true
|
|
185
|
+
else
|
|
186
|
+
false
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
require 'railroader/processors/lib/processor_helper'
|
|
3
|
+
|
|
4
|
+
#Checks for user input in methods which open or manipulate files
|
|
5
|
+
class Railroader::CheckFileAccess < Railroader::BaseCheck
|
|
6
|
+
Railroader::Checks.add self
|
|
7
|
+
|
|
8
|
+
@description = "Finds possible file access using user input"
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
Railroader.debug "Finding possible file access"
|
|
12
|
+
methods = tracker.find_call :targets => [:Dir, :File, :IO, :Kernel, :"Net::FTP", :"Net::HTTP", :PStore, :Pathname, :Shell], :methods => [:[], :chdir, :chroot, :delete, :entries, :foreach, :glob, :install, :lchmod, :lchown, :link, :load, :load_file, :makedirs, :move, :new, :open, :read, :readlines, :rename, :rmdir, :safe_unlink, :symlink, :syscopy, :sysopen, :truncate, :unlink]
|
|
13
|
+
|
|
14
|
+
methods.concat tracker.find_call :target => :YAML, :methods => [:load_file, :parse_file]
|
|
15
|
+
methods.concat tracker.find_call :target => nil, :method => [:open]
|
|
16
|
+
|
|
17
|
+
Railroader.debug "Finding calls to load()"
|
|
18
|
+
methods.concat tracker.find_call :target => false, :method => :load
|
|
19
|
+
|
|
20
|
+
Railroader.debug "Finding calls using FileUtils"
|
|
21
|
+
methods.concat tracker.find_call :target => :FileUtils
|
|
22
|
+
|
|
23
|
+
Railroader.debug "Processing found calls"
|
|
24
|
+
methods.each do |call|
|
|
25
|
+
process_result call
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def process_result result
|
|
30
|
+
return unless original? result
|
|
31
|
+
call = result[:call]
|
|
32
|
+
file_name = call.first_arg
|
|
33
|
+
|
|
34
|
+
if match = has_immediate_user_input?(file_name)
|
|
35
|
+
confidence = :high
|
|
36
|
+
elsif match = has_immediate_model?(file_name)
|
|
37
|
+
match = Match.new(:model, match)
|
|
38
|
+
confidence = :medium
|
|
39
|
+
elsif tracker.options[:check_arguments] and
|
|
40
|
+
match = include_user_input?(file_name)
|
|
41
|
+
|
|
42
|
+
#Check for string building in file name
|
|
43
|
+
if call?(file_name) and (file_name.method == :+ or file_name.method == :<<)
|
|
44
|
+
confidence = :high
|
|
45
|
+
else
|
|
46
|
+
confidence = :weak
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if match and not temp_file? match.match
|
|
51
|
+
|
|
52
|
+
message = "#{friendly_type_of(match).capitalize} used in file name"
|
|
53
|
+
|
|
54
|
+
warn :result => result,
|
|
55
|
+
:warning_type => "File Access",
|
|
56
|
+
:warning_code => :file_access,
|
|
57
|
+
:message => message,
|
|
58
|
+
:confidence => confidence,
|
|
59
|
+
:code => call,
|
|
60
|
+
:user_input => match
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def temp_file? exp
|
|
65
|
+
if call? exp
|
|
66
|
+
return true if exp.call_chain.include? :tempfile
|
|
67
|
+
|
|
68
|
+
params? exp.target and exp.method == :path
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckFileDisclosure < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = 'Checks for versions with file existence disclosure vulnerability'
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
fix_version = case
|
|
10
|
+
when version_between?('2.0.0', '2.3.18')
|
|
11
|
+
'3.2.21'
|
|
12
|
+
when version_between?('3.0.0', '3.2.20')
|
|
13
|
+
'3.2.21'
|
|
14
|
+
when version_between?('4.0.0', '4.0.11')
|
|
15
|
+
'4.0.12'
|
|
16
|
+
when version_between?('4.1.0', '4.1.7')
|
|
17
|
+
'4.1.8'
|
|
18
|
+
else
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if fix_version and serves_static_assets?
|
|
23
|
+
warn :warning_type => "File Access",
|
|
24
|
+
:warning_code => :CVE_2014_7829,
|
|
25
|
+
:message => "Rails #{rails_version} has a file existence disclosure. Upgrade to #{fix_version} or disable serving static assets",
|
|
26
|
+
:confidence => :high,
|
|
27
|
+
:gem_info => gemfile_or_environment,
|
|
28
|
+
:link_path => "https://groups.google.com/d/msg/rubyonrails-security/23fiuwb1NBA/MQVM1-5GkPMJ"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serves_static_assets?
|
|
33
|
+
true? tracker.config.rails[:serve_static_assets]
|
|
34
|
+
end
|
|
35
|
+
end
|