railroader 4.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.md +1091 -0
  3. data/FEATURES +16 -0
  4. data/README.md +174 -0
  5. data/bin/railroader +8 -0
  6. data/lib/railroader/app_tree.rb +191 -0
  7. data/lib/railroader/call_index.rb +219 -0
  8. data/lib/railroader/checks/base_check.rb +505 -0
  9. data/lib/railroader/checks/check_basic_auth.rb +88 -0
  10. data/lib/railroader/checks/check_basic_auth_timing_attack.rb +33 -0
  11. data/lib/railroader/checks/check_content_tag.rb +200 -0
  12. data/lib/railroader/checks/check_create_with.rb +74 -0
  13. data/lib/railroader/checks/check_cross_site_scripting.rb +381 -0
  14. data/lib/railroader/checks/check_default_routes.rb +86 -0
  15. data/lib/railroader/checks/check_deserialize.rb +56 -0
  16. data/lib/railroader/checks/check_detailed_exceptions.rb +55 -0
  17. data/lib/railroader/checks/check_digest_dos.rb +38 -0
  18. data/lib/railroader/checks/check_divide_by_zero.rb +42 -0
  19. data/lib/railroader/checks/check_dynamic_finders.rb +48 -0
  20. data/lib/railroader/checks/check_escape_function.rb +21 -0
  21. data/lib/railroader/checks/check_evaluation.rb +35 -0
  22. data/lib/railroader/checks/check_execute.rb +189 -0
  23. data/lib/railroader/checks/check_file_access.rb +71 -0
  24. data/lib/railroader/checks/check_file_disclosure.rb +35 -0
  25. data/lib/railroader/checks/check_filter_skipping.rb +31 -0
  26. data/lib/railroader/checks/check_forgery_setting.rb +81 -0
  27. data/lib/railroader/checks/check_header_dos.rb +31 -0
  28. data/lib/railroader/checks/check_i18n_xss.rb +48 -0
  29. data/lib/railroader/checks/check_jruby_xml.rb +36 -0
  30. data/lib/railroader/checks/check_json_encoding.rb +47 -0
  31. data/lib/railroader/checks/check_json_parsing.rb +107 -0
  32. data/lib/railroader/checks/check_link_to.rb +132 -0
  33. data/lib/railroader/checks/check_link_to_href.rb +146 -0
  34. data/lib/railroader/checks/check_mail_to.rb +49 -0
  35. data/lib/railroader/checks/check_mass_assignment.rb +196 -0
  36. data/lib/railroader/checks/check_mime_type_dos.rb +39 -0
  37. data/lib/railroader/checks/check_model_attr_accessible.rb +55 -0
  38. data/lib/railroader/checks/check_model_attributes.rb +119 -0
  39. data/lib/railroader/checks/check_model_serialize.rb +67 -0
  40. data/lib/railroader/checks/check_nested_attributes.rb +38 -0
  41. data/lib/railroader/checks/check_nested_attributes_bypass.rb +58 -0
  42. data/lib/railroader/checks/check_number_to_currency.rb +74 -0
  43. data/lib/railroader/checks/check_permit_attributes.rb +43 -0
  44. data/lib/railroader/checks/check_quote_table_name.rb +40 -0
  45. data/lib/railroader/checks/check_redirect.rb +256 -0
  46. data/lib/railroader/checks/check_regex_dos.rb +68 -0
  47. data/lib/railroader/checks/check_render.rb +97 -0
  48. data/lib/railroader/checks/check_render_dos.rb +37 -0
  49. data/lib/railroader/checks/check_render_inline.rb +53 -0
  50. data/lib/railroader/checks/check_response_splitting.rb +21 -0
  51. data/lib/railroader/checks/check_route_dos.rb +42 -0
  52. data/lib/railroader/checks/check_safe_buffer_manipulation.rb +31 -0
  53. data/lib/railroader/checks/check_sanitize_methods.rb +112 -0
  54. data/lib/railroader/checks/check_secrets.rb +40 -0
  55. data/lib/railroader/checks/check_select_tag.rb +59 -0
  56. data/lib/railroader/checks/check_select_vulnerability.rb +60 -0
  57. data/lib/railroader/checks/check_send.rb +47 -0
  58. data/lib/railroader/checks/check_send_file.rb +19 -0
  59. data/lib/railroader/checks/check_session_manipulation.rb +35 -0
  60. data/lib/railroader/checks/check_session_settings.rb +176 -0
  61. data/lib/railroader/checks/check_simple_format.rb +58 -0
  62. data/lib/railroader/checks/check_single_quotes.rb +101 -0
  63. data/lib/railroader/checks/check_skip_before_filter.rb +60 -0
  64. data/lib/railroader/checks/check_sql.rb +700 -0
  65. data/lib/railroader/checks/check_sql_cves.rb +106 -0
  66. data/lib/railroader/checks/check_ssl_verify.rb +48 -0
  67. data/lib/railroader/checks/check_strip_tags.rb +89 -0
  68. data/lib/railroader/checks/check_symbol_dos.rb +71 -0
  69. data/lib/railroader/checks/check_symbol_dos_cve.rb +30 -0
  70. data/lib/railroader/checks/check_translate_bug.rb +45 -0
  71. data/lib/railroader/checks/check_unsafe_reflection.rb +50 -0
  72. data/lib/railroader/checks/check_unscoped_find.rb +57 -0
  73. data/lib/railroader/checks/check_validation_regex.rb +116 -0
  74. data/lib/railroader/checks/check_weak_hash.rb +148 -0
  75. data/lib/railroader/checks/check_without_protection.rb +80 -0
  76. data/lib/railroader/checks/check_xml_dos.rb +45 -0
  77. data/lib/railroader/checks/check_yaml_parsing.rb +121 -0
  78. data/lib/railroader/checks.rb +209 -0
  79. data/lib/railroader/codeclimate/engine_configuration.rb +97 -0
  80. data/lib/railroader/commandline.rb +179 -0
  81. data/lib/railroader/differ.rb +66 -0
  82. data/lib/railroader/file_parser.rb +54 -0
  83. data/lib/railroader/format/style.css +133 -0
  84. data/lib/railroader/options.rb +339 -0
  85. data/lib/railroader/parsers/rails2_erubis.rb +6 -0
  86. data/lib/railroader/parsers/rails2_xss_plugin_erubis.rb +48 -0
  87. data/lib/railroader/parsers/rails3_erubis.rb +81 -0
  88. data/lib/railroader/parsers/template_parser.rb +108 -0
  89. data/lib/railroader/processor.rb +102 -0
  90. data/lib/railroader/processors/alias_processor.rb +1229 -0
  91. data/lib/railroader/processors/base_processor.rb +295 -0
  92. data/lib/railroader/processors/config_processor.rb +14 -0
  93. data/lib/railroader/processors/controller_alias_processor.rb +278 -0
  94. data/lib/railroader/processors/controller_processor.rb +249 -0
  95. data/lib/railroader/processors/erb_template_processor.rb +77 -0
  96. data/lib/railroader/processors/erubis_template_processor.rb +92 -0
  97. data/lib/railroader/processors/gem_processor.rb +64 -0
  98. data/lib/railroader/processors/haml_template_processor.rb +191 -0
  99. data/lib/railroader/processors/lib/basic_processor.rb +37 -0
  100. data/lib/railroader/processors/lib/call_conversion_helper.rb +90 -0
  101. data/lib/railroader/processors/lib/find_all_calls.rb +224 -0
  102. data/lib/railroader/processors/lib/find_call.rb +183 -0
  103. data/lib/railroader/processors/lib/find_return_value.rb +166 -0
  104. data/lib/railroader/processors/lib/module_helper.rb +111 -0
  105. data/lib/railroader/processors/lib/processor_helper.rb +88 -0
  106. data/lib/railroader/processors/lib/rails2_config_processor.rb +145 -0
  107. data/lib/railroader/processors/lib/rails2_route_processor.rb +313 -0
  108. data/lib/railroader/processors/lib/rails3_config_processor.rb +132 -0
  109. data/lib/railroader/processors/lib/rails3_route_processor.rb +308 -0
  110. data/lib/railroader/processors/lib/render_helper.rb +181 -0
  111. data/lib/railroader/processors/lib/render_path.rb +107 -0
  112. data/lib/railroader/processors/lib/route_helper.rb +68 -0
  113. data/lib/railroader/processors/lib/safe_call_helper.rb +16 -0
  114. data/lib/railroader/processors/library_processor.rb +74 -0
  115. data/lib/railroader/processors/model_processor.rb +91 -0
  116. data/lib/railroader/processors/output_processor.rb +144 -0
  117. data/lib/railroader/processors/route_processor.rb +17 -0
  118. data/lib/railroader/processors/slim_template_processor.rb +111 -0
  119. data/lib/railroader/processors/template_alias_processor.rb +118 -0
  120. data/lib/railroader/processors/template_processor.rb +85 -0
  121. data/lib/railroader/report/config/remediation.yml +71 -0
  122. data/lib/railroader/report/ignore/config.rb +153 -0
  123. data/lib/railroader/report/ignore/interactive.rb +362 -0
  124. data/lib/railroader/report/pager.rb +112 -0
  125. data/lib/railroader/report/renderer.rb +24 -0
  126. data/lib/railroader/report/report_base.rb +292 -0
  127. data/lib/railroader/report/report_codeclimate.rb +79 -0
  128. data/lib/railroader/report/report_csv.rb +55 -0
  129. data/lib/railroader/report/report_hash.rb +23 -0
  130. data/lib/railroader/report/report_html.rb +216 -0
  131. data/lib/railroader/report/report_json.rb +45 -0
  132. data/lib/railroader/report/report_markdown.rb +107 -0
  133. data/lib/railroader/report/report_table.rb +117 -0
  134. data/lib/railroader/report/report_tabs.rb +17 -0
  135. data/lib/railroader/report/report_text.rb +198 -0
  136. data/lib/railroader/report/templates/controller_overview.html.erb +22 -0
  137. data/lib/railroader/report/templates/controller_warnings.html.erb +21 -0
  138. data/lib/railroader/report/templates/error_overview.html.erb +29 -0
  139. data/lib/railroader/report/templates/header.html.erb +58 -0
  140. data/lib/railroader/report/templates/ignored_warnings.html.erb +25 -0
  141. data/lib/railroader/report/templates/model_warnings.html.erb +21 -0
  142. data/lib/railroader/report/templates/overview.html.erb +38 -0
  143. data/lib/railroader/report/templates/security_warnings.html.erb +23 -0
  144. data/lib/railroader/report/templates/template_overview.html.erb +21 -0
  145. data/lib/railroader/report/templates/view_warnings.html.erb +34 -0
  146. data/lib/railroader/report/templates/warning_overview.html.erb +17 -0
  147. data/lib/railroader/report.rb +88 -0
  148. data/lib/railroader/rescanner.rb +483 -0
  149. data/lib/railroader/scanner.rb +321 -0
  150. data/lib/railroader/tracker/collection.rb +93 -0
  151. data/lib/railroader/tracker/config.rb +154 -0
  152. data/lib/railroader/tracker/constants.rb +171 -0
  153. data/lib/railroader/tracker/controller.rb +161 -0
  154. data/lib/railroader/tracker/library.rb +17 -0
  155. data/lib/railroader/tracker/model.rb +90 -0
  156. data/lib/railroader/tracker/template.rb +33 -0
  157. data/lib/railroader/tracker.rb +362 -0
  158. data/lib/railroader/util.rb +503 -0
  159. data/lib/railroader/version.rb +3 -0
  160. data/lib/railroader/warning.rb +294 -0
  161. data/lib/railroader/warning_codes.rb +117 -0
  162. data/lib/railroader.rb +544 -0
  163. data/lib/ruby_parser/bm_sexp.rb +626 -0
  164. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  165. metadata +386 -0
@@ -0,0 +1,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