diff --git src/wp-includes/kses.php src/wp-includes/kses.php
index 0cf4ce14c4..64acf7ff7d 100644
--- src/wp-includes/kses.php
+++ src/wp-includes/kses.php
@@ -854,6 +854,7 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
  * Determine whether an attribute is allowed.
  *
  * @since 4.2.3
+ * @since 5.0.0 Add support for `data-*` wildcard attributes.
  *
  * @param string $name The attribute name. Returns empty string when not allowed.
  * @param string $value The attribute value. Returns a filtered value.
@@ -864,12 +865,31 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
  * @return bool Is the attribute allowed?
  */
 function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowed_html ) {
-	$allowed_attr = $allowed_html[strtolower( $element )];
+	$allowed_attr = $allowed_html[ strtolower( $element ) ];
 
 	$name_low = strtolower( $name );
+
 	if ( ! isset( $allowed_attr[$name_low] ) || '' == $allowed_attr[$name_low] ) {
-		$name = $value = $whole = '';
-		return false;
+		/*
+		 * Allow `data-*` attributes.
+		 *
+		 * When specifying `$allowed_html`, the attribute name should be set as
+		 * `data-*` (not to be mixed with the HTML 4.0 `data` attribute, see
+		 * https://www.w3.org/TR/html40/struct/objects.html#adef-data).
+		 *
+		 * Note: the attribute name should only contain `A-Za-z0-9_-` chars,
+		 * double hyphens `--` are not accepted by WordPress.
+		 */
+		if ( strpos( $name_low, 'data-' ) === 0 && ! empty( $allowed_attr['data-*'] ) && preg_match( '/^data(?:-[a-z0-9_]+)+$/', $name_low, $match ) ) {
+			/*
+			 * Add the whole attribute name to the allowed attributes and set any restrictions
+			 * for the `data-*` attribute values for the current element.
+			 */
+			$allowed_attr[ $match[0] ] = $allowed_attr['data-*'];
+		} else {
+			$name = $value = $whole = '';
+			return false;
+		}
 	}
 
 	if ( 'style' == $name_low ) {
@@ -884,7 +904,7 @@ function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowe
 		$value = $new_value;
 	}
 
-	if ( is_array( $allowed_attr[$name_low] ) ) {
+	if ( is_array( $allowed_attr[ $name_low ] ) ) {
 		// there are some checks
 		foreach ( $allowed_attr[$name_low] as $currkey => $currval ) {
 			if ( ! wp_kses_check_attr_val( $value, $vless, $currkey, $currval ) ) {
@@ -1808,6 +1828,7 @@ function safecss_filter_attr( $css, $deprecated = '' ) {
  * Helper function to add global attributes to a tag in the allowed html list.
  *
  * @since 3.5.0
+ * @since 5.0.0 Add support for `data-*` wildcard attributes.
  * @access private
  *
  * @param array $value An array of attributes.
@@ -1820,6 +1841,7 @@ function _wp_add_global_attributes( $value ) {
 		'style' => true,
 		'title' => true,
 		'role' => true,
+		'data-*' => true,
 	);
 
 	if ( true === $value )
diff --git tests/phpunit/tests/kses.php tests/phpunit/tests/kses.php
index dea4a881a2..2d9c28411f 100644
--- tests/phpunit/tests/kses.php
+++ tests/phpunit/tests/kses.php
@@ -718,4 +718,99 @@ EOF;
 
 		$this->assertEquals( "<{$element}>", wp_kses_attr( $element, $attribute, array( 'foo' => false ), array() ) );
 	}
+
+	/**
+	 * Data attributes are globally accepted.
+	 *
+	 * @ticket 33121
+	 */
+	function test_wp_kses_attr_data_attribute_is_allowed() {
+		$test = '<div data-foo="foo" data-bar="bar" datainvalid="gone" data--invaild="gone"  data-also-invaild-="gone" data-two-hyphens="remains">Pens and pencils</div>';
+		$expected = '<div data-foo="foo" data-bar="bar" data-two-hyphens="remains">Pens and pencils</div>';
+
+		$this->assertEquals( $expected, wp_kses_post( $test ) );
+	}
+
+	/**
+	 * Ensure wildcard attributes block unprefixed wildcard uses.
+	 *
+	 * @ticket 33121
+	 */
+	function test_wildcard_requires_hyphen_after_prefix() {
+		$allowed_html = array(
+			'div' => array(
+				'data-*' => true,
+				'on-*' => true,
+			),
+		);
+
+		$string = '<div datamelformed-prefix="gone" data="gone" data-="gone" onclick="alert(1)">Malformed attributes</div>';
+		$expected = '<div>Malformed attributes</div>';
+
+		$actual = wp_kses( $string, $allowed_html );
+
+		$this->assertSame( $expected, $actual );
+	}
+
+	/**
+	 * Ensure wildcard allows two hyphen.
+	 *
+	 * @ticket 33121
+	 */
+	function test_wildcard_allows_two_hyphens() {
+		$allowed_html = array(
+			'div' => array(
+				'data-*' => true,
+			),
+		);
+
+		$string = '<div data-wp-id="pens-and-pencils">Well formed attribute</div>';
+		$expected = '<div data-wp-id="pens-and-pencils">Well formed attribute</div>';
+
+		$actual = wp_kses( $string, $allowed_html );
+
+		$this->assertSame( $expected, $actual );
+	}
+
+	/**
+	 * Ensure wildcard attributes only support valid prefixes.
+	 *
+	 * @dataProvider data_wildcard_attribute_prefixes
+	 *
+	 * @ticket 33121
+	 */
+	function test_wildcard_attribute_prefixes( $wildcard_attribute, $expected ) {
+		$allowed_html = array(
+			'div' => array(
+				$wildcard_attribute => true,
+			),
+		);
+
+		$name = str_replace( '*', strtolower( __FUNCTION__ ), $wildcard_attribute );
+		$value = __FUNCTION__;
+		$whole = "{$name}=\"{$value}\"";
+
+		$actual = wp_kses_attr_check( $name, $value, $whole, 'n', 'div', $allowed_html );
+
+		$this->assertSame( $expected, $actual );
+	}
+
+	/**
+	 * @return array Array of arguments for wildcard testing
+	 *               [0] The prefix being tested.
+	 *               [1] The outcome of `wp_kses_attr_check` for the prefix.
+	 */
+	function data_wildcard_attribute_prefixes() {
+		return array(
+			// Ends correctly
+			array( 'data-*', true ),
+
+			// Does not end with trialing `-`.
+			array( 'data*', false ),
+
+			// Multiple wildcards.
+			array( 'd*ta-*', false ),
+			array( 'data**', false ),
+		);
+	}
 }
diff --git tests/phpunit/tests/rest-api/rest-attachments-controller.php tests/phpunit/tests/rest-api/rest-attachments-controller.php
index a5ca6b8c74..c1ddc4ed68 100644
--- tests/phpunit/tests/rest-api/rest-attachments-controller.php
+++ tests/phpunit/tests/rest-api/rest-attachments-controller.php
@@ -928,9 +928,9 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control
 			array(
 				// Raw values.
 				array(
-					'title'   => '<a href="#" target="_blank" data-unfiltered=true>link</a>',
-					'description' => '<a href="#" target="_blank" data-unfiltered=true>link</a>',
-					'caption' => '<a href="#" target="_blank" data-unfiltered=true>link</a>',
+					'title'   => '<a href="#" target="_blank" unfiltered=true>link</a>',
+					'description' => '<a href="#" target="_blank" unfiltered=true>link</a>',
+					'caption' => '<a href="#" target="_blank" unfiltered=true>link</a>',
 				),
 				// Expected returned values.
 				array(
diff --git tests/phpunit/tests/rest-api/rest-posts-controller.php tests/phpunit/tests/rest-api/rest-posts-controller.php
index a51fa59d13..e2c3e5edef 100644
--- tests/phpunit/tests/rest-api/rest-posts-controller.php
+++ tests/phpunit/tests/rest-api/rest-posts-controller.php
@@ -2913,9 +2913,9 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te
 			array(
 				// Raw values.
 				array(
-					'title'   => '<a href="#" target="_blank" data-unfiltered=true>link</a>',
-					'content' => '<a href="#" target="_blank" data-unfiltered=true>link</a>',
-					'excerpt' => '<a href="#" target="_blank" data-unfiltered=true>link</a>',
+					'title'   => '<a href="#" target="_blank" unfiltered=true>link</a>',
+					'content' => '<a href="#" target="_blank" unfiltered=true>link</a>',
+					'excerpt' => '<a href="#" target="_blank" unfiltered=true>link</a>',
 				),
 				// Expected returned values.
 				array(
