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,148 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckWeakHash < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add_optional self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for use of weak hashes like MD5"
|
|
7
|
+
|
|
8
|
+
DIGEST_CALLS = [:base64digest, :digest, :hexdigest, :new]
|
|
9
|
+
|
|
10
|
+
def run_check
|
|
11
|
+
tracker.find_call(:targets => [:'Digest::MD5', :'Digest::SHA1', :'OpenSSL::Digest::MD5', :'OpenSSL::Digest::SHA1'], :nested => true).each do |result|
|
|
12
|
+
process_hash_result result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
tracker.find_call(:target => :'Digest::HMAC', :methods => [:new, :hexdigest], :nested => true).each do |result|
|
|
16
|
+
process_hmac_result result
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
tracker.find_call(:targets => [:'OpenSSL::Digest::Digest', :'OpenSSL::Digest'], :method => :new).each do |result|
|
|
20
|
+
process_openssl_result result
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def process_hash_result result
|
|
25
|
+
return unless original? result
|
|
26
|
+
|
|
27
|
+
input = nil
|
|
28
|
+
call = result[:call]
|
|
29
|
+
|
|
30
|
+
if DIGEST_CALLS.include? call.method
|
|
31
|
+
if input = user_input_as_arg?(call)
|
|
32
|
+
confidence = :high
|
|
33
|
+
elsif input = hashing_password?(call)
|
|
34
|
+
confidence = :high
|
|
35
|
+
else
|
|
36
|
+
confidence = :medium
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
confidence = :medium
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
alg = case call.target.last
|
|
44
|
+
when :MD5
|
|
45
|
+
" (MD5)"
|
|
46
|
+
when :SHA1
|
|
47
|
+
" (SHA1)"
|
|
48
|
+
else
|
|
49
|
+
""
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
warn :result => result,
|
|
53
|
+
:warning_type => "Weak Hash",
|
|
54
|
+
:warning_code => :weak_hash_digest,
|
|
55
|
+
:message => "Weak hashing algorithm#{alg} used",
|
|
56
|
+
:confidence => confidence,
|
|
57
|
+
:user_input => input
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def process_hmac_result result
|
|
61
|
+
return unless original? result
|
|
62
|
+
|
|
63
|
+
call = result[:call]
|
|
64
|
+
|
|
65
|
+
alg = case call.third_arg.last
|
|
66
|
+
when :MD5
|
|
67
|
+
'MD5'
|
|
68
|
+
when :SHA1
|
|
69
|
+
'SHA1'
|
|
70
|
+
else
|
|
71
|
+
return
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
warn :result => result,
|
|
75
|
+
:warning_type => "Weak Hash",
|
|
76
|
+
:warning_code => :weak_hash_hmac,
|
|
77
|
+
:message => "Weak hashing algorithm (#{alg}) used in HMAC",
|
|
78
|
+
:confidence => :medium
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def process_openssl_result result
|
|
82
|
+
return unless original? result
|
|
83
|
+
|
|
84
|
+
arg = result[:call].first_arg
|
|
85
|
+
|
|
86
|
+
if string? arg
|
|
87
|
+
alg = arg.value.upcase
|
|
88
|
+
|
|
89
|
+
if alg == 'MD5' or alg == 'SHA1'
|
|
90
|
+
warn :result => result,
|
|
91
|
+
:warning_type => "Weak Hash",
|
|
92
|
+
:warning_code => :weak_hash_digest,
|
|
93
|
+
:message => "Weak hashing algorithm (#{alg}) used",
|
|
94
|
+
:confidence => :medium
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def user_input_as_arg? call
|
|
100
|
+
call.each_arg do |arg|
|
|
101
|
+
if input = include_user_input?(arg)
|
|
102
|
+
return input
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def hashing_password? call
|
|
110
|
+
call.each_arg do |arg|
|
|
111
|
+
@has_password = false
|
|
112
|
+
|
|
113
|
+
process arg
|
|
114
|
+
|
|
115
|
+
if @has_password
|
|
116
|
+
return @has_password
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def process_call exp
|
|
124
|
+
if exp.method == :password
|
|
125
|
+
@has_password = exp
|
|
126
|
+
else
|
|
127
|
+
process_default exp
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
exp
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def process_ivar exp
|
|
134
|
+
if exp.value == :@password
|
|
135
|
+
@has_password = exp
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
exp
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def process_lvar exp
|
|
142
|
+
if exp.value == :password
|
|
143
|
+
@has_password = exp
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
exp
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
#Check for bypassing mass assignment protection
|
|
4
|
+
#with without_protection => true
|
|
5
|
+
#
|
|
6
|
+
#Only for Rails 3.1
|
|
7
|
+
class Railroader::CheckWithoutProtection < Railroader::BaseCheck
|
|
8
|
+
Railroader::Checks.add self
|
|
9
|
+
|
|
10
|
+
@description = "Check for mass assignment using without_protection"
|
|
11
|
+
|
|
12
|
+
def run_check
|
|
13
|
+
if version_between? "0.0.0", "3.0.99"
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return if active_record_models.empty?
|
|
18
|
+
|
|
19
|
+
Railroader.debug "Finding all mass assignments"
|
|
20
|
+
calls = tracker.find_call :targets => active_record_models.keys, :methods => [:new,
|
|
21
|
+
:attributes=,
|
|
22
|
+
:update_attributes,
|
|
23
|
+
:update_attributes!,
|
|
24
|
+
:create,
|
|
25
|
+
:create!]
|
|
26
|
+
|
|
27
|
+
Railroader.debug "Processing all mass assignments"
|
|
28
|
+
calls.each do |result|
|
|
29
|
+
process_result result
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#All results should be Model.new(...) or Model.attributes=() calls
|
|
34
|
+
def process_result res
|
|
35
|
+
call = res[:call]
|
|
36
|
+
last_arg = call.last_arg
|
|
37
|
+
|
|
38
|
+
if hash? last_arg and not call.original_line and not duplicate? res
|
|
39
|
+
|
|
40
|
+
if value = hash_access(last_arg, :without_protection)
|
|
41
|
+
if true? value
|
|
42
|
+
add_result res
|
|
43
|
+
|
|
44
|
+
if input = include_user_input?(call.arglist)
|
|
45
|
+
confidence = :high
|
|
46
|
+
elsif all_literals? call
|
|
47
|
+
return
|
|
48
|
+
else
|
|
49
|
+
confidence = :medium
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
warn :result => res,
|
|
53
|
+
:warning_type => "Mass Assignment",
|
|
54
|
+
:warning_code => :mass_assign_without_protection,
|
|
55
|
+
:message => "Unprotected mass assignment",
|
|
56
|
+
:code => call,
|
|
57
|
+
:user_input => input,
|
|
58
|
+
:confidence => confidence
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def all_literals? call
|
|
66
|
+
call.each_arg do |arg|
|
|
67
|
+
if hash? arg
|
|
68
|
+
hash_iterate arg do |k, v|
|
|
69
|
+
unless node_type? k, :str, :lit, :false, :true and node_type? v, :str, :lit, :false, :true
|
|
70
|
+
return false
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
return false
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckXMLDoS < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for XML denial of service (CVE-2015-3227)"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
version = rails_version
|
|
10
|
+
|
|
11
|
+
fix_version = case
|
|
12
|
+
when version_between?("2.0.0", "3.2.21")
|
|
13
|
+
"3.2.22"
|
|
14
|
+
when version_between?("4.1.0", "4.1.10")
|
|
15
|
+
"4.1.11"
|
|
16
|
+
when version_between?("4.2.0", "4.2.1")
|
|
17
|
+
"4.2.2"
|
|
18
|
+
when version_between?("4.0.0", "4.0.99")
|
|
19
|
+
"4.2.2"
|
|
20
|
+
else
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return if has_workaround?
|
|
25
|
+
|
|
26
|
+
message = "Rails #{version} is vulnerable to denial of service via XML parsing (CVE-2015-3227). Upgrade to Rails version #{fix_version}"
|
|
27
|
+
|
|
28
|
+
warn :warning_type => "Denial of Service",
|
|
29
|
+
:warning_code => :CVE_2015_3227,
|
|
30
|
+
:message => message,
|
|
31
|
+
:confidence => :medium,
|
|
32
|
+
:gem_info => gemfile_or_environment,
|
|
33
|
+
:link_path => "https://groups.google.com/d/msg/rubyonrails-security/bahr2JLnxvk/x4EocXnHPp8J"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def has_workaround?
|
|
37
|
+
tracker.check_initializers(:"ActiveSupport::XmlMini", :backend=).any? do |match|
|
|
38
|
+
arg = match.call.first_arg
|
|
39
|
+
if string? arg
|
|
40
|
+
value = arg.value
|
|
41
|
+
value == 'Nokogiri' or value == 'LibXML'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
require 'railroader/checks/base_check'
|
|
2
|
+
|
|
3
|
+
class Railroader::CheckYAMLParsing < Railroader::BaseCheck
|
|
4
|
+
Railroader::Checks.add self
|
|
5
|
+
|
|
6
|
+
@description = "Checks for YAML parsing vulnerabilities (CVE-2013-0156)"
|
|
7
|
+
|
|
8
|
+
def run_check
|
|
9
|
+
return unless version_between? "0.0.0", "2.3.14" or
|
|
10
|
+
version_between? "3.0.0", "3.0.18" or
|
|
11
|
+
version_between? "3.1.0", "3.1.9" or
|
|
12
|
+
version_between? "3.2.0", "3.2.10"
|
|
13
|
+
|
|
14
|
+
unless disabled_xml_parser? or disabled_xml_dangerous_types?
|
|
15
|
+
new_version = if version_between? "0.0.0", "2.3.14"
|
|
16
|
+
"2.3.15"
|
|
17
|
+
elsif version_between? "3.0.0", "3.0.18"
|
|
18
|
+
"3.0.19"
|
|
19
|
+
elsif version_between? "3.1.0", "3.1.9"
|
|
20
|
+
"3.1.10"
|
|
21
|
+
elsif version_between? "3.2.0", "3.2.10"
|
|
22
|
+
"3.2.11"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
message = "Rails #{rails_version} has a remote code execution vulnerability: upgrade to #{new_version} or disable XML parsing"
|
|
26
|
+
|
|
27
|
+
warn :warning_type => "Remote Code Execution",
|
|
28
|
+
:warning_code => :CVE_2013_0156,
|
|
29
|
+
:message => message,
|
|
30
|
+
:confidence => :high,
|
|
31
|
+
:gem_info => gemfile_or_environment,
|
|
32
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
#Warn if app accepts YAML
|
|
36
|
+
if version_between?("0.0.0", "2.3.14") and enabled_yaml_parser?
|
|
37
|
+
message = "Parsing YAML request parameters enables remote code execution: disable YAML parser"
|
|
38
|
+
|
|
39
|
+
warn :warning_type => "Remote Code Execution",
|
|
40
|
+
:warning_code => :CVE_2013_0156,
|
|
41
|
+
:message => message,
|
|
42
|
+
:confidence => :high,
|
|
43
|
+
:gem_info => gemfile_or_environment,
|
|
44
|
+
:link_path => "https://groups.google.com/d/topic/rubyonrails-security/61bkgvnSGTQ/discussion"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def disabled_xml_parser?
|
|
49
|
+
if version_between? "0.0.0", "2.3.14"
|
|
50
|
+
#Look for ActionController::Base.param_parsers.delete(Mime::XML)
|
|
51
|
+
params_parser = s(:call,
|
|
52
|
+
s(:colon2, s(:const, :ActionController), :Base),
|
|
53
|
+
:param_parsers)
|
|
54
|
+
|
|
55
|
+
matches = tracker.check_initializers(params_parser, :delete)
|
|
56
|
+
else
|
|
57
|
+
#Look for ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML)
|
|
58
|
+
matches = tracker.check_initializers(:"ActionDispatch::ParamsParser::DEFAULT_PARSERS", :delete)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless matches.empty?
|
|
62
|
+
mime_xml = s(:colon2, s(:const, :Mime), :XML)
|
|
63
|
+
|
|
64
|
+
matches.each do |result|
|
|
65
|
+
if result.call.first_arg == mime_xml
|
|
66
|
+
return true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
#Look for ActionController::Base.param_parsers[Mime::YAML] = :yaml
|
|
75
|
+
#in Rails 2.x apps
|
|
76
|
+
def enabled_yaml_parser?
|
|
77
|
+
param_parsers = s(:call,
|
|
78
|
+
s(:colon2, s(:const, :ActionController), :Base),
|
|
79
|
+
:param_parsers)
|
|
80
|
+
|
|
81
|
+
matches = tracker.check_initializers(param_parsers, :[]=)
|
|
82
|
+
|
|
83
|
+
mime_yaml = s(:colon2, s(:const, :Mime), :YAML)
|
|
84
|
+
|
|
85
|
+
matches.each do |result|
|
|
86
|
+
if result.call.first_arg == mime_yaml and
|
|
87
|
+
symbol? result.call.second_arg and
|
|
88
|
+
result.call.second_arg.value == :yaml
|
|
89
|
+
|
|
90
|
+
return true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def disabled_xml_dangerous_types?
|
|
98
|
+
if version_between? "0.0.0", "2.3.14"
|
|
99
|
+
matches = tracker.check_initializers(:"ActiveSupport::CoreExtensions::Hash::Conversions::XML_PARSING", :delete)
|
|
100
|
+
else
|
|
101
|
+
matches = tracker.check_initializers(:"ActiveSupport::XmlMini::PARSING", :delete)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
symbols_off = false
|
|
105
|
+
yaml_off = false
|
|
106
|
+
|
|
107
|
+
matches.each do |result|
|
|
108
|
+
arg = result.call.first_arg
|
|
109
|
+
|
|
110
|
+
if string? arg
|
|
111
|
+
if arg.value == "yaml"
|
|
112
|
+
yaml_off = true
|
|
113
|
+
elsif arg.value == "symbol"
|
|
114
|
+
symbols_off = true
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
symbols_off and yaml_off
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
require 'thread'
|
|
2
|
+
require 'railroader/differ'
|
|
3
|
+
|
|
4
|
+
#Collects up results from running different checks.
|
|
5
|
+
#
|
|
6
|
+
#Checks can be added with +Check.add(check_class)+
|
|
7
|
+
#
|
|
8
|
+
#All .rb files in checks/ will be loaded.
|
|
9
|
+
class Railroader::Checks
|
|
10
|
+
@checks = []
|
|
11
|
+
@optional_checks = []
|
|
12
|
+
|
|
13
|
+
attr_reader :warnings, :controller_warnings, :model_warnings, :template_warnings, :checks_run
|
|
14
|
+
|
|
15
|
+
#Add a check. This will call +_klass_.new+ when running tests
|
|
16
|
+
def self.add klass
|
|
17
|
+
@checks << klass unless @checks.include? klass
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#Add an optional check
|
|
21
|
+
def self.add_optional klass
|
|
22
|
+
@optional_checks << klass unless @checks.include? klass
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.checks
|
|
26
|
+
@checks + @optional_checks
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.optional_checks
|
|
30
|
+
@optional_checks
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.initialize_checks check_directory = ""
|
|
34
|
+
#Load all files in check_directory
|
|
35
|
+
Dir.glob(File.join(check_directory, "*.rb")).sort.each do |f|
|
|
36
|
+
require f
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.missing_checks included_checks, excluded_checks
|
|
41
|
+
included_checks = included_checks.map(&:to_s).to_set
|
|
42
|
+
excluded_checks = excluded_checks.map(&:to_s).to_set
|
|
43
|
+
|
|
44
|
+
if included_checks == Set['CheckNone']
|
|
45
|
+
return []
|
|
46
|
+
else
|
|
47
|
+
loaded = self.checks.map { |name| name.to_s.gsub('Railroader::', '') }.to_set
|
|
48
|
+
missing = (included_checks - loaded) + (excluded_checks - loaded)
|
|
49
|
+
|
|
50
|
+
unless missing.empty?
|
|
51
|
+
return missing
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
[]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#No need to use this directly.
|
|
59
|
+
def initialize options = { }
|
|
60
|
+
if options[:min_confidence]
|
|
61
|
+
@min_confidence = options[:min_confidence]
|
|
62
|
+
else
|
|
63
|
+
@min_confidence = Railroader.get_defaults[:min_confidence]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@warnings = []
|
|
67
|
+
@template_warnings = []
|
|
68
|
+
@model_warnings = []
|
|
69
|
+
@controller_warnings = []
|
|
70
|
+
@checks_run = []
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
#Add Warning to list of warnings to report.
|
|
74
|
+
#Warnings are split into four different arrays
|
|
75
|
+
#for template, controller, model, and generic warnings.
|
|
76
|
+
#
|
|
77
|
+
#Will not add warnings which are below the minimum confidence level.
|
|
78
|
+
def add_warning warning
|
|
79
|
+
unless warning.confidence > @min_confidence
|
|
80
|
+
case warning.warning_set
|
|
81
|
+
when :template
|
|
82
|
+
@template_warnings << warning
|
|
83
|
+
when :warning
|
|
84
|
+
@warnings << warning
|
|
85
|
+
when :controller
|
|
86
|
+
@controller_warnings << warning
|
|
87
|
+
when :model
|
|
88
|
+
@model_warnings << warning
|
|
89
|
+
else
|
|
90
|
+
raise "Unknown warning: #{warning.warning_set}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
#Return a hash of arrays of new and fixed warnings
|
|
96
|
+
#
|
|
97
|
+
# diff = checks.diff old_checks
|
|
98
|
+
# diff[:fixed] # [...]
|
|
99
|
+
# diff[:new] # [...]
|
|
100
|
+
def diff other_checks
|
|
101
|
+
my_warnings = self.all_warnings
|
|
102
|
+
other_warnings = other_checks.all_warnings
|
|
103
|
+
Railroader::Differ.new(my_warnings, other_warnings).diff
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
#Return an array of all warnings found.
|
|
107
|
+
def all_warnings
|
|
108
|
+
@warnings + @template_warnings + @controller_warnings + @model_warnings
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#Run all the checks on the given Tracker.
|
|
112
|
+
#Returns a new instance of Checks with the results.
|
|
113
|
+
def self.run_checks(app_tree, tracker)
|
|
114
|
+
checks = self.checks_to_run(tracker)
|
|
115
|
+
check_runner = self.new :min_confidence => tracker.options[:min_confidence]
|
|
116
|
+
self.actually_run_checks(checks, check_runner, app_tree, tracker)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.actually_run_checks(checks, check_runner, app_tree, tracker)
|
|
120
|
+
threads = [] # Results for parallel
|
|
121
|
+
results = [] # Results for sequential
|
|
122
|
+
parallel = tracker.options[:parallel_checks]
|
|
123
|
+
error_mutex = Mutex.new
|
|
124
|
+
|
|
125
|
+
checks.each do |c|
|
|
126
|
+
check_name = get_check_name c
|
|
127
|
+
Railroader.notify " - #{check_name}"
|
|
128
|
+
|
|
129
|
+
if parallel
|
|
130
|
+
threads << Thread.new do
|
|
131
|
+
self.run_a_check(c, error_mutex, app_tree, tracker)
|
|
132
|
+
end
|
|
133
|
+
else
|
|
134
|
+
results << self.run_a_check(c, error_mutex, app_tree, tracker)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
#Maintain list of which checks were run
|
|
138
|
+
#mainly for reporting purposes
|
|
139
|
+
check_runner.checks_run << check_name[5..-1]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
threads.each { |t| t.join }
|
|
143
|
+
|
|
144
|
+
Railroader.notify "Checks finished, collecting results..."
|
|
145
|
+
|
|
146
|
+
if parallel
|
|
147
|
+
threads.each do |thread|
|
|
148
|
+
thread.value.each do |warning|
|
|
149
|
+
check_runner.add_warning warning
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
else
|
|
153
|
+
results.each do |warnings|
|
|
154
|
+
warnings.each do |warning|
|
|
155
|
+
check_runner.add_warning warning
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
check_runner
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def self.get_check_name check_class
|
|
166
|
+
check_class.to_s.split("::").last
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def self.checks_to_run tracker
|
|
170
|
+
to_run = if tracker.options[:run_all_checks] or tracker.options[:run_checks]
|
|
171
|
+
@checks + @optional_checks
|
|
172
|
+
else
|
|
173
|
+
@checks
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
self.filter_checks to_run, tracker
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def self.filter_checks checks, tracker
|
|
180
|
+
skipped = tracker.options[:skip_checks]
|
|
181
|
+
explicit = tracker.options[:run_checks]
|
|
182
|
+
|
|
183
|
+
checks.reject do |c|
|
|
184
|
+
check_name = self.get_check_name(c)
|
|
185
|
+
|
|
186
|
+
skipped.include? check_name or
|
|
187
|
+
(explicit and not explicit.include? check_name)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def self.run_a_check klass, mutex, app_tree, tracker
|
|
192
|
+
check = klass.new(app_tree, tracker)
|
|
193
|
+
|
|
194
|
+
begin
|
|
195
|
+
check.run_check
|
|
196
|
+
rescue => e
|
|
197
|
+
mutex.synchronize do
|
|
198
|
+
tracker.error e
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
check.warnings
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
#Load all files in checks/ directory
|
|
207
|
+
Dir.glob("#{File.expand_path(File.dirname(__FILE__))}/checks/*.rb").sort.each do |f|
|
|
208
|
+
require f.match(/(railroader\/checks\/.*)\.rb$/)[0]
|
|
209
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module Railroader
|
|
4
|
+
module Codeclimate
|
|
5
|
+
class EngineConfiguration
|
|
6
|
+
|
|
7
|
+
def initialize(engine_config = {})
|
|
8
|
+
@engine_config = engine_config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def options
|
|
12
|
+
default_options.merge(configured_options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :engine_config
|
|
18
|
+
|
|
19
|
+
def default_options
|
|
20
|
+
@default_options = {
|
|
21
|
+
:output_format => :codeclimate,
|
|
22
|
+
:quiet => true,
|
|
23
|
+
:pager => false,
|
|
24
|
+
:app_path => Dir.pwd
|
|
25
|
+
}
|
|
26
|
+
if system("test -w /dev/stdout")
|
|
27
|
+
@default_options[:output_files] = ["/dev/stdout"]
|
|
28
|
+
end
|
|
29
|
+
@default_options
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def configured_options
|
|
33
|
+
@configured_options = {}
|
|
34
|
+
# ATM this gets parsed as a string instead of bool: "config":{ "debug":"true" }
|
|
35
|
+
if railroader_configuration["debug"] && railroader_configuration["debug"].to_s.downcase == "true"
|
|
36
|
+
@configured_options[:debug] = true
|
|
37
|
+
@configured_options[:report_progress] = false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
if active_include_paths
|
|
41
|
+
@configured_options[:only_files] = active_include_paths
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if railroader_configuration["app_path"]
|
|
45
|
+
@configured_options[:path_prefix] = railroader_configuration["app_path"]
|
|
46
|
+
path = Pathname.new(Dir.pwd) + railroader_configuration["app_path"]
|
|
47
|
+
@configured_options[:app_path] = path.to_s
|
|
48
|
+
end
|
|
49
|
+
@configured_options
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def railroader_configuration
|
|
53
|
+
if engine_config["config"]
|
|
54
|
+
engine_config["config"]
|
|
55
|
+
else
|
|
56
|
+
{}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def active_include_paths
|
|
61
|
+
@active_include_paths ||=
|
|
62
|
+
if railroader_configuration["app_path"]
|
|
63
|
+
stripped_include_paths(railroader_configuration["app_path"])
|
|
64
|
+
else
|
|
65
|
+
engine_config["include_paths"] && engine_config["include_paths"].compact
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def stripped_include_paths(prefix)
|
|
70
|
+
subprefixes = path_subprefixes(prefix)
|
|
71
|
+
engine_config["include_paths"] && engine_config["include_paths"].map do |path|
|
|
72
|
+
next unless path
|
|
73
|
+
stripped_include_path(prefix, subprefixes, path)
|
|
74
|
+
end.compact
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def path_subprefixes(path)
|
|
78
|
+
Pathname.new(path).each_filename.inject([]) do |memo, piece|
|
|
79
|
+
memo <<
|
|
80
|
+
if memo.any?
|
|
81
|
+
File.join(memo.last, piece)
|
|
82
|
+
else
|
|
83
|
+
File.join(piece)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def stripped_include_path(prefix, subprefixes, path)
|
|
89
|
+
if path.start_with?(prefix)
|
|
90
|
+
path.sub(%r{^#{prefix}/?}, "/")
|
|
91
|
+
elsif subprefixes.any? { |subprefix| path =~ %r{^#{subprefix}/?$} }
|
|
92
|
+
"/"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|