Skip to content
Snippets Groups Projects
Commit 4d8e0102 authored by Alex Pott's avatar Alex Pott
Browse files

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 9d3c2c52
No related branches found
No related tags found
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
...@@ -304,25 +304,23 @@ function drupal_get_path($type, $name) { ...@@ -304,25 +304,23 @@ function drupal_get_path($type, $name) {
* break up strings for translation. * break up strings for translation.
* *
* @section sec_translating_vars Translating Variables * @section sec_translating_vars Translating Variables
* You should never use t() to translate variables, such as calling t($text) * You should never use t() to translate variables, such as calling t($text).
* unless the text that the variable holds has been passed through t() * Doing that can lead to cross-site scripting vulnerabilities and other
* elsewhere (e.g., $text is one of several translated literal strings in an * security problems.
* array). It is especially important never to call t($user_text) where *
* $user_text is some text that a user entered - doing that can lead to * However, you can use placeholder replacement in your string, to put variable
* cross-site scripting and other security problems. However, you can use * text such as user names or link URLs into translated text. Variable
* variable substitution in your string, to put variable text such as user * substitution looks like this:
* names or link URLs into translated text. Variable substitution looks like
* this:
* @code * @code
* $text = t("@name's blog", array('@name' => $account->getDisplayName())); * $text = t("@name's blog", array('@name' => $account->getDisplayName()));
* @endcode * @endcode
* Basically, you can put variables like @name into your string, and t() will * Basically, you can put variables like @name into your string, and t() will
* substitute their sanitized values at translation time. (See the * substitute their sanitized values at translation time. (See the
* Localization API pages referenced above and the documentation of * Localization API pages referenced above and the documentation of
* \Drupal\Component\Utility\SafeMarkup::format() for details about how to * \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat()
* define variables in your string.). Translators can then rearrange the string * for details about how to safely and correctly define variables in your
* as necessary for the language (e.g., in Spanish, it might be "blog de * string.). Translators can then rearrange the string as necessary for the
* @name"). * language (e.g., in Spanish, it might be "blog de @name").
* *
* @param $string * @param $string
* A string containing the English string to translate. * A string containing the English string to translate.
...@@ -337,10 +335,10 @@ function drupal_get_path($type, $name) { ...@@ -337,10 +335,10 @@ function drupal_get_path($type, $name) {
* - 'context' (defaults to the empty context): The context the source string * - 'context' (defaults to the empty context): The context the source string
* belongs to. * belongs to.
* *
* @return * @return \Drupal\Core\StringTranslation\TranslatableString
* The translated string. * An object that, when cast to a string, will yield the translated string.
* *
* @see \Drupal\Component\Utility\SafeMarkup::format() * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat()
* @ingroup sanitization * @ingroup sanitization
*/ */
function t($string, array $args = array(), array $options = array()) { function t($string, array $args = array(), array $options = array()) {
......
...@@ -10,28 +10,36 @@ ...@@ -10,28 +10,36 @@
/** /**
* 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, $string passed to the constructor should
* contain minimal HTML.
*
* The result of casting this object to a string 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 HTML that cannot meet these restrictions, 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,44 +15,100 @@ trait PlaceholderTrait { ...@@ -15,44 +15,100 @@ 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 method is designed for formatting messages that are mostly text, not
* attribute value, as only URL attributes such as "src" and "href" are * as an HTML template language. As such, $string should contain minimal HTML.
* supported (using ":variable"). Never use this method on unsafe HTML *
* attributes such as "on*" and "style" and take care when using this with * In $string the placeholders should not be within the "<" and ">" of an HTML
* unsupported attributes such as "title" or "alt" as this can lead to * tag, except a placeholder starting with : as described below. This would be
* unexpected and unsafe output. * a security risk.
*
* The return value should not be used within the "<" and ">" of an HTML tag,
* such as in HTML attribute values. Also, do not concatenate the return
* value into JavaScript or CSS. This would be a security risk.
*
* To build HTML that cannot meet these restrictions, 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: Use as the default choice for anything displayed on the
* <em> tags, which makes the following HTML code: * site. Do not use within the "<" and ">" of an HTML tag, such as in HTML
* attribute values. Doing so is a security risk.
* When the placeholder replacement value is:
* - A string, it will be sanitized using
* \Drupal\Component\Utility\Html::escape().
* - A SafeStringInterface object, the object will be cast to a string and
* not sanitized. To force sanitization of a SafeStringInterface object,
* cast the replacement value to a string, since it is not known what
* strategy a SafeStringInterface object uses for santitization.
* A call like:
* @code
* placeholderFormat("Prefix @something", ['@something' => 'text output.']);
* @endcode
* returns the following HTML string:
* @code * @code
* <em class="placeholder">text output here.</em> * Prefix text output.
* @endcode * @endcode
* As with @variable, do not use this within HTML attributes. * - %variable: Use when @variable would be appropriate, but the placeholder
* - :variable: Escaped to HTML using Html::escape() and filtered for * value will also be wrapped in <em> tags with a class. As with
* dangerous protocols using UrlHelper::stripDangerousProtocols(). Use * @variable, do not use within the "<" and ">" of an HTML tag, such as in
* this when passing in a URL, such as when using the "src" or "href" * HTML attribute values. Doing so is a security risk.
* attributes, ensuring the value is always wrapped in quotes: * A call like:
* - Secure: <a href=":variable">@variable</a> * @code
* - Insecure: <a href=:variable>@variable</a> * placeholderFormat("Prefix %something", ['%something' => 'text output.']);
* @endcode
* returns the following HTML string:
* @code
* Prefix <em class="placeholder">text output.</em>
* @endcode
* - :variable: Use when the return value is to be used as a URL value of an
* HTML attribute, for example the href attribute in an a tag.
* Return value is escaped with
* \Drupal\Component\Utility\Html::escape() and filtered for dangerous
* protocols using UrlHelper::stripDangerousProtocols(). Use this when
* using the "href" attribute, ensuring the value is always wrapped in
* quotes.
* 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
* Url::fromUri($user_input)->toString() (which either throws an exception * Url::fromUri($user_input)->toString() (which either throws an exception
* or returns a well-formed URL) before passing the result into a * or returns a well-formed URL) before passing the result into a
* ":variable" placeholder. * ":variable" placeholder.
* @todo Add some advice and stronger warnings.
* https://www.drupal.org/node/2569041
*
* In $string, only HTML attributes that take a URL as the value, are
* supported via the special ":variable" placeholder. For example, with the
* "href" attribute, the ":variable" placeholder allows simple links to be
* inserted:
* - Secure: @code <a href=":variable">link text</a> @endcode
* - Secure: @code <a href=":variable" title="static text">link text</a> @endcode
* - Secure: @code <a href=":variable">@variable</a> @endcode
* Insecure $string examples:
* - It is insecure in $string to have @ or % placeholders within the "<"
* and ">" of an HTML tag:
* - Insecure: @code <@variable>text</@variable> @endcode
* - Insecure: @code <a @variable>link text</a> @endcode
* - Insecure: @code <a href="@variable">link text</a> @endcode
* - Insecure: @code <a title="@variable">link text</a> @endcode
* - Insecure: @code <a href=":variable" title="@variable">link text</a>@endcode
* - The : placeholder should be used for HTML attribute values, but is
* insecure not to have quotes around the attribute value:
* - Insecure: @code <a href=:variable>@variable</a>@endcode
* - Insecure: @code <img src=:variable />@endcode
* *
* @return string * @return string
* The string with the placeholders replaced. * A formatted string with the placeholders replaced.
* *
* @ingroup sanitization * @ingroup sanitization
* *
...@@ -62,27 +118,39 @@ trait PlaceholderTrait { ...@@ -62,27 +118,39 @@ trait PlaceholderTrait {
* @see \Drupal\Component\Utility\Html::escape() * @see \Drupal\Component\Utility\Html::escape()
* @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols()
* @see \Drupal\Core\Url::fromUri() * @see \Drupal\Core\Url::fromUri()
* @see http://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value/2725168#2725168
*/ */
protected static function placeholderFormat($string, array $args) { protected static function placeholderFormat($string, array $args) {
// Transform arguments before inserting them. // Transform arguments before inserting them.
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.
$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.
$args[$key] = Html::escape($value);
break; break;
case '%': case '%':
default: default:
// Escaped and placeholder. // Acts like @, but adds wrapping markup: an <em> tag with a class.
// Not for use within attributes, per the warning above regarding @
// 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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment