diff --git a/core/includes/common.inc b/core/includes/common.inc
index 8685360..0e2241b 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -4349,6 +4349,15 @@ function _drupal_bootstrap_code() {
     $allowed_protocols = array('http', 'https');
   }
   UrlValidator::setAllowedProtocols($allowed_protocols);
+  // Set the allowed attributes once we have the config available.
+  $allowed_attributes = \Drupal::config('system.filter')->get('attributes');
+  if (!isset($allowed_attributes)) {
+    // filter_xss_admin() is called by the installer and update.php, in which
+    // case the configuration may not exist (yet). Provide a minimal default set
+    // of allowed attributes for these cases.
+    $allowed_attributes = array('alt', 'lang', 'rel', 'title', 'value');
+  }
+  Xss::setAllowedAttributes($allowed_attributes);
 }
 
 /**
diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php
index d55b91c..31457be 100644
--- a/core/lib/Drupal/Component/Utility/Xss.php
+++ b/core/lib/Drupal/Component/Utility/Xss.php
@@ -22,6 +22,23 @@ class Xss {
   protected static $adminTags = array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr');
 
   /**
+   * The list of Safe HTML attributes
+   * 
+   * @var array
+   */
+  protected static $allowedAttributes = array('alt', 'lang', 'rel', 'title', 'value');
+
+  /**
+   * Sets the allowed and/or Safe HTML attributes.
+   *
+   * @param array $attributes
+   *   An array of attributes, for example alt, title and value.
+   */
+  public static function setAllowedAttributes(array $attributes = array()) {
+    static::$allowedAttributes = $attributes;
+  }
+
+  /**
    * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
    *
    * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
@@ -185,6 +202,7 @@ protected static function split($matches, $store = FALSE) {
    *   Cleaned up version of the HTML attributes.
    */
   protected static function attributes($attributes) {
+    $allowed_attributes = array_flip(static::$allowedAttributes);
     $attributes_array = array();
     $mode = 0;
     $attribute_name = '';
@@ -224,8 +242,14 @@ protected static function attributes($attributes) {
         case 2:
           // Attribute value, a URL after href= for instance.
           if (preg_match('/^"([^"]*)"(\s+|$)/', $attributes, $match)) {
-            $thisval = UrlValidator::filterBadProtocol($match[1]);
-
+            // If the attribute is listed under the allowed and/or Safe HTML
+            // allowed, then do not check for bad protocols.
+            if (!isset($allowed_attributes[strtolower($attribute_name)])) {
+              $thisval = UrlValidator::filterBadProtocol($match[1]);
+            } 
+            else {
+              $thisval = $match[1];
+            }
             if (!$skip) {
               $attributes_array[] = "$attribute_name=\"$thisval\"";
             }
@@ -236,8 +260,12 @@ protected static function attributes($attributes) {
           }
 
           if (preg_match("/^'([^']*)'(\s+|$)/", $attributes, $match)) {
-            $thisval = UrlValidator::filterBadProtocol($match[1]);
-
+            if (!isset($allowed_attributes[strtolower($attribute_name)])) {
+              $thisval = UrlValidator::filterBadProtocol($match[1]);
+            }
+            else {
+              $thisval = $match[1];
+            }
             if (!$skip) {
               $attributes_array[] = "$attribute_name='$thisval'";
             }
@@ -247,7 +275,12 @@ protected static function attributes($attributes) {
           }
 
           if (preg_match("%^([^\s\"']+)(\s+|$)%", $attributes, $match)) {
-            $thisval = UrlValidator::filterBadProtocol($match[1]);
+            if (!isset($allowed_attributes[strtolower($attribute_name)])) {
+              $thisval = UrlValidator::filterBadProtocol($match[1]);
+            }
+            else {
+              $thisval = $match[1];
+            }
 
             if (!$skip) {
               $attributes_array[] = "$attribute_name=\"$thisval\"";
diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index d41d414..c6822b8 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -127,6 +127,12 @@ system.filter:
       sequence:
         - type: string
           label: 'Protocol'
+    attributes:
+      type: sequence
+      label: 'Allowed attributes'
+      sequence:
+        - type: string
+          label: 'Attribute'
 
 system.logging:
   type: mapping
diff --git a/core/modules/system/config/system.filter.yml b/core/modules/system/config/system.filter.yml
index 3bfbd9a..f5d5df3 100644
--- a/core/modules/system/config/system.filter.yml
+++ b/core/modules/system/config/system.filter.yml
@@ -11,3 +11,47 @@ protocols:
   - sftp
   - webcal
   - rtsp
+
+attributes:
+  - alt
+  - lang
+  - title
+  - rel
+  - value
+  - align
+  - alink
+  - bgcolor
+  - border
+  - cellpadding
+  - cellspacing
+  - class
+  - color
+  - cols
+  - colspan
+  - coords
+  - dir
+  - face
+  - height
+  - hspace
+  - ismap
+  - marginheight
+  - marginwidth
+  - multiple
+  - nohref
+  - noresize
+  - noshade
+  - nowrap
+  - ref
+  - rev
+  - rows
+  - rowspan
+  - scrolling
+  - shape
+  - span
+  - summary
+  - tabindex
+  - usemap
+  - valign
+  - vlink
+  - vspace
+  - width
