Commit ba36dea7 authored by alexpott's avatar alexpott

Issue #2570431 by stefan.r, YesCT, David_Rothstein, xjm, catch, effulgentsia,...

Issue #2570431 by stefan.r, YesCT, David_Rothstein, xjm, catch, effulgentsia, Heine, dawehner, plach, lauriii, alexpott, joelpittet: Document that certain (non-"href") attribute values in t() and SafeMarkup::format() are not supported and may be insecure
parent 4a8366bb
...@@ -10,28 +10,34 @@ ...@@ -10,28 +10,34 @@
/** /**
* Formats a string for HTML display by replacing variable placeholders. * Formats a string for HTML display by replacing variable placeholders.
* *
* When cast to a string it replaces variable placeholders in the string with * When cast to a string, this object replaces variable placeholders in the
* the arguments passed in during construction and escapes the values so they * string with the arguments passed in during construction and escapes the
* can be safely displayed as HTML. It should be used on any unknown text that * values so they can be safely displayed as HTML. See the documentation of
* is intended to be printed to an HTML page (especially text that may have come * \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() for
* from untrusted users, since in that case it prevents cross-site scripting and * details on the supported placeholders and how to use them securely. Incorrect
* other security problems). * use of this class can result in security vulnerabilities.
*
* This class is not intended for passing arbitrary user input into any HTML
* attribute value, as only URL attributes such as "src" and "href" are
* supported (using ":variable"). Never use this method on unsafe HTML
* attributes such as "on*" and "style" and take care when using this with
* unsupported attributes such as "title" or "alt" as this can lead to
* unexpected output.
* *
* In most cases, you should use TranslatableString or PluralTranslatableString * In most cases, you should use TranslatableString or PluralTranslatableString
* rather than this object, since they will translate the text (on * rather than this object, since they will translate the text (on
* non-English-only sites) in addition to formatting it. * non-English-only sites) in addition to formatting it. Variables concatenated
* without the insertion of language-specific words or punctuation are some
* examples where translation is not applicable and using this class directly
* directly is appropriate.
*
* This class is designed for formatting messages that are mostly text, not as
* an HTML template language. As such:
* - The passed in string should contain no (or minimal) HTML.
* - Variable placeholders should not be used within the "<" and ">" of an
* HTML tag, such as in HTML attribute values. This would be a security
* risk.
* To build non-minimal HTML, use an HTML template language such as Twig,
* rather than this class.
* *
* @ingroup sanitization * @ingroup sanitization
* *
* @see \Drupal\Core\StringTranslation\TranslatableString * @see \Drupal\Core\StringTranslation\TranslatableString
* @see \Drupal\Core\StringTranslation\PluralTranslatableString * @see \Drupal\Core\StringTranslation\PluralTranslatableString
* @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat()
*/ */
class FormattableString implements SafeStringInterface { class FormattableString implements SafeStringInterface {
......
...@@ -15,35 +15,87 @@ trait PlaceholderTrait { ...@@ -15,35 +15,87 @@ trait PlaceholderTrait {
/** /**
* Formats a string by replacing variable placeholders. * Formats a string by replacing variable placeholders.
* *
* This trait is not intended for passing arbitrary user input into any HTML * This trait is designed for formatting messages that are mostly text, not as
* attribute value, as only URL attributes such as "src" and "href" are * an HTML template language. As such:
* supported (using ":variable"). Never use this method on unsafe HTML * - The passed in string should contain no (or minimal) HTML.
* attributes such as "on*" and "style" and take care when using this with * - Variable placeholders should not be used within the "<" and ">" of an
* unsupported attributes such as "title" or "alt" as this can lead to * HTML tag, such as in HTML attribute values. This would be a security
* unexpected and unsafe output. * risk. Examples:
* @code
* // Insecure (placeholder within "<" and ">"):
* $this->placeholderFormat('<@variable>text</@variable>', ['@variable' => $variable]);
* // Insecure (placeholder within "<" and ">"):
* $this->placeholderFormat('<a @variable>link text</a>', ['@variable' => $variable]);
* // Insecure (placeholder within "<" and ">"):
* $this->placeholderFormat('<a title="@variable">link text</a>', ['@variable' => $variable]);
* @endcode
* Only the "href" attribute is supported via the special ":variable"
* placeholder, to allow simple links to be inserted:
* @code
* // Secure (usage of ":variable" placeholder for href attribute):
* $this->placeholderFormat('<a href=":variable">link text</a>', [':variable' , $variable]);
* // Secure (usage of ":variable" placeholder for href attribute):
* $this->placeholderFormat('<a href=":variable" title="static text">link text</a>', [':variable' => $variable]);
* // Insecure (the "@variable" placeholder does not filter dangerous
* // protocols):
* $this->placeholderFormat('<a href="@variable">link text</a>', ['@variable' => $variable]);
* // Insecure ("@variable" placeholder within "<" and ">"):
* $this->placeholderFormat('<a href=":url" title="@variable">link text</a>', [':url' => $url, '@variable' => $variable]);
* @endcode
* To build non-minimal HTML, use an HTML template language such as Twig,
* rather than this trait.
* *
* @param string $string * @param string $string
* A string containing placeholders. * A string containing placeholders. The string itself is expected to be
* safe and correct HTML. Any unsafe content must be in $args and
* inserted via placeholders.
* @param array $args * @param array $args
* An associative array of replacements to make. Occurrences in $string of * An associative array of replacements. Each array key should be the same
* any key in $args are replaced with the corresponding value, after * as a placeholder in $string. The corresponding value should be a string
* optional sanitization and formatting. The type of sanitization and * or an object that implements
* formatting depends on the first character of the key: * \Drupal\Component\Utility\SafeStringInterface. The value replaces the
* - @variable: Escaped to HTML using Html::escape() unless the value is * placeholder in $string. Sanitization and formatting will be done before
* already HTML-safe. Use this as the default choice for anything * replacement. The type of sanitization and formatting depends on the first
* displayed on a page on the site, but not within HTML attributes. * character of the key:
* - %variable: Escaped to HTML just like @variable, but also wrapped in * - @variable: When the placeholder replacement value is:
* <em> tags, which makes the following HTML code: * - A string, the replaced value in the returned string will be sanitized
* using \Drupal\Component\Utility\Html::escape().
* - A SafeStringInterface object, the replaced value in the returned
* string will not be sanitized.
* - A SafeStringInterface object cast to a string, the replaced value in
* the returned string be forcibly sanitized using
* \Drupal\Component\Utility\Html::escape().
* @code
* $this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object));
* @endcode
* Use this placeholder as the default choice for anything displayed on
* the site, but not within HTML attributes, JavaScript, or CSS. Doing so
* is a security risk.
* - %variable: Use when the replacement value is to be wrapped in <em>
* tags.
* A call like:
* @code
* $string = "%output_text";
* $arguments = ['output_text' => 'text output here.'];
* $this->placeholderFormat($string, $arguments);
* @endcode
* makes the following HTML code:
* @code * @code
* <em class="placeholder">text output here.</em> * <em class="placeholder">text output here.</em>
* @endcode * @endcode
* As with @variable, do not use this within HTML attributes. * As with @variable, do not use this within HTML attributes, JavaScript,
* - :variable: Escaped to HTML using Html::escape() and filtered for * or CSS. Doing so is a security risk.
* dangerous protocols using UrlHelper::stripDangerousProtocols(). Use * - :variable: Return value is escaped with
* this when passing in a URL, such as when using the "src" or "href" * \Drupal\Component\Utility\Html::escape() and filtered for dangerous
* attributes, ensuring the value is always wrapped in quotes: * protocols using UrlHelper::stripDangerousProtocols(). Use this when
* - Secure: <a href=":variable">@variable</a> * using the "href" attribute, ensuring the attribute value is always
* - Insecure: <a href=:variable>@variable</a> * wrapped in quotes:
* @code
* // Secure (with quotes):
* $this->placeholderFormat('<a href=":url">@variable</a>', [':url' => $url, @variable => $variable]);
* // Insecure (without quotes):
* $this->placeholderFormat('<a href=:url>@variable</a>', [':url' => $url, @variable => $variable]);
* @endcode
* When ":variable" comes from arbitrary user input, the result is secure, * When ":variable" comes from arbitrary user input, the result is secure,
* but not guaranteed to be a valid URL (which means the resulting output * but not guaranteed to be a valid URL (which means the resulting output
* could fail HTML validation). To guarantee a valid URL, use * could fail HTML validation). To guarantee a valid URL, use
...@@ -52,7 +104,7 @@ trait PlaceholderTrait { ...@@ -52,7 +104,7 @@ trait PlaceholderTrait {
* ":variable" placeholder. * ":variable" placeholder.
* *
* @return string * @return string
* The string with the placeholders replaced. * A formatted HTML string with the placeholders replaced.
* *
* @ingroup sanitization * @ingroup sanitization
* *
...@@ -68,21 +120,40 @@ protected static function placeholderFormat($string, array $args) { ...@@ -68,21 +120,40 @@ protected static function placeholderFormat($string, array $args) {
foreach ($args as $key => $value) { foreach ($args as $key => $value) {
switch ($key[0]) { switch ($key[0]) {
case '@': case '@':
// Escaped only. // Escape if the value is not an object from a class that implements
// \Drupal\Component\Utility\SafeStringInterface, for example strings
// will be escaped.
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() may
// return TRUE for content that is safe within HTML fragments, but not
// within other contexts, so this placeholder type must not be used
// within HTML attributes, JavaScript, or CSS. See
// \Drupal\Component\Utility\SafeMarkup::format().
$args[$key] = static::placeholderEscape($value); $args[$key] = static::placeholderEscape($value);
break; break;
case ':': case ':':
// URL attributes must be escaped unconditionally (even if they were // Strip URL protocols that can be XSS vectors.
// already marked safe) since content that has been filtered for XSS $value = UrlHelper::stripDangerousProtocols($value);
// can still contain characters that are unsafe for use in attributes. // Escape unconditionally, without checking
// @todo decide what to do about non-URL attribute values (#2570431) // \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe(). This
$args[$key] = Html::escape(UrlHelper::stripDangerousProtocols($value)); // forces characters that are unsafe for use in an "href" HTML
// attribute to be encoded. If a caller wants to pass a value that is
// extracted from HTML and therefore is already HTML encoded, it must
// invoke
// \Drupal\Component\Utility\OutputStrategyInterface::renderFromHtml()
// on it prior to passing it in as a placeholder value of this type.
// @todo Add some advice and stronger warnings.
// https://www.drupal.org/node/2569041.
$args[$key] = Html::escape($value);
break; break;
case '%': case '%':
default: default:
// Escaped and placeholder. // Similarly to @, escape non-safe values. Also, add wrapping markup
// in order to render as a placeholder. Not for use within attributes,
// per the warning above about
// \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe() and also
// due to the wrapping markup.
$args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>'; $args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>';
break; break;
} }
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
* This is useful for using translation in very low level subsystems like entity * This is useful for using translation in very low level subsystems like entity
* definition and stream wrappers. * definition and stream wrappers.
* *
* @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat()
* @see \Drupal\Core\StringTranslation\TranslationManager::translate() * @see \Drupal\Core\StringTranslation\TranslationManager::translate()
* @see \Drupal\Core\StringTranslation\TranslationManager::translateString() * @see \Drupal\Core\StringTranslation\TranslationManager::translateString()
* @see \Drupal\Core\Annotation\Translation * @see \Drupal\Core\Annotation\Translation
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment