railroader 4.3.4

Sign up to get free protection for your applications and to get access to all the features.
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