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,88 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ #Checks if password is stored in controller
4
+ #when using http_basic_authenticate_with
5
+ #
6
+ #Only for Rails >= 3.1
7
+ class Brakeman::CheckBasicAuth < Brakeman::BaseCheck
8
+ Brakeman::Checks.add self
9
+
10
+ @description = "Checks for the use of http_basic_authenticate_with"
11
+
12
+ def run_check
13
+ return if version_between? "0.0.0", "3.0.99"
14
+
15
+ check_basic_auth_filter
16
+ check_basic_auth_request
17
+ end
18
+
19
+ def check_basic_auth_filter
20
+ controllers = tracker.controllers.select do |name, c|
21
+ c.options[:http_basic_authenticate_with]
22
+ end
23
+
24
+ Hash[controllers].each do |name, controller|
25
+ controller.options[:http_basic_authenticate_with].each do |call|
26
+
27
+ if pass = get_password(call) and string? pass
28
+ warn :controller => name,
29
+ :warning_type => "Basic Auth",
30
+ :warning_code => :basic_auth_password,
31
+ :message => "Basic authentication password stored in source code",
32
+ :code => call,
33
+ :confidence => 0,
34
+ :file => controller.file
35
+ break
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # Look for
42
+ # authenticate_or_request_with_http_basic do |username, password|
43
+ # username == "foo" && password == "bar"
44
+ # end
45
+ def check_basic_auth_request
46
+ tracker.find_call(:target => nil, :method => :authenticate_or_request_with_http_basic).each do |result|
47
+ if include_password_literal? result
48
+ warn :result => result,
49
+ :code => @include_password,
50
+ :warning_type => "Basic Auth",
51
+ :warning_code => :basic_auth_password,
52
+ :message => "Basic authentication password stored in source code",
53
+ :confidence => 0
54
+ end
55
+ end
56
+ end
57
+
58
+ # Check if the block of a result contains a comparison of password to string
59
+ def include_password_literal? result
60
+ @password_var = result[:block_args].last
61
+ @include_password = false
62
+ process result[:block]
63
+ @include_password
64
+ end
65
+
66
+ # Looks for :== calls on password var
67
+ def process_call exp
68
+ target = exp.target
69
+
70
+ if node_type?(target, :lvar) and
71
+ target.value == @password_var and
72
+ exp.method == :== and
73
+ string? exp.first_arg
74
+
75
+ @include_password = exp
76
+ end
77
+
78
+ exp
79
+ end
80
+
81
+ def get_password call
82
+ arg = call.first_arg
83
+
84
+ return false if arg.nil? or not hash? arg
85
+
86
+ hash_access(arg, :password)
87
+ end
88
+ end
@@ -0,0 +1,33 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckBasicAuthTimingAttack < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Check for timing attack in basic auth (CVE-2015-7576)"
7
+
8
+ def run_check
9
+ @upgrade = case
10
+ when version_between?("0.0.0", "3.2.22")
11
+ "3.2.22.1"
12
+ when version_between?("4.0.0", "4.1.14")
13
+ "4.1.14.1"
14
+ when version_between?("4.2.0", "4.2.5")
15
+ "4.2.5.1"
16
+ else
17
+ return
18
+ end
19
+
20
+ check_basic_auth_call
21
+ end
22
+
23
+ def check_basic_auth_call
24
+ tracker.find_call(target: nil, method: :http_basic_authenticate_with).each do |result|
25
+ warn :result => result,
26
+ :warning_type => "Timing Attack",
27
+ :warning_code => :CVE_2015_7576,
28
+ :message => "Basic authentication in Rails #{rails_version} is vulnerable to timing attacks. Upgrade to #@upgrade",
29
+ :confidence => CONFIDENCE[:high],
30
+ :link => "https://groups.google.com/d/msg/rubyonrails-security/ANv0HDHEC3k/mt7wNGxbFQAJ"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,160 @@
1
+ require 'brakeman/checks/check_cross_site_scripting'
2
+
3
+ #Checks for unescaped values in `content_tag`
4
+ #
5
+ # content_tag :tag, body
6
+ # ^-- Unescaped in Rails 2.x
7
+ #
8
+ # content_tag, :tag, body, attribute => value
9
+ # ^-- Unescaped in all versions
10
+ #
11
+ # content_tag, :tag, body, attribute => value
12
+ # ^
13
+ # |
14
+ # Escaped by default, can be explicitly escaped
15
+ # or not by passing in (true|false) as fourth argument
16
+ class Brakeman::CheckContentTag < Brakeman::CheckCrossSiteScripting
17
+ Brakeman::Checks.add self
18
+
19
+ @description = "Checks for XSS in calls to content_tag"
20
+
21
+ def run_check
22
+ @ignore_methods = Set[:button_to, :check_box, :escapeHTML, :escape_once,
23
+ :field_field, :fields_for, :h, :hidden_field,
24
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
25
+ :mail_to, :radio_button, :select,
26
+ :submit_tag, :text_area, :text_field,
27
+ :text_field_tag, :url_encode, :u, :url_for,
28
+ :will_paginate].merge tracker.options[:safe_methods]
29
+
30
+ @known_dangerous = []
31
+ methods = tracker.find_call :target => false, :method => :content_tag
32
+
33
+ @models = tracker.models.keys
34
+ @inspect_arguments = tracker.options[:check_arguments]
35
+ @mark = nil
36
+
37
+ Brakeman.debug "Checking for XSS in content_tag"
38
+ methods.each do |call|
39
+ process_result call
40
+ end
41
+ end
42
+
43
+ def process_result result
44
+ return if duplicate? result
45
+
46
+ call = result[:call] = result[:call].dup
47
+
48
+ args = call.arglist
49
+
50
+ tag_name = args[1]
51
+ content = args[2]
52
+ attributes = args[3]
53
+ escape_attr = args[4]
54
+
55
+ @matched = false
56
+
57
+ #Silly, but still dangerous if someone uses user input in the tag type
58
+ check_argument result, tag_name
59
+
60
+ #Versions before 3.x do not escape body of tag, nor does the rails_xss gem
61
+ unless @matched or (tracker.options[:rails3] and not raw? content)
62
+ check_argument result, content
63
+ end
64
+
65
+ #Attribute keys are never escaped, so check them for user input
66
+ if not @matched and hash? attributes and not request_value? attributes
67
+ hash_iterate(attributes) do |k, v|
68
+ check_argument result, k
69
+ return if @matched
70
+ end
71
+ end
72
+
73
+ #By default, content_tag escapes attribute values passed in as a hash.
74
+ #But this behavior can be disabled. So only check attributes hash
75
+ #if they are explicitly not escaped.
76
+ if not @matched and attributes and false? escape_attr
77
+ if request_value? attributes or not hash? attributes
78
+ check_argument result, attributes
79
+ else #check hash values
80
+ hash_iterate(attributes) do |k, v|
81
+ check_argument result, v
82
+ return if @matched
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ def check_argument result, exp
89
+ #Check contents of raw() calls directly
90
+ if call? exp and exp.method == :raw
91
+ arg = process exp.first_arg
92
+ else
93
+ arg = process exp
94
+ end
95
+
96
+ if input = has_immediate_user_input?(arg)
97
+ message = "Unescaped #{friendly_type_of input} in content_tag"
98
+
99
+ add_result result
100
+
101
+ warn :result => result,
102
+ :warning_type => "Cross Site Scripting",
103
+ :warning_code => :xss_content_tag,
104
+ :message => message,
105
+ :user_input => input,
106
+ :confidence => CONFIDENCE[:high],
107
+ :link_path => "content_tag"
108
+
109
+ elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(arg)
110
+ unless IGNORE_MODEL_METHODS.include? match.method
111
+ add_result result
112
+
113
+ if likely_model_attribute? match
114
+ confidence = CONFIDENCE[:high]
115
+ else
116
+ confidence = CONFIDENCE[:med]
117
+ end
118
+
119
+ warn :result => result,
120
+ :warning_type => "Cross Site Scripting",
121
+ :warning_code => :xss_content_tag,
122
+ :message => "Unescaped model attribute in content_tag",
123
+ :user_input => match,
124
+ :confidence => confidence,
125
+ :link_path => "content_tag"
126
+ end
127
+
128
+ elsif @matched
129
+ return if @matched.type == :model and tracker.options[:ignore_model_output]
130
+
131
+ message = "Unescaped #{friendly_type_of @matched} in content_tag"
132
+
133
+ add_result result
134
+
135
+ warn :result => result,
136
+ :warning_type => "Cross Site Scripting",
137
+ :warning_code => :xss_content_tag,
138
+ :message => message,
139
+ :user_input => @matched,
140
+ :confidence => CONFIDENCE[:med],
141
+ :link_path => "content_tag"
142
+ end
143
+ end
144
+
145
+ def process_call exp
146
+ if @mark
147
+ actually_process_call exp
148
+ else
149
+ @mark = true
150
+ actually_process_call exp
151
+ @mark = false
152
+ end
153
+
154
+ exp
155
+ end
156
+
157
+ def raw? exp
158
+ call? exp and exp.method == :raw
159
+ end
160
+ end
@@ -0,0 +1,75 @@
1
+ require 'brakeman/checks/base_check'
2
+
3
+ class Brakeman::CheckCreateWith < Brakeman::BaseCheck
4
+ Brakeman::Checks.add self
5
+
6
+ @description = "Checks for strong params bypass in CVE-2014-3514"
7
+
8
+ def run_check
9
+ @warned = false
10
+
11
+ if version_between? "4.0.0", "4.0.8"
12
+ suggested_version = "4.0.9"
13
+ elsif version_between? "4.1.0", "4.1.4"
14
+ suggested_version = "4.1.5"
15
+ else
16
+ return
17
+ end
18
+
19
+ @message = "create_with is vulnerable to strong params bypass. Upgrade to Rails #{suggested_version} or patch"
20
+
21
+ tracker.find_call(:method => :create_with, :nested => true).each do |result|
22
+ process_result result
23
+ end
24
+
25
+ generic_warning unless @warned
26
+ end
27
+
28
+ def process_result result
29
+ return if duplicate? result
30
+ add_result result
31
+ arg = result[:call].first_arg
32
+
33
+ confidence = danger_level arg
34
+
35
+ if confidence
36
+ @warned = true
37
+
38
+ warn :warning_type => "Mass Assignment",
39
+ :warning_code => :CVE_2014_3514_call,
40
+ :result => result,
41
+ :message => @message,
42
+ :confidence => confidence,
43
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/M4chq5Sb540/CC1Fh0Y_NWwJ"
44
+ end
45
+ end
46
+
47
+ #For a given create_with call, set confidence level.
48
+ #Ignore calls that use permit()
49
+ def danger_level exp
50
+ return unless sexp? exp
51
+
52
+ if call? exp and exp.method == :permit
53
+ nil
54
+ elsif request_value? exp
55
+ CONFIDENCE[:high]
56
+ elsif hash? exp
57
+ nil
58
+ elsif has_immediate_user_input?(exp)
59
+ CONFIDENCE[:high]
60
+ elsif include_user_input? exp
61
+ CONFIDENCE[:med]
62
+ else
63
+ CONFIDENCE[:low]
64
+ end
65
+ end
66
+
67
+ def generic_warning
68
+ warn :warning_type => "Mass Assignment",
69
+ :warning_code => :CVE_2014_3514,
70
+ :message => @message,
71
+ :gem_info => gemfile_or_environment,
72
+ :confidence => CONFIDENCE[:med],
73
+ :link_path => "https://groups.google.com/d/msg/rubyonrails-security/M4chq5Sb540/CC1Fh0Y_NWwJ"
74
+ end
75
+ end
@@ -0,0 +1,385 @@
1
+ require 'brakeman/checks/base_check'
2
+ require 'brakeman/processors/lib/find_call'
3
+ require 'brakeman/processors/lib/processor_helper'
4
+ require 'brakeman/util'
5
+ require 'set'
6
+
7
+ #This check looks for unescaped output in templates which contains
8
+ #parameters or model attributes.
9
+ #
10
+ #For example:
11
+ #
12
+ # <%= User.find(:id).name %>
13
+ # <%= params[:id] %>
14
+ class Brakeman::CheckCrossSiteScripting < Brakeman::BaseCheck
15
+ Brakeman::Checks.add self
16
+
17
+ @description = "Checks for unescaped output in views"
18
+
19
+ #Model methods which are known to be harmless
20
+ IGNORE_MODEL_METHODS = Set[:average, :count, :maximum, :minimum, :sum, :id]
21
+
22
+ MODEL_METHODS = Set[:all, :find, :first, :last, :new]
23
+
24
+ IGNORE_LIKE = /^link_to_|(_path|_tag|_url)$/
25
+
26
+ HAML_HELPERS = Sexp.new(:colon2, Sexp.new(:const, :Haml), :Helpers)
27
+
28
+ XML_HELPER = Sexp.new(:colon2, Sexp.new(:const, :Erubis), :XmlHelper)
29
+
30
+ URI = Sexp.new(:const, :URI)
31
+
32
+ CGI = Sexp.new(:const, :CGI)
33
+
34
+ FORM_BUILDER = Sexp.new(:call, Sexp.new(:const, :FormBuilder), :new)
35
+
36
+ #Run check
37
+ def run_check
38
+ setup
39
+
40
+ tracker.each_template do |name, template|
41
+ Brakeman.debug "Checking #{name} for XSS"
42
+
43
+ @current_template = template
44
+
45
+ template.each_output do |out|
46
+ unless check_for_immediate_xss out
47
+ @matched = false
48
+ @mark = false
49
+ process out
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def check_for_immediate_xss exp
56
+ return :duplicate if duplicate? exp
57
+
58
+ if exp.node_type == :output
59
+ out = exp.value
60
+ elsif exp.node_type == :escaped_output
61
+ if raw_call? exp
62
+ out = exp.value.first_arg
63
+ elsif html_safe_call? exp
64
+ out = exp.value.target
65
+ end
66
+ end
67
+
68
+ return if call? out and ignore_call? out.target, out.method
69
+
70
+ if input = has_immediate_user_input?(out)
71
+ add_result exp
72
+
73
+ message = "Unescaped #{friendly_type_of input}"
74
+
75
+ warn :template => @current_template,
76
+ :warning_type => "Cross Site Scripting",
77
+ :warning_code => :cross_site_scripting,
78
+ :message => message,
79
+ :code => input.match,
80
+ :confidence => CONFIDENCE[:high]
81
+
82
+ elsif not tracker.options[:ignore_model_output] and match = has_immediate_model?(out)
83
+ method = if call? match
84
+ match.method
85
+ else
86
+ nil
87
+ end
88
+
89
+ unless IGNORE_MODEL_METHODS.include? method
90
+ add_result exp
91
+
92
+ if likely_model_attribute? match
93
+ confidence = CONFIDENCE[:high]
94
+ else
95
+ confidence = CONFIDENCE[:med]
96
+ end
97
+
98
+ message = "Unescaped model attribute"
99
+ link_path = "cross_site_scripting"
100
+ warning_code = :cross_site_scripting
101
+
102
+ if node_type?(out, :call, :safe_call, :attrasgn, :safe_attrasgn) && out.method == :to_json
103
+ message += " in JSON hash"
104
+ link_path += "_to_json"
105
+ warning_code = :xss_to_json
106
+ end
107
+
108
+ warn :template => @current_template,
109
+ :warning_type => "Cross Site Scripting",
110
+ :warning_code => warning_code,
111
+ :message => message,
112
+ :code => match,
113
+ :confidence => confidence,
114
+ :link_path => link_path
115
+ end
116
+
117
+ else
118
+ false
119
+ end
120
+ end
121
+
122
+ #Call already involves a model, but might not be acting on a record
123
+ def likely_model_attribute? exp
124
+ return false unless call? exp
125
+
126
+ method = exp.method
127
+
128
+ if MODEL_METHODS.include? method or method.to_s.start_with? "find_by_"
129
+ true
130
+ else
131
+ likely_model_attribute? exp.target
132
+ end
133
+ end
134
+
135
+ #Process an output Sexp
136
+ def process_output exp
137
+ process exp.value.dup
138
+ end
139
+
140
+ #Look for calls to raw()
141
+ #Otherwise, ignore
142
+ def process_escaped_output exp
143
+ unless check_for_immediate_xss exp
144
+ if not duplicate? exp
145
+ if raw_call? exp
146
+ process exp.value.first_arg
147
+ elsif html_safe_call? exp
148
+ process exp.value.target
149
+ end
150
+ end
151
+ end
152
+ exp
153
+ end
154
+
155
+ #Check a call for user input
156
+ #
157
+ #
158
+ #Since we want to report an entire call and not just part of one, use @mark
159
+ #to mark when a call is started. Any dangerous values inside will then
160
+ #report the entire call chain.
161
+ def process_call exp
162
+ if @mark
163
+ actually_process_call exp
164
+ else
165
+ @mark = true
166
+ actually_process_call exp
167
+ message = nil
168
+
169
+ if @matched
170
+ unless @matched.type and tracker.options[:ignore_model_output]
171
+ message = "Unescaped #{friendly_type_of @matched}"
172
+ end
173
+
174
+ if message and not duplicate? exp
175
+ add_result exp
176
+
177
+ link_path = "cross_site_scripting"
178
+ warning_code = :cross_site_scripting
179
+
180
+ if @known_dangerous.include? exp.method
181
+ confidence = CONFIDENCE[:high]
182
+ if exp.method == :to_json
183
+ message += " in JSON hash"
184
+ link_path += "_to_json"
185
+ warning_code = :xss_to_json
186
+ end
187
+ else
188
+ confidence = CONFIDENCE[:low]
189
+ end
190
+
191
+ warn :template => @current_template,
192
+ :warning_type => "Cross Site Scripting",
193
+ :warning_code => warning_code,
194
+ :message => message,
195
+ :code => exp,
196
+ :user_input => @matched,
197
+ :confidence => confidence,
198
+ :link_path => link_path
199
+ end
200
+ end
201
+
202
+ @mark = @matched = false
203
+ end
204
+
205
+ exp
206
+ end
207
+
208
+ def actually_process_call exp
209
+ return if @matched
210
+ target = exp.target
211
+ if sexp? target
212
+ target = process target
213
+ end
214
+
215
+ method = exp.method
216
+
217
+ #Ignore safe items
218
+ if ignore_call? target, method
219
+ @matched = false
220
+ elsif sexp? target and model_name? target[1] #TODO: use method call?
221
+ @matched = Match.new(:model, exp)
222
+ elsif cookies? exp
223
+ @matched = Match.new(:cookies, exp)
224
+ elsif @inspect_arguments and params? exp
225
+ @matched = Match.new(:params, exp)
226
+ elsif @inspect_arguments
227
+ process_call_args exp
228
+ end
229
+ end
230
+
231
+ #Note that params have been found
232
+ def process_params exp
233
+ @matched = Match.new(:params, exp)
234
+ exp
235
+ end
236
+
237
+ #Note that cookies have been found
238
+ def process_cookies exp
239
+ @matched = Match.new(:cookies, exp)
240
+ exp
241
+ end
242
+
243
+ #Ignore calls to render
244
+ def process_render exp
245
+ exp
246
+ end
247
+
248
+ #Process as default
249
+ def process_dstr exp
250
+ process_default exp
251
+ end
252
+
253
+ #Process as default
254
+ def process_format exp
255
+ process_default exp
256
+ end
257
+
258
+ #Ignore output HTML escaped via HAML
259
+ def process_format_escaped exp
260
+ exp
261
+ end
262
+
263
+ #Ignore condition in if Sexp
264
+ def process_if exp
265
+ process exp.then_clause if sexp? exp.then_clause
266
+ process exp.else_clause if sexp? exp.else_clause
267
+ exp
268
+ end
269
+
270
+ def process_case exp
271
+ #Ignore user input in case value
272
+ #TODO: also ignore when values
273
+
274
+ current = 2
275
+ while current < exp.length
276
+ process exp[current] if exp[current]
277
+ current += 1
278
+ end
279
+
280
+ exp
281
+ end
282
+
283
+ def setup
284
+ @ignore_methods = Set[:==, :!=, :button_to, :check_box, :content_tag, :escapeHTML, :escape_once,
285
+ :field_field, :fields_for, :h, :hidden_field,
286
+ :hidden_field, :hidden_field_tag, :image_tag, :label,
287
+ :link_to, :mail_to, :radio_button, :select,
288
+ :submit_tag, :text_area, :text_field,
289
+ :text_field_tag, :url_encode, :u, :url_for,
290
+ :will_paginate].merge tracker.options[:safe_methods]
291
+
292
+ @models = tracker.models.keys
293
+ @inspect_arguments = tracker.options[:check_arguments]
294
+
295
+ @known_dangerous = Set[:truncate, :concat]
296
+
297
+ if version_between? "2.0.0", "3.0.5"
298
+ @known_dangerous << :auto_link
299
+ elsif version_between? "3.0.6", "3.0.99"
300
+ @ignore_methods << :auto_link
301
+ end
302
+
303
+ if version_between? "2.0.0", "2.3.14" or tracker.config.gem_version(:'rails-html-sanitizer') == '1.0.2'
304
+ @known_dangerous << :strip_tags
305
+ end
306
+
307
+ if tracker.config.has_gem? :'rails-html-sanitizer' and
308
+ version_between? "1.0.0", "1.0.2", tracker.config.gem_version(:'rails-html-sanitizer')
309
+
310
+ @known_dangerous << :sanitize
311
+ end
312
+
313
+ json_escape_on = false
314
+ initializers = tracker.check_initializers :ActiveSupport, :escape_html_entities_in_json=
315
+ initializers.each {|result| json_escape_on = true?(result.call.first_arg) }
316
+
317
+ if tracker.config.escape_html_entities_in_json?
318
+ json_escape_on = true
319
+ elsif version_between? "4.0.0", "9.9.9"
320
+ json_escape_on = true
321
+ end
322
+
323
+ if !json_escape_on or version_between? "0.0.0", "2.0.99"
324
+ @known_dangerous << :to_json
325
+ Brakeman.debug("Automatic to_json escaping not enabled, consider to_json dangerous")
326
+ else
327
+ @safe_input_attributes << :to_json
328
+ Brakeman.debug("Automatic to_json escaping is enabled.")
329
+ end
330
+ end
331
+
332
+ def raw_call? exp
333
+ exp.value.node_type == :call and exp.value.method == :raw
334
+ end
335
+
336
+ def html_safe_call? exp
337
+ call? exp.value and exp.value.method == :html_safe
338
+ end
339
+
340
+ def ignore_call? target, method
341
+ ignored_method?(target, method) or
342
+ safe_input_attribute?(target, method) or
343
+ ignored_model_method?(target, method) or
344
+ form_builder_method?(target, method) or
345
+ haml_escaped?(target, method) or
346
+ boolean_method?(method) or
347
+ cgi_escaped?(target, method) or
348
+ xml_escaped?(target, method)
349
+ end
350
+
351
+ def ignored_model_method? target, method
352
+ ((@matched and @matched.type == :model) or
353
+ model_name? target) and
354
+ IGNORE_MODEL_METHODS.include? method
355
+ end
356
+
357
+ def ignored_method? target, method
358
+ @ignore_methods.include? method or method.to_s =~ IGNORE_LIKE
359
+ end
360
+
361
+ def cgi_escaped? target, method
362
+ method == :escape and
363
+ (target == URI or target == CGI)
364
+ end
365
+
366
+ def haml_escaped? target, method
367
+ method == :html_escape and target == HAML_HELPERS
368
+ end
369
+
370
+ def xml_escaped? target, method
371
+ method == :escape_xml and target == XML_HELPER
372
+ end
373
+
374
+ def form_builder_method? target, method
375
+ target == FORM_BUILDER and @ignore_methods.include? method
376
+ end
377
+
378
+ def safe_input_attribute? target, method
379
+ target and always_safe_method? method
380
+ end
381
+
382
+ def boolean_method? method
383
+ method.to_s.end_with? "?"
384
+ end
385
+ end