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,43 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckPermitAttributes < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Warn on potentially dangerous attributes whitelisted via permit"
|
|
7
|
+
|
|
8
|
+
SUSPICIOUS_KEYS = {
|
|
9
|
+
admin: :high,
|
|
10
|
+
account_id: :high,
|
|
11
|
+
role: :medium,
|
|
12
|
+
banned: :medium,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def run_check
|
|
16
|
+
tracker.find_call(:method => :permit).each do |result|
|
|
17
|
+
check_permit result
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def check_permit result
|
|
22
|
+
return unless original? result
|
|
23
|
+
|
|
24
|
+
call = result[:call]
|
|
25
|
+
|
|
26
|
+
call.each_arg do |arg|
|
|
27
|
+
if symbol? arg
|
|
28
|
+
if SUSPICIOUS_KEYS.key? arg.value
|
|
29
|
+
warn_on_permit_key result, arg
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def warn_on_permit_key result, key, confidence = nil
|
|
36
|
+
warn :result => result,
|
|
37
|
+
:warning_type => "Mass Assignment",
|
|
38
|
+
:warning_code => :dangerous_permit_key,
|
|
39
|
+
:message => "Potentially dangerous key allowed for mass assignment",
|
|
40
|
+
:confidence => (confidence || SUSPICIOUS_KEYS[key.value]),
|
|
41
|
+
:user_input => key
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for uses of quote_table_name in Rails versions before 2.3.13 and 3.0.10
|
|
4
|
+
#http://groups.google.com/group/rubyonrails-security/browse_thread/thread/6a1e473744bc389b
|
|
5
|
+
class Railroader::CheckQuoteTableName < Railroader::BaseCheck
|
|
6
|
+
Railroader::Checks.add self
|
|
7
|
+
|
|
8
|
+
@description = "Checks for quote_table_name vulnerability in versions before 2.3.14 and 3.0.10"
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
if (version_between?('2.0.0', '2.3.13') or
|
|
12
|
+
version_between?('3.0.0', '3.0.9'))
|
|
13
|
+
|
|
14
|
+
if uses_quote_table_name?
|
|
15
|
+
confidence = :high
|
|
16
|
+
else
|
|
17
|
+
confidence = :medium
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if rails_version =~ /^3/
|
|
21
|
+
message = "Versions before 3.0.10 have a vulnerability in quote_table_name: CVE-2011-2930"
|
|
22
|
+
else
|
|
23
|
+
message = "Versions before 2.3.14 have a vulnerability in quote_table_name: CVE-2011-2930"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
warn :warning_type => "SQL Injection",
|
|
27
|
+
:warning_code => :CVE_2011_2930,
|
|
28
|
+
:message => message,
|
|
29
|
+
:confidence => confidence,
|
|
30
|
+
:gem_info => gemfile_or_environment,
|
|
31
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/ah5HN0S8OJs/discussion"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def uses_quote_table_name?
|
|
36
|
+
Railroader.debug "Finding calls to quote_table_name()"
|
|
37
|
+
|
|
38
|
+
not tracker.find_call(:target => false, :method => :quote_table_name).empty?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Reports any calls to +redirect_to+ which include parameters in the arguments.
|
|
4
|
+
#
|
|
5
|
+
#For example:
|
|
6
|
+
#
|
|
7
|
+
# redirect_to params.merge(:action => :elsewhere)
|
|
8
|
+
class Railroader::CheckRedirect < Railroader::BaseCheck
|
|
9
|
+
Railroader::Checks.add self
|
|
10
|
+
|
|
11
|
+
@description = "Looks for calls to redirect_to with user input as arguments"
|
|
12
|
+
|
|
13
|
+
def run_check
|
|
14
|
+
Railroader.debug "Finding calls to redirect_to()"
|
|
15
|
+
|
|
16
|
+
@model_find_calls = Set[:all, :create, :create!, :find, :find_by_sql, :first, :last, :new]
|
|
17
|
+
|
|
18
|
+
if tracker.options[:rails3]
|
|
19
|
+
@model_find_calls.merge [:from, :group, :having, :joins, :lock, :order, :reorder, :select, :where]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if version_between? "4.0.0", "9.9.9"
|
|
23
|
+
@model_find_calls.merge [:find_by, :find_by!, :take]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@tracker.find_call(:target => false, :method => :redirect_to).each do |res|
|
|
27
|
+
process_result res
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process_result result
|
|
32
|
+
return unless original? result
|
|
33
|
+
|
|
34
|
+
call = result[:call]
|
|
35
|
+
method = call.method
|
|
36
|
+
|
|
37
|
+
opt = call.first_arg
|
|
38
|
+
|
|
39
|
+
if method == :redirect_to and
|
|
40
|
+
not only_path?(call) and
|
|
41
|
+
not explicit_host?(opt) and
|
|
42
|
+
not slice_call?(opt) and
|
|
43
|
+
not safe_permit?(opt) and
|
|
44
|
+
res = include_user_input?(call)
|
|
45
|
+
|
|
46
|
+
if res.type == :immediate
|
|
47
|
+
confidence = :high
|
|
48
|
+
else
|
|
49
|
+
confidence = :weak
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
warn :result => result,
|
|
53
|
+
:warning_type => "Redirect",
|
|
54
|
+
:warning_code => :open_redirect,
|
|
55
|
+
:message => "Possible unprotected redirect",
|
|
56
|
+
:code => call,
|
|
57
|
+
:user_input => res,
|
|
58
|
+
:confidence => confidence
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#Custom check for user input. First looks to see if the user input
|
|
63
|
+
#is being output directly. This is necessary because of tracker.options[:check_arguments]
|
|
64
|
+
#which can be used to enable/disable reporting output of method calls which use
|
|
65
|
+
#user input as arguments.
|
|
66
|
+
def include_user_input? call, immediate = :immediate
|
|
67
|
+
Railroader.debug "Checking if call includes user input"
|
|
68
|
+
|
|
69
|
+
arg = call.first_arg
|
|
70
|
+
|
|
71
|
+
# if the first argument is an array, rails assumes you are building a
|
|
72
|
+
# polymorphic route, which will never jump off-host
|
|
73
|
+
return false if array? arg
|
|
74
|
+
|
|
75
|
+
if tracker.options[:ignore_redirect_to_model]
|
|
76
|
+
if model_instance?(arg) or decorated_model?(arg)
|
|
77
|
+
return false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if res = has_immediate_model?(arg)
|
|
82
|
+
unless call? arg and arg.method.to_s =~ /_path/
|
|
83
|
+
return Match.new(immediate, res)
|
|
84
|
+
end
|
|
85
|
+
elsif call? arg
|
|
86
|
+
if request_value? arg
|
|
87
|
+
return Match.new(immediate, arg)
|
|
88
|
+
elsif request_value? arg.target
|
|
89
|
+
return Match.new(immediate, arg.target)
|
|
90
|
+
elsif arg.method == :url_for and include_user_input? arg
|
|
91
|
+
return Match.new(immediate, arg)
|
|
92
|
+
#Ignore helpers like some_model_url?
|
|
93
|
+
elsif arg.method.to_s =~ /_(url|path)\z/
|
|
94
|
+
return false
|
|
95
|
+
end
|
|
96
|
+
elsif request_value? arg
|
|
97
|
+
return Match.new(immediate, arg)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if tracker.options[:check_arguments] and call? arg
|
|
101
|
+
include_user_input? arg, false #I'm doubting if this is really necessary...
|
|
102
|
+
else
|
|
103
|
+
false
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
#Checks +redirect_to+ arguments for +only_path => true+ which essentially
|
|
108
|
+
#nullifies the danger posed by redirecting with user input
|
|
109
|
+
def only_path? call
|
|
110
|
+
arg = call.first_arg
|
|
111
|
+
|
|
112
|
+
if hash? arg
|
|
113
|
+
return has_only_path? arg
|
|
114
|
+
elsif call? arg and arg.method == :url_for
|
|
115
|
+
return check_url_for(arg)
|
|
116
|
+
elsif call? arg and hash? arg.first_arg and use_unsafe_hash_method? arg
|
|
117
|
+
return has_only_path? arg.first_arg
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
false
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def use_unsafe_hash_method? arg
|
|
124
|
+
return call_has_param(arg, :to_unsafe_hash) || call_has_param(arg, :to_unsafe_h)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def call_has_param arg, key
|
|
128
|
+
if call? arg and call? arg.target
|
|
129
|
+
target = arg.target
|
|
130
|
+
method = target.method
|
|
131
|
+
|
|
132
|
+
node_type? target.target, :params and method == key
|
|
133
|
+
else
|
|
134
|
+
false
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def has_only_path? arg
|
|
139
|
+
if value = hash_access(arg, :only_path)
|
|
140
|
+
return true if true?(value)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def explicit_host? arg
|
|
147
|
+
return unless sexp? arg
|
|
148
|
+
|
|
149
|
+
if hash? arg
|
|
150
|
+
if value = hash_access(arg, :host)
|
|
151
|
+
return !has_immediate_user_input?(value)
|
|
152
|
+
end
|
|
153
|
+
elsif call? arg
|
|
154
|
+
target = arg.target
|
|
155
|
+
|
|
156
|
+
if hash? target and value = hash_access(target, :host)
|
|
157
|
+
return !has_immediate_user_input?(value)
|
|
158
|
+
elsif call? arg
|
|
159
|
+
return explicit_host? target
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
false
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
#+url_for+ is only_path => true by default. This checks to see if it is
|
|
167
|
+
#set to false for some reason.
|
|
168
|
+
def check_url_for call
|
|
169
|
+
arg = call.first_arg
|
|
170
|
+
|
|
171
|
+
if hash? arg
|
|
172
|
+
if value = hash_access(arg, :only_path)
|
|
173
|
+
return false if false?(value)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
true
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
#Returns true if exp is (probably) a model instance
|
|
181
|
+
def model_instance? exp
|
|
182
|
+
if node_type? exp, :or
|
|
183
|
+
model_instance? exp.lhs or model_instance? exp.rhs
|
|
184
|
+
elsif call? exp
|
|
185
|
+
if model_target? exp and
|
|
186
|
+
(@model_find_calls.include? exp.method or exp.method.to_s.match(/^find_by_/))
|
|
187
|
+
true
|
|
188
|
+
else
|
|
189
|
+
association?(exp.target, exp.method)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def model_target? exp
|
|
195
|
+
return false unless call? exp
|
|
196
|
+
model_name? exp.target or
|
|
197
|
+
friendly_model? exp.target or
|
|
198
|
+
model_target? exp.target
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
#Returns true if exp is (probably) a friendly model instance
|
|
202
|
+
#using the FriendlyId gem
|
|
203
|
+
def friendly_model? exp
|
|
204
|
+
call? exp and model_name? exp.target and exp.method == :friendly
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
#Returns true if exp is (probably) a decorated model instance
|
|
208
|
+
#using the Draper gem
|
|
209
|
+
def decorated_model? exp
|
|
210
|
+
if node_type? exp, :or
|
|
211
|
+
decorated_model? exp.lhs or decorated_model? exp.rhs
|
|
212
|
+
else
|
|
213
|
+
tracker.config.has_gem? :draper and
|
|
214
|
+
call? exp and
|
|
215
|
+
node_type?(exp.target, :const) and
|
|
216
|
+
exp.target.value.to_s.match(/Decorator$/) and
|
|
217
|
+
exp.method == :decorate
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
#Check if method is actually an association in a Model
|
|
222
|
+
def association? model_name, meth
|
|
223
|
+
if call? model_name
|
|
224
|
+
return association? model_name.target, meth
|
|
225
|
+
elsif model_name? model_name
|
|
226
|
+
model = tracker.models[class_name(model_name)]
|
|
227
|
+
else
|
|
228
|
+
return false
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
return false unless model
|
|
232
|
+
|
|
233
|
+
model.association? meth
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def slice_call? exp
|
|
237
|
+
return unless call? exp
|
|
238
|
+
exp.method == :slice
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
DANGEROUS_KEYS = [:host, :subdomain, :domain, :port]
|
|
242
|
+
|
|
243
|
+
def safe_permit? exp
|
|
244
|
+
if call? exp and params? exp.target and exp.method == :permit
|
|
245
|
+
exp.each_arg do |opt|
|
|
246
|
+
if symbol? opt and DANGEROUS_KEYS.include? opt.value
|
|
247
|
+
return false
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
return true
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
false
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#This check looks for regexes that include user input.
|
|
4
|
+
class Railroader::CheckRegexDoS < Railroader::BaseCheck
|
|
5
|
+
Railroader::Checks.add self
|
|
6
|
+
|
|
7
|
+
ESCAPES = {
|
|
8
|
+
s(:const, :Regexp) => [
|
|
9
|
+
:escape,
|
|
10
|
+
:quote
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@description = "Searches regexes including user input"
|
|
15
|
+
|
|
16
|
+
#Process calls
|
|
17
|
+
def run_check
|
|
18
|
+
Railroader.debug "Finding dynamic regexes"
|
|
19
|
+
calls = tracker.find_call :method => [:railroader_regex_interp]
|
|
20
|
+
|
|
21
|
+
Railroader.debug "Processing dynamic regexes"
|
|
22
|
+
calls.each do |call|
|
|
23
|
+
process_result call
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#Warns if regex includes user input
|
|
28
|
+
def process_result result
|
|
29
|
+
return unless original? result
|
|
30
|
+
|
|
31
|
+
call = result[:call]
|
|
32
|
+
components = call[1..-1]
|
|
33
|
+
|
|
34
|
+
components.any? do |component|
|
|
35
|
+
next unless sexp? component
|
|
36
|
+
|
|
37
|
+
if match = has_immediate_user_input?(component)
|
|
38
|
+
confidence = :high
|
|
39
|
+
elsif match = has_immediate_model?(component)
|
|
40
|
+
match = Match.new(:model, match)
|
|
41
|
+
confidence = :medium
|
|
42
|
+
elsif match = include_user_input?(component)
|
|
43
|
+
confidence = :weak
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
if match
|
|
47
|
+
message = "#{friendly_type_of(match).capitalize} used in regex"
|
|
48
|
+
|
|
49
|
+
warn :result => result,
|
|
50
|
+
:warning_type => "Denial of Service",
|
|
51
|
+
:warning_code => :regex_dos,
|
|
52
|
+
:message => message,
|
|
53
|
+
:confidence => confidence,
|
|
54
|
+
:user_input => match
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def process_call(exp)
|
|
60
|
+
if escape_methods = ESCAPES[exp.target]
|
|
61
|
+
if escape_methods.include? exp.method
|
|
62
|
+
return exp
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
super
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check calls to +render()+ for dangerous values
|
|
4
|
+
class Railroader::CheckRender < Railroader::BaseCheck
|
|
5
|
+
Railroader::Checks.add self
|
|
6
|
+
|
|
7
|
+
@description = "Finds calls to render that might allow file access or code execution"
|
|
8
|
+
|
|
9
|
+
def run_check
|
|
10
|
+
tracker.find_call(:target => nil, :method => :render).each do |result|
|
|
11
|
+
process_render_result result
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def process_render_result result
|
|
16
|
+
return unless node_type? result[:call], :render
|
|
17
|
+
|
|
18
|
+
case result[:call].render_type
|
|
19
|
+
when :partial, :template, :action, :file
|
|
20
|
+
check_for_rce(result) or
|
|
21
|
+
check_for_dynamic_path(result)
|
|
22
|
+
when :inline
|
|
23
|
+
when :js
|
|
24
|
+
when :json
|
|
25
|
+
when :text
|
|
26
|
+
when :update
|
|
27
|
+
when :xml
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#Check if path to action or file is determined dynamically
|
|
32
|
+
def check_for_dynamic_path result
|
|
33
|
+
view = result[:call][2]
|
|
34
|
+
|
|
35
|
+
if sexp? view and original? result
|
|
36
|
+
|
|
37
|
+
if input = has_immediate_user_input?(view)
|
|
38
|
+
if string_interp? view
|
|
39
|
+
confidence = :medium
|
|
40
|
+
else
|
|
41
|
+
confidence = :high
|
|
42
|
+
end
|
|
43
|
+
elsif input = include_user_input?(view)
|
|
44
|
+
confidence = :weak
|
|
45
|
+
else
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
return if input.type == :model #skip models
|
|
50
|
+
return if safe_param? input.match
|
|
51
|
+
|
|
52
|
+
message = "Render path contains #{friendly_type_of input}"
|
|
53
|
+
|
|
54
|
+
warn :result => result,
|
|
55
|
+
:warning_type => "Dynamic Render Path",
|
|
56
|
+
:warning_code => :dynamic_render_path,
|
|
57
|
+
:message => message,
|
|
58
|
+
:user_input => input,
|
|
59
|
+
:confidence => confidence
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def check_for_rce result
|
|
64
|
+
return unless version_between? "0.0.0", "3.2.22" or
|
|
65
|
+
version_between? "4.0.0", "4.1.14" or
|
|
66
|
+
version_between? "4.2.0", "4.2.5"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
view = result[:call][2]
|
|
70
|
+
if sexp? view and not duplicate? result
|
|
71
|
+
if params? view
|
|
72
|
+
add_result result
|
|
73
|
+
return if safe_param? view
|
|
74
|
+
|
|
75
|
+
warn :result => result,
|
|
76
|
+
:warning_type => "Remote Code Execution",
|
|
77
|
+
:warning_code => :dynamic_render_path_rce,
|
|
78
|
+
:message => "Passing query parameters to render() is vulnerable in Rails #{rails_version} (CVE-2016-0752)",
|
|
79
|
+
:user_input => view,
|
|
80
|
+
:confidence => :high
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def safe_param? exp
|
|
86
|
+
if params? exp and call? exp
|
|
87
|
+
method_name = exp.method
|
|
88
|
+
|
|
89
|
+
if method_name == :[]
|
|
90
|
+
arg = exp.first_arg
|
|
91
|
+
symbol? arg and [:controller, :action].include? arg.value
|
|
92
|
+
else
|
|
93
|
+
boolean_method? method_name
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckRenderDoS < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Warn about denial of service with render :text (CVE-2014-0082)"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
if version_between? "3.0.0", "3.0.20" or
|
|
10
|
+
version_between? "3.1.0", "3.1.12" or
|
|
11
|
+
version_between? "3.2.0", "3.2.16"
|
|
12
|
+
|
|
13
|
+
tracker.find_call(:target => nil, :method => :render).each do |result|
|
|
14
|
+
if text_render? result
|
|
15
|
+
warn_about_text_render
|
|
16
|
+
break
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def text_render? result
|
|
23
|
+
node_type? result[:call], :render and
|
|
24
|
+
result[:call].render_type == :text
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warn_about_text_render
|
|
28
|
+
message = "Rails #{rails_version} has a denial of service vulnerability (CVE-2014-0082). Upgrade to Rails version 3.2.17"
|
|
29
|
+
|
|
30
|
+
warn :warning_type => "Denial of Service",
|
|
31
|
+
:warning_code => :CVE_2014_0082,
|
|
32
|
+
:message => message,
|
|
33
|
+
:confidence => :high,
|
|
34
|
+
:link_path => "https://groups.google.com/d/msg/rubyonrails-security/LMxO_3_eCuc/ozGBEhKaJbIJ",
|
|
35
|
+
:gem_info => gemfile_or_environment
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
class Railroader::CheckRenderInline < Railroader::CheckCrossSiteScripting
|
|
2
|
+
Railroader::Checks.add self
|
|
3
|
+
|
|
4
|
+
@description = "Checks for cross-site scripting in render calls"
|
|
5
|
+
|
|
6
|
+
def run_check
|
|
7
|
+
setup
|
|
8
|
+
|
|
9
|
+
tracker.find_call(:target => nil, :method => :render).each do |result|
|
|
10
|
+
check_render result
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def check_render result
|
|
15
|
+
return unless original? result
|
|
16
|
+
|
|
17
|
+
call = result[:call]
|
|
18
|
+
|
|
19
|
+
if node_type? call, :render and
|
|
20
|
+
(call.render_type == :text or call.render_type == :inline)
|
|
21
|
+
|
|
22
|
+
unless call.render_type == :text and content_type_set? call[3]
|
|
23
|
+
render_value = call[2]
|
|
24
|
+
|
|
25
|
+
if input = has_immediate_user_input?(render_value)
|
|
26
|
+
warn :result => result,
|
|
27
|
+
:warning_type => "Cross-Site Scripting",
|
|
28
|
+
:warning_code => :cross_site_scripting_inline,
|
|
29
|
+
:message => "Unescaped #{friendly_type_of input} rendered inline",
|
|
30
|
+
:user_input => input,
|
|
31
|
+
:confidence => :high
|
|
32
|
+
elsif input = has_immediate_model?(render_value)
|
|
33
|
+
warn :result => result,
|
|
34
|
+
:warning_type => "Cross-Site Scripting",
|
|
35
|
+
:warning_code => :cross_site_scripting_inline,
|
|
36
|
+
:message => "Unescaped model attribute rendered inline",
|
|
37
|
+
:user_input => input,
|
|
38
|
+
:confidence => :medium
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
CONTENT_TYPES = ["text/html", "text/javascript", "application/javascript"]
|
|
45
|
+
|
|
46
|
+
def content_type_set? opts
|
|
47
|
+
if hash? opts
|
|
48
|
+
content_type = hash_access(opts, :content_type)
|
|
49
|
+
|
|
50
|
+
string? content_type and not CONTENT_TYPES.include? content_type.value
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Warn about response splitting in Rails versions before 2.3.13
|
|
4
|
+
#http://groups.google.com/group/rubyonrails-security/browse_thread/thread/6ffc93bde0298768
|
|
5
|
+
class Railroader::CheckResponseSplitting < Railroader::BaseCheck
|
|
6
|
+
Railroader::Checks.add self
|
|
7
|
+
|
|
8
|
+
@description = "Report response splitting in Rails 2.3.0 - 2.3.13"
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
if version_between?('2.3.0', '2.3.13')
|
|
12
|
+
|
|
13
|
+
warn :warning_type => "Response Splitting",
|
|
14
|
+
:warning_code => :CVE_2011_3186,
|
|
15
|
+
:message => "Versions before 2.3.14 have a vulnerability content type handling allowing injection of headers: CVE-2011-3186",
|
|
16
|
+
:confidence => :medium,
|
|
17
|
+
:gem_info => gemfile_or_environment,
|
|
18
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/b_yTveAph2g/discussion"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckRouteDoS < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for route DoS (CVE-2015-7581)"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
fix_version = case
|
|
10
|
+
when version_between?("4.0.0", "4.1.14")
|
|
11
|
+
"4.1.14.1"
|
|
12
|
+
when version_between?("4.2.0", "4.2.5")
|
|
13
|
+
"4.2.5.1"
|
|
14
|
+
else
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if controller_wildcards?
|
|
19
|
+
message = "Rails #{rails_version} has a denial of service vulnerability with :controller routes (CVE-2015-7581). Upgrade to Rails #{fix_version}"
|
|
20
|
+
|
|
21
|
+
warn :warning_type => "Denial of Service",
|
|
22
|
+
:warning_code => :CVE_2015_7581,
|
|
23
|
+
:message => message,
|
|
24
|
+
:confidence => :medium,
|
|
25
|
+
:gem_info => gemfile_or_environment,
|
|
26
|
+
:link_path => "https://groups.google.com/d/msg/rubyonrails-security/dthJ5wL69JE/YzPnFelbFQAJ"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def controller_wildcards?
|
|
31
|
+
tracker.routes.each do |name, actions|
|
|
32
|
+
if name == :':controllerController'
|
|
33
|
+
# awful hack for routes with :controller in them
|
|
34
|
+
return true
|
|
35
|
+
elsif string? actions and actions.value.include? ":controller"
|
|
36
|
+
return true
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for unsafe manipulation of strings
|
|
4
|
+
#Right now this is just a version check for
|
|
5
|
+
#https://groups.google.com/group/rubyonrails-security/browse_thread/thread/edd28f1e3d04e913?pli=1
|
|
6
|
+
class Railroader::CheckSafeBufferManipulation < Railroader::BaseCheck
|
|
7
|
+
Railroader::Checks.add self
|
|
8
|
+
|
|
9
|
+
@description = "Check for Rails versions with SafeBuffer bug"
|
|
10
|
+
|
|
11
|
+
def run_check
|
|
12
|
+
|
|
13
|
+
if version_between? "3.0.0", "3.0.11"
|
|
14
|
+
suggested_version = "3.0.12"
|
|
15
|
+
elsif version_between? "3.1.0", "3.1.3"
|
|
16
|
+
suggested_version = "3.1.4"
|
|
17
|
+
elsif version_between? "3.2.0", "3.2.1"
|
|
18
|
+
suggested_version = "3.2.2"
|
|
19
|
+
else
|
|
20
|
+
return
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
message = "Rails #{rails_version} has a vulnerabilty in SafeBuffer. Upgrade to #{suggested_version} or apply patches."
|
|
24
|
+
|
|
25
|
+
warn :warning_type => "Cross-Site Scripting",
|
|
26
|
+
:warning_code => :safe_buffer_vuln,
|
|
27
|
+
:message => message,
|
|
28
|
+
:confidence => :medium,
|
|
29
|
+
:gem_info => gemfile_or_environment
|
|
30
|
+
end
|
|
31
|
+
end
|