From 4a8366bb623ad65fbda061678266b03569f9a3b9 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Tue, 29 Sep 2015 11:43:27 +0100 Subject: [PATCH] Revert "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" This reverts commit 4d8e0102f389a6c7c01a79f10b78d7f417a1a1f6. --- core/includes/bootstrap.inc | 30 ++-- .../Component/Utility/FormattableString.php | 36 ++--- .../Component/Utility/PlaceholderTrait.php | 132 +++++------------- .../StringTranslation/TranslatableString.php | 1 - 4 files changed, 62 insertions(+), 137 deletions(-) diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 7e7180f7277f..0b1f6337b019 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -304,23 +304,25 @@ function drupal_get_path($type, $name) { * break up strings for translation. * * @section sec_translating_vars Translating Variables - * You should never use t() to translate variables, such as calling t($text). - * Doing that can lead to cross-site scripting vulnerabilities and other - * security problems. - * - * However, you can use placeholder replacement in your string, to put variable - * text such as user names or link URLs into translated text. Variable - * substitution looks like this: + * 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() + * elsewhere (e.g., $text is one of several translated literal strings in an + * 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 + * cross-site scripting and other security problems. However, you can use + * variable substitution in your string, to put variable text such as user + * names or link URLs into translated text. Variable substitution looks like + * this: * @code * $text = t("@name's blog", array('@name' => $account->getDisplayName())); * @endcode * Basically, you can put variables like @name into your string, and t() will * substitute their sanitized values at translation time. (See the * Localization API pages referenced above and the documentation of - * \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() - * for details about how to safely and correctly define variables in your - * string.). Translators can then rearrange the string as necessary for the - * language (e.g., in Spanish, it might be "blog de @name"). + * \Drupal\Component\Utility\SafeMarkup::format() for details about how to + * define variables in your string.). Translators can then rearrange the string + * as necessary for the language (e.g., in Spanish, it might be "blog de + * @name"). * * @param $string * A string containing the English string to translate. @@ -335,10 +337,10 @@ function drupal_get_path($type, $name) { * - 'context' (defaults to the empty context): The context the source string * belongs to. * - * @return \Drupal\Core\StringTranslation\TranslatableString - * An object that, when cast to a string, will yield the translated string. + * @return + * The translated string. * - * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() + * @see \Drupal\Component\Utility\SafeMarkup::format() * @ingroup sanitization */ function t($string, array $args = array(), array $options = array()) { diff --git a/core/lib/Drupal/Component/Utility/FormattableString.php b/core/lib/Drupal/Component/Utility/FormattableString.php index 4ad604a18713..ea7861c325b6 100644 --- a/core/lib/Drupal/Component/Utility/FormattableString.php +++ b/core/lib/Drupal/Component/Utility/FormattableString.php @@ -10,36 +10,28 @@ /** * Formats a string for HTML display by replacing variable placeholders. * - * When cast to a string, this object replaces variable placeholders in the - * string with the arguments passed in during construction and escapes the - * values so they can be safely displayed as HTML. See the documentation of - * \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() for - * details on the supported placeholders and how to use them securely. Incorrect - * use of this class can result in security vulnerabilities. + * When cast to a string it replaces variable placeholders in the string with + * the arguments passed in during construction and escapes the values so they + * can be safely displayed as HTML. It should be used on any unknown text that + * is intended to be printed to an HTML page (especially text that may have come + * from untrusted users, since in that case it prevents cross-site scripting and + * other security problems). + * + * 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 * rather than this object, since they will translate the text (on - * 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. + * non-English-only sites) in addition to formatting it. * * @ingroup sanitization * * @see \Drupal\Core\StringTranslation\TranslatableString * @see \Drupal\Core\StringTranslation\PluralTranslatableString - * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() */ class FormattableString implements SafeStringInterface { diff --git a/core/lib/Drupal/Component/Utility/PlaceholderTrait.php b/core/lib/Drupal/Component/Utility/PlaceholderTrait.php index 671f5d8e6e33..e2db26b3ecb1 100644 --- a/core/lib/Drupal/Component/Utility/PlaceholderTrait.php +++ b/core/lib/Drupal/Component/Utility/PlaceholderTrait.php @@ -15,100 +15,44 @@ trait PlaceholderTrait { /** * Formats a string by replacing variable placeholders. * - * This method is designed for formatting messages that are mostly text, not - * as an HTML template language. As such, $string should contain minimal HTML. - * - * In $string the placeholders should not be within the "<" and ">" of an HTML - * tag, except a placeholder starting with : as described below. This would be - * 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. + * This trait 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 and unsafe output. * * @param string $string - * 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. + * A string containing placeholders. * @param array $args - * An associative array of replacements. Each array key should be the same - * as a placeholder in $string. The corresponding value should be a string - * or an object that implements - * \Drupal\Component\Utility\SafeStringInterface. The value replaces the - * placeholder in $string. Sanitization and formatting will be done before - * replacement. The type of sanitization and formatting depends on the first - * character of the key: - * - @variable: Use as the default choice for anything displayed on the - * 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: + * An associative array of replacements to make. Occurrences in $string of + * any key in $args are replaced with the corresponding value, after + * optional sanitization and formatting. The type of sanitization and + * formatting depends on the first character of the key: + * - @variable: Escaped to HTML using Html::escape() unless the value is + * already HTML-safe. Use this as the default choice for anything + * displayed on a page on the site, but not within HTML attributes. + * - %variable: Escaped to HTML just like @variable, but also wrapped in + * <em> tags, which makes the following HTML code: * @code - * Prefix text output. + * <em class="placeholder">text output here.</em> * @endcode - * - %variable: Use when @variable would be appropriate, but the placeholder - * value will also be wrapped in <em> tags with a class. As with - * @variable, do not use within the "<" and ">" of an HTML tag, such as in - * HTML attribute values. Doing so is a security risk. - * A call like: - * @code - * 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. + * As with @variable, do not use this within HTML attributes. + * - :variable: Escaped to HTML using Html::escape() and filtered for + * dangerous protocols using UrlHelper::stripDangerousProtocols(). Use + * this when passing in a URL, such as when using the "src" or "href" + * attributes, ensuring the value is always wrapped in quotes: + * - Secure: <a href=":variable">@variable</a> + * - Insecure: <a href=:variable>@variable</a> * When ":variable" comes from arbitrary user input, the result is secure, * but not guaranteed to be a valid URL (which means the resulting output * could fail HTML validation). To guarantee a valid URL, use * Url::fromUri($user_input)->toString() (which either throws an exception * or returns a well-formed URL) before passing the result into a * ":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 - * A formatted string with the placeholders replaced. + * The string with the placeholders replaced. * * @ingroup sanitization * @@ -118,39 +62,27 @@ trait PlaceholderTrait { * @see \Drupal\Component\Utility\Html::escape() * @see \Drupal\Component\Utility\UrlHelper::stripDangerousProtocols() * @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) { // Transform arguments before inserting them. foreach ($args as $key => $value) { switch ($key[0]) { case '@': - // 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. + // Escaped only. $args[$key] = static::placeholderEscape($value); break; case ':': - // Strip URL protocols that can be XSS vectors. - $value = UrlHelper::stripDangerousProtocols($value); - // Escape unconditionally, without checking - // \Drupal\Component\Utility\SafeMarkup\SafeMarkup::isSafe(). This - // forces characters that are unsafe for use in an "href" HTML - // attribute to be encoded. - $args[$key] = Html::escape($value); + // URL attributes must be escaped unconditionally (even if they were + // already marked safe) since content that has been filtered for XSS + // can still contain characters that are unsafe for use in attributes. + // @todo decide what to do about non-URL attribute values (#2570431) + $args[$key] = Html::escape(UrlHelper::stripDangerousProtocols($value)); break; case '%': default: - // 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. + // Escaped and placeholder. $args[$key] = '<em class="placeholder">' . static::placeholderEscape($value) . '</em>'; break; } diff --git a/core/lib/Drupal/Core/StringTranslation/TranslatableString.php b/core/lib/Drupal/Core/StringTranslation/TranslatableString.php index 6d587a1902d9..0dbb3a6a3f7e 100644 --- a/core/lib/Drupal/Core/StringTranslation/TranslatableString.php +++ b/core/lib/Drupal/Core/StringTranslation/TranslatableString.php @@ -19,7 +19,6 @@ * This is useful for using translation in very low level subsystems like entity * definition and stream wrappers. * - * @see \Drupal\Component\Utility\PlaceholderTrait::placeholderFormat() * @see \Drupal\Core\StringTranslation\TranslationManager::translate() * @see \Drupal\Core\StringTranslation\TranslationManager::translateString() * @see \Drupal\Core\Annotation\Translation -- GitLab