brakeman-lib 3.3.1

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 (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES +872 -0
  3. data/FEATURES +16 -0
  4. data/README.md +169 -0
  5. data/WARNING_TYPES +95 -0
  6. data/bin/brakeman +89 -0
  7. data/lib/brakeman.rb +495 -0
  8. data/lib/brakeman/app_tree.rb +161 -0
  9. data/lib/brakeman/brakeman.rake +17 -0
  10. data/lib/brakeman/call_index.rb +219 -0
  11. data/lib/brakeman/checks.rb +191 -0
  12. data/lib/brakeman/checks/base_check.rb +518 -0
  13. data/lib/brakeman/checks/check_basic_auth.rb +88 -0
  14. data/lib/brakeman/checks/check_basic_auth_timing_attack.rb +33 -0
  15. data/lib/brakeman/checks/check_content_tag.rb +160 -0
  16. data/lib/brakeman/checks/check_create_with.rb +75 -0
  17. data/lib/brakeman/checks/check_cross_site_scripting.rb +385 -0
  18. data/lib/brakeman/checks/check_default_routes.rb +86 -0
  19. data/lib/brakeman/checks/check_deserialize.rb +57 -0
  20. data/lib/brakeman/checks/check_detailed_exceptions.rb +55 -0
  21. data/lib/brakeman/checks/check_digest_dos.rb +38 -0
  22. data/lib/brakeman/checks/check_dynamic_finders.rb +49 -0
  23. data/lib/brakeman/checks/check_escape_function.rb +21 -0
  24. data/lib/brakeman/checks/check_evaluation.rb +36 -0
  25. data/lib/brakeman/checks/check_execute.rb +167 -0
  26. data/lib/brakeman/checks/check_file_access.rb +63 -0
  27. data/lib/brakeman/checks/check_file_disclosure.rb +35 -0
  28. data/lib/brakeman/checks/check_filter_skipping.rb +31 -0
  29. data/lib/brakeman/checks/check_forgery_setting.rb +74 -0
  30. data/lib/brakeman/checks/check_header_dos.rb +31 -0
  31. data/lib/brakeman/checks/check_i18n_xss.rb +48 -0
  32. data/lib/brakeman/checks/check_jruby_xml.rb +38 -0
  33. data/lib/brakeman/checks/check_json_encoding.rb +47 -0
  34. data/lib/brakeman/checks/check_json_parsing.rb +107 -0
  35. data/lib/brakeman/checks/check_link_to.rb +132 -0
  36. data/lib/brakeman/checks/check_link_to_href.rb +115 -0
  37. data/lib/brakeman/checks/check_mail_to.rb +49 -0
  38. data/lib/brakeman/checks/check_mass_assignment.rb +198 -0
  39. data/lib/brakeman/checks/check_mime_type_dos.rb +39 -0
  40. data/lib/brakeman/checks/check_model_attr_accessible.rb +55 -0
  41. data/lib/brakeman/checks/check_model_attributes.rb +119 -0
  42. data/lib/brakeman/checks/check_model_serialize.rb +67 -0
  43. data/lib/brakeman/checks/check_nested_attributes.rb +38 -0
  44. data/lib/brakeman/checks/check_nested_attributes_bypass.rb +58 -0
  45. data/lib/brakeman/checks/check_number_to_currency.rb +74 -0
  46. data/lib/brakeman/checks/check_quote_table_name.rb +40 -0
  47. data/lib/brakeman/checks/check_redirect.rb +215 -0
  48. data/lib/brakeman/checks/check_regex_dos.rb +69 -0
  49. data/lib/brakeman/checks/check_render.rb +92 -0
  50. data/lib/brakeman/checks/check_render_dos.rb +37 -0
  51. data/lib/brakeman/checks/check_render_inline.rb +54 -0
  52. data/lib/brakeman/checks/check_response_splitting.rb +21 -0
  53. data/lib/brakeman/checks/check_route_dos.rb +42 -0
  54. data/lib/brakeman/checks/check_safe_buffer_manipulation.rb +31 -0
  55. data/lib/brakeman/checks/check_sanitize_methods.rb +79 -0
  56. data/lib/brakeman/checks/check_secrets.rb +40 -0
  57. data/lib/brakeman/checks/check_select_tag.rb +60 -0
  58. data/lib/brakeman/checks/check_select_vulnerability.rb +60 -0
  59. data/lib/brakeman/checks/check_send.rb +48 -0
  60. data/lib/brakeman/checks/check_send_file.rb +19 -0
  61. data/lib/brakeman/checks/check_session_manipulation.rb +36 -0
  62. data/lib/brakeman/checks/check_session_settings.rb +170 -0
  63. data/lib/brakeman/checks/check_simple_format.rb +59 -0
  64. data/lib/brakeman/checks/check_single_quotes.rb +101 -0
  65. data/lib/brakeman/checks/check_skip_before_filter.rb +60 -0
  66. data/lib/brakeman/checks/check_sql.rb +660 -0
  67. data/lib/brakeman/checks/check_sql_cves.rb +101 -0
  68. data/lib/brakeman/checks/check_ssl_verify.rb +49 -0
  69. data/lib/brakeman/checks/check_strip_tags.rb +89 -0
  70. data/lib/brakeman/checks/check_symbol_dos.rb +64 -0
  71. data/lib/brakeman/checks/check_symbol_dos_cve.rb +30 -0
  72. data/lib/brakeman/checks/check_translate_bug.rb +45 -0
  73. data/lib/brakeman/checks/check_unsafe_reflection.rb +51 -0
  74. data/lib/brakeman/checks/check_unscoped_find.rb +41 -0
  75. data/lib/brakeman/checks/check_validation_regex.rb +116 -0
  76. data/lib/brakeman/checks/check_weak_hash.rb +151 -0
  77. data/lib/brakeman/checks/check_without_protection.rb +80 -0
  78. data/lib/brakeman/checks/check_xml_dos.rb +51 -0
  79. data/lib/brakeman/checks/check_yaml_parsing.rb +121 -0
  80. data/lib/brakeman/differ.rb +66 -0
  81. data/lib/brakeman/file_parser.rb +50 -0
  82. data/lib/brakeman/format/style.css +133 -0
  83. data/lib/brakeman/options.rb +301 -0
  84. data/lib/brakeman/parsers/rails2_erubis.rb +6 -0
  85. data/lib/brakeman/parsers/rails2_xss_plugin_erubis.rb +48 -0
  86. data/lib/brakeman/parsers/rails3_erubis.rb +74 -0
  87. data/lib/brakeman/parsers/template_parser.rb +89 -0
  88. data/lib/brakeman/processor.rb +102 -0
  89. data/lib/brakeman/processors/alias_processor.rb +1013 -0
  90. data/lib/brakeman/processors/base_processor.rb +277 -0
  91. data/lib/brakeman/processors/config_processor.rb +14 -0
  92. data/lib/brakeman/processors/controller_alias_processor.rb +273 -0
  93. data/lib/brakeman/processors/controller_processor.rb +326 -0
  94. data/lib/brakeman/processors/erb_template_processor.rb +80 -0
  95. data/lib/brakeman/processors/erubis_template_processor.rb +104 -0
  96. data/lib/brakeman/processors/gem_processor.rb +57 -0
  97. data/lib/brakeman/processors/haml_template_processor.rb +190 -0
  98. data/lib/brakeman/processors/lib/basic_processor.rb +37 -0
  99. data/lib/brakeman/processors/lib/find_all_calls.rb +223 -0
  100. data/lib/brakeman/processors/lib/find_call.rb +183 -0
  101. data/lib/brakeman/processors/lib/find_return_value.rb +134 -0
  102. data/lib/brakeman/processors/lib/processor_helper.rb +75 -0
  103. data/lib/brakeman/processors/lib/rails2_config_processor.rb +145 -0
  104. data/lib/brakeman/processors/lib/rails2_route_processor.rb +313 -0
  105. data/lib/brakeman/processors/lib/rails3_config_processor.rb +132 -0
  106. data/lib/brakeman/processors/lib/rails3_route_processor.rb +308 -0
  107. data/lib/brakeman/processors/lib/render_helper.rb +181 -0
  108. data/lib/brakeman/processors/lib/render_path.rb +107 -0
  109. data/lib/brakeman/processors/lib/route_helper.rb +68 -0
  110. data/lib/brakeman/processors/lib/safe_call_helper.rb +16 -0
  111. data/lib/brakeman/processors/library_processor.rb +119 -0
  112. data/lib/brakeman/processors/model_processor.rb +191 -0
  113. data/lib/brakeman/processors/output_processor.rb +171 -0
  114. data/lib/brakeman/processors/route_processor.rb +17 -0
  115. data/lib/brakeman/processors/slim_template_processor.rb +107 -0
  116. data/lib/brakeman/processors/template_alias_processor.rb +116 -0
  117. data/lib/brakeman/processors/template_processor.rb +74 -0
  118. data/lib/brakeman/report.rb +78 -0
  119. data/lib/brakeman/report/config/remediation.yml +71 -0
  120. data/lib/brakeman/report/ignore/config.rb +135 -0
  121. data/lib/brakeman/report/ignore/interactive.rb +311 -0
  122. data/lib/brakeman/report/renderer.rb +24 -0
  123. data/lib/brakeman/report/report_base.rb +286 -0
  124. data/lib/brakeman/report/report_codeclimate.rb +70 -0
  125. data/lib/brakeman/report/report_csv.rb +55 -0
  126. data/lib/brakeman/report/report_hash.rb +23 -0
  127. data/lib/brakeman/report/report_html.rb +216 -0
  128. data/lib/brakeman/report/report_json.rb +42 -0
  129. data/lib/brakeman/report/report_markdown.rb +156 -0
  130. data/lib/brakeman/report/report_table.rb +107 -0
  131. data/lib/brakeman/report/report_tabs.rb +17 -0
  132. data/lib/brakeman/report/templates/controller_overview.html.erb +22 -0
  133. data/lib/brakeman/report/templates/controller_warnings.html.erb +21 -0
  134. data/lib/brakeman/report/templates/error_overview.html.erb +29 -0
  135. data/lib/brakeman/report/templates/header.html.erb +58 -0
  136. data/lib/brakeman/report/templates/ignored_warnings.html.erb +25 -0
  137. data/lib/brakeman/report/templates/model_warnings.html.erb +21 -0
  138. data/lib/brakeman/report/templates/overview.html.erb +38 -0
  139. data/lib/brakeman/report/templates/security_warnings.html.erb +23 -0
  140. data/lib/brakeman/report/templates/template_overview.html.erb +21 -0
  141. data/lib/brakeman/report/templates/view_warnings.html.erb +34 -0
  142. data/lib/brakeman/report/templates/warning_overview.html.erb +17 -0
  143. data/lib/brakeman/rescanner.rb +483 -0
  144. data/lib/brakeman/scanner.rb +317 -0
  145. data/lib/brakeman/tracker.rb +347 -0
  146. data/lib/brakeman/tracker/collection.rb +93 -0
  147. data/lib/brakeman/tracker/config.rb +101 -0
  148. data/lib/brakeman/tracker/constants.rb +101 -0
  149. data/lib/brakeman/tracker/controller.rb +161 -0
  150. data/lib/brakeman/tracker/library.rb +17 -0
  151. data/lib/brakeman/tracker/model.rb +90 -0
  152. data/lib/brakeman/tracker/template.rb +33 -0
  153. data/lib/brakeman/util.rb +481 -0
  154. data/lib/brakeman/version.rb +3 -0
  155. data/lib/brakeman/warning.rb +255 -0
  156. data/lib/brakeman/warning_codes.rb +111 -0
  157. data/lib/ruby_parser/bm_sexp.rb +610 -0
  158. data/lib/ruby_parser/bm_sexp_processor.rb +116 -0
  159. metadata +362 -0
@@ -0,0 +1,86 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #Checks if default routes are allowed in routes.rb
4
+ class Brakeman::CheckDefaultRoutes < Brakeman::BaseCheck
5
+ Brakeman::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 => 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
+ Brakeman.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 => CONFIDENCE[:med],
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 = CONFIDENCE[:high]
71
+ else
72
+ confidence = CONFIDENCE[:med]
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,57 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckDeserialize < Brakeman::BaseCheck
4
+ Brakeman::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 if duplicate? result
34
+ add_result result
35
+
36
+ arg ||= result[:call].first_arg
37
+ method = result[:call].method
38
+
39
+ if input = has_immediate_user_input?(arg)
40
+ confidence = CONFIDENCE[:high]
41
+ elsif input = include_user_input?(arg)
42
+ confidence = CONFIDENCE[:med]
43
+ end
44
+
45
+ if confidence
46
+ message = "#{target}.#{method} called with #{friendly_type_of input}"
47
+
48
+ warn :result => result,
49
+ :warning_type => "Remote Code Execution",
50
+ :warning_code => :unsafe_deserialize,
51
+ :message => message,
52
+ :user_input => input,
53
+ :confidence => confidence,
54
+ :link_path => "unsafe_deserialization"
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ # Check for detailed exceptions enabled for production
4
+ class Brakeman::CheckDetailedExceptions < Brakeman::BaseCheck
5
+ Brakeman::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 => 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 = CONFIDENCE[:high]
36
+ else
37
+ confidence = CONFIDENCE[:med]
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 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckDigestDoS < Brakeman::BaseCheck
4
+ Brakeman::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 = CONFIDENCE[:high]
23
+ else
24
+ confidence = CONFIDENCE[:low]
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,49 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #This check looks for regexes that include user input.
4
+ class Brakeman::CheckDynamicFinders < Brakeman::BaseCheck
5
+ Brakeman::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 if duplicate? result or result[:call].original_line
19
+ add_result result
20
+
21
+ call = result[:call]
22
+
23
+ if potentially_dangerous? call.method
24
+ call.each_arg do |arg|
25
+ if params? arg and not safe_call? arg
26
+ warn :result => result,
27
+ :warning_type => "SQL Injection",
28
+ :warning_code => :sql_injection_dynamic_finder,
29
+ :message => "MySQL integer conversion may cause 0 to match any string",
30
+ :confidence => CONFIDENCE[:med],
31
+ :user_input => arg
32
+
33
+ break
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ def safe_call? arg
40
+ return false unless call? arg
41
+
42
+ meth = arg.method
43
+ meth == :to_s or meth == :to_i
44
+ end
45
+
46
+ def potentially_dangerous? method_name
47
+ method_name.match /^find_by_.*(token|guid|password|api_key|activation|code|private|reset)/
48
+ end
49
+ end
@@ -0,0 +1,21 @@
1
+ require 'brakeman/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 Brakeman::CheckEscapeFunction < Brakeman::BaseCheck
6
+ Brakeman::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 => 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,36 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #This check looks for calls to +eval+, +instance_eval+, etc. which include
4
+ #user input.
5
+ class Brakeman::CheckEvaluation < Brakeman::BaseCheck
6
+ Brakeman::Checks.add self
7
+
8
+ @description = "Searches for evaluation of user input"
9
+
10
+ #Process calls
11
+ def run_check
12
+ Brakeman.debug "Finding eval-like calls"
13
+ calls = tracker.find_call :method => [:eval, :instance_eval, :class_eval, :module_eval]
14
+
15
+ Brakeman.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 if duplicate? result or result[:call].original_line
24
+ add_result result
25
+
26
+ if input = include_user_input?(result[:call].arglist)
27
+ warn :result => result,
28
+ :warning_type => "Dangerous Eval",
29
+ :warning_code => :code_eval,
30
+ :message => "User input in eval",
31
+ :code => result[:call],
32
+ :user_input => input,
33
+ :confidence => CONFIDENCE[:high]
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,167 @@
1
+ require 'brakeman/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 Brakeman::CheckExecute < Brakeman::BaseCheck
12
+ Brakeman::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
+
20
+ #Check models, controllers, and views for command injection.
21
+ def run_check
22
+ Brakeman.debug "Finding system calls using ``"
23
+ check_for_backticks tracker
24
+
25
+ check_open_calls
26
+
27
+ Brakeman.debug "Finding other system calls"
28
+ calls = tracker.find_call :targets => [:IO, :Open3, :Kernel, :'POSIX::Spawn', :Process, nil],
29
+ :methods => [:capture2, :capture2e, :capture3, :exec, :pipeline, :pipeline_r,
30
+ :pipeline_rw, :pipeline_start, :pipeline_w, :popen, :popen2, :popen2e,
31
+ :popen3, :spawn, :syscall, :system]
32
+
33
+ Brakeman.debug "Processing system calls"
34
+ calls.each do |result|
35
+ process_result result
36
+ end
37
+ end
38
+
39
+ #Processes results from Tracker#find_call.
40
+ def process_result result
41
+ call = result[:call]
42
+ args = call.arglist
43
+ first_arg = call.first_arg
44
+
45
+ case call.method
46
+ when :popen
47
+ unless array? first_arg
48
+ failure = include_user_input?(args) || dangerous_interp?(args)
49
+ end
50
+ when :system, :exec
51
+ failure = include_user_input?(first_arg) || dangerous_interp?(first_arg)
52
+ else
53
+ failure = include_user_input?(args) || dangerous_interp?(args)
54
+ end
55
+
56
+ if failure and not duplicate? result
57
+ add_result result
58
+
59
+ if failure.type == :interp #Not from user input
60
+ confidence = CONFIDENCE[:med]
61
+ else
62
+ confidence = CONFIDENCE[:high]
63
+ end
64
+
65
+ warn :result => result,
66
+ :warning_type => "Command Injection",
67
+ :warning_code => :command_injection,
68
+ :message => "Possible command injection",
69
+ :code => call,
70
+ :user_input => failure,
71
+ :confidence => confidence
72
+ end
73
+ end
74
+
75
+ def check_open_calls
76
+ tracker.find_call(:targets => [nil, :Kernel], :method => :open).each do |result|
77
+ if match = dangerous_open_arg?(result[:call].first_arg)
78
+ warn :result => result,
79
+ :warning_type => "Command Injection",
80
+ :warning_code => :command_injection,
81
+ :message => "Possible command injection in open()",
82
+ :user_input => match,
83
+ :confidence => CONFIDENCE[:high]
84
+ end
85
+ end
86
+ end
87
+
88
+ def dangerous_open_arg? exp
89
+ if string_interp? exp
90
+ # Check for input at start of string
91
+ exp[1] == "" and
92
+ node_type? exp[2], :evstr and
93
+ has_immediate_user_input? exp[2]
94
+ else
95
+ has_immediate_user_input? exp
96
+ end
97
+ end
98
+
99
+ #Looks for calls using backticks such as
100
+ #
101
+ # `rm -rf #{params[:file]}`
102
+ def check_for_backticks tracker
103
+ tracker.find_call(:target => nil, :method => :`).each do |result|
104
+ process_backticks result
105
+ end
106
+ end
107
+
108
+ #Processes backticks.
109
+ def process_backticks result
110
+ return if duplicate? result
111
+
112
+ add_result result
113
+
114
+ exp = result[:call]
115
+
116
+ if input = include_user_input?(exp)
117
+ confidence = CONFIDENCE[:high]
118
+ elsif input = dangerous?(exp)
119
+ confidence = CONFIDENCE[:med]
120
+ else
121
+ return
122
+ end
123
+
124
+ warn :result => result,
125
+ :warning_type => "Command Injection",
126
+ :warning_code => :command_injection,
127
+ :message => "Possible command injection",
128
+ :code => exp,
129
+ :user_input => input,
130
+ :confidence => confidence
131
+ end
132
+
133
+ def dangerous? exp
134
+ exp.each_sexp do |e|
135
+ next if node_type? e, :lit, :str
136
+ next if SAFE_VALUES.include? e
137
+
138
+ if call? e and e.method == :to_s
139
+ e = e.target
140
+ end
141
+
142
+ if node_type? e, :or, :evstr, :dstr
143
+ if res = dangerous?(e)
144
+ return res
145
+ end
146
+ else
147
+ return e
148
+ end
149
+ end
150
+
151
+ false
152
+ end
153
+
154
+ def dangerous_interp? exp
155
+ match = include_interp? exp
156
+ return unless match
157
+ interp = match.match
158
+
159
+ interp.each_sexp do |e|
160
+ if res = dangerous?(e)
161
+ return Match.new(:interp, res)
162
+ end
163
+ end
164
+
165
+ false
166
+ end
167
+ end