diff --git src/wp-includes/kses.php src/wp-includes/kses.php
index 0cf4ce14c4..27805ac14d 100644
--- src/wp-includes/kses.php
+++ src/wp-includes/kses.php
@@ -549,7 +549,7 @@ function wp_kses_one_attr( $string, $element ) {
 	$allowed_html = wp_kses_allowed_html( 'post' );
 	$allowed_protocols = wp_allowed_protocols();
 	$string = wp_kses_no_null( $string, array( 'slash_zero' => 'keep' ) );
-	
+
 	// Preserve leading and trailing whitespace.
 	$matches = array();
 	preg_match('/^\s*/', $string, $matches);
@@ -561,7 +561,7 @@ function wp_kses_one_attr( $string, $element ) {
 	} else {
 		$string = substr( $string, strlen( $lead ), -strlen( $trail ) );
 	}
-	
+
 	// Parse attribute name and value from input.
 	$split = preg_split( '/\s*=\s*/', $string, 2 );
 	$name = $split[0];
@@ -598,7 +598,7 @@ function wp_kses_one_attr( $string, $element ) {
 		$value = '';
 		$vless = 'y';
 	}
-	
+
 	// Sanitize attribute by name.
 	wp_kses_attr_check( $name, $value, $string, $vless, $element, $allowed_html );
 
@@ -854,6 +854,8 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
  * Determine whether an attribute is allowed.
  *
  * @since 4.2.3
+ * @since 5.0.0 Accepts wildcard attributes in allowed attributes in the form `prefix-*`.
+ *              Wilcards are accepted as a suffix only and the prefix must end with a hyphen.
  *
  * @param string $name The attribute name. Returns empty string when not allowed.
  * @param string $value The attribute value. Returns a filtered value.
@@ -867,9 +869,36 @@ function wp_kses_attr_check( &$name, &$value, &$whole, $vless, $element, $allowe
 	$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;
+		$allowed_wildcard = false;
+
+		// Check if attribute matches allowed wildcard.
+		foreach ( array_keys( $allowed_attr ) as $attr ) {
+			if ( '-*' !== substr( $attr, -2 ) ) {
+				continue;
+			}
+
+			// Store prefix without trailing `-*`.
+			$prefix = strtolower( substr( $attr, 0, -2 ) );
+
+			if ( 1 !== preg_match( '/^' . preg_quote( $prefix ) . '(-[a-z0-9_]+)*$/', $name_low ) ) {
+				// Disallowed attribute format.
+				continue;
+			}
+
+			// Attribute name is valid.
+			$allowed_attr[$name_low] = true;
+			$allowed_wildcard = true;
+
+			// Stop checking.
+			break;
+		}
+
+		if ( false === $allowed_wildcard ) {
+			$name = $value = $whole = '';
+			return false;
+		}
 	}
 
 	if ( 'style' == $name_low ) {
@@ -1062,7 +1091,7 @@ function wp_kses_attr_parse( $element ) {
 	} else {
 		$xhtml_slash = '';
 	}
-	
+
 	// Split it
 	$attrarr = wp_kses_hair_parse( $attr );
 	if ( false === $attrarr ) {
@@ -1072,7 +1101,7 @@ function wp_kses_attr_parse( $element ) {
 	// Make sure all input is returned by adding front and back matter.
 	array_unshift( $attrarr, $begin . $slash . $elname );
 	array_push( $attrarr, $xhtml_slash . $end );
-	
+
 	return $attrarr;
 }
 
@@ -1808,6 +1837,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 `data-*` wildcard added to globally accepted attributes.
  * @access private
  *
  * @param array $value An array of attributes.
@@ -1820,6 +1850,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..0c6b91a6e0 100644
--- tests/phpunit/tests/kses.php
+++ tests/phpunit/tests/kses.php
@@ -718,4 +718,58 @@ 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 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( '33121-*', true ),
+
+			// Does not end with trialing `-`.
+			array( '33121*', false ),
+
+			// Multiple wildcards.
+			array( '3*121-*', false ),
+			array( '33121**', false ),
+		);
+	}
 }
