VYPR
Unrated severityNVD Advisory· Published Jan 3, 2011· Updated Apr 29, 2026

CVE-2010-4536

CVE-2010-4536

Description

Multiple cross-site scripting (XSS) vulnerabilities in KSES, as used in WordPress before 3.0.4, allow remote attackers to inject arbitrary web script or HTML via vectors related to (1) the & (ampersand) character, (2) the case of an attribute name, (3) a padded entity, and (4) an entity that is not in normalized form.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

KSES XSS in WordPress before 3.0.4 allows remote attackers to inject arbitrary web script or HTML via malformed HTML entities and attribute vectors.

Vulnerability

KSES, the HTML filtering library used in WordPress, contains multiple cross-site scripting (XSS) vulnerabilities. The flaws arise from improper handling of (1) the & (ampersand) character, (2) the case of attribute names, (3) padded entities, and (4) entities not in normalized form [2]. WordPress versions before 3.0.4 are affected.

Exploitation

An attacker can inject arbitrary web script or HTML by crafting input that bypasses KSES sanitization. No special privileges are required; any vector where user input is processed by KSES (e.g., comments, posts) is exploitable. The attacker sends a request containing one of the malformed entity or attribute vectors to trigger the XSS.

Impact

Successful exploitation allows remote attackers to execute arbitrary JavaScript in the context of a victim's browser. This can lead to session hijacking, content manipulation, or redirection to malicious sites, potentially compromising the affected WordPress site and its users.

Mitigation

The vulnerability is fixed in WordPress version 3.0.4, released on December 20, 2010 [2]. Users should upgrade to this version or later. No workarounds have been published.

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

3

Patches

1
5ca8bf6641eb

Don't be case sensitive to attribute names. Handle padded entities when checking for bad protocols. Normalize entities before checking for bad protocols in esc_url(). Props Mauro Gentile, duck_, miqrogroove

https://github.com/wordpress/wordpressryanDec 29, 2010Fixed in 3.0.4via llm-release-walk
2 files changed · +14 28
  • wp-includes/formatting.php+2 1 modified
    @@ -2236,7 +2236,8 @@ function esc_url( $url, $protocols = null, $_context = 'display' ) {
     
     	// Replace ampersands and single quotes only when displaying.
     	if ( 'display' == $_context ) {
    -		$url = preg_replace('/&([^#])(?![a-z]{2,8};)/', '&$1', $url);
    +		$url = wp_kses_normalize_entities( $url );
    +		$url = str_replace( '&', '&', $url );
     		$url = str_replace( "'", ''', $url );
     	}
     
    
  • wp-includes/kses.php+12 27 modified
    @@ -670,7 +670,7 @@ function wp_kses_attr($element, $attr, $allowed_html, $allowed_protocols) {
     					break;
     				}
     
    -			if ( $arreach['name'] == 'style' ) {
    +			if ( strtolower($arreach['name']) == 'style' ) {
     				$orig_value = $arreach['value'];
     
     				$value = safecss_filter_attr($orig_value);
    @@ -762,7 +762,7 @@ function wp_kses_hair($attr, $allowed_protocols) {
     					# "value"
     					{
     					$thisval = $match[1];
    -					if ( in_array($attrname, $uris) )
    +					if ( in_array(strtolower($attrname), $uris) )
     						$thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
     
     					if(FALSE === array_key_exists($attrname, $attrarr)) {
    @@ -778,7 +778,7 @@ function wp_kses_hair($attr, $allowed_protocols) {
     					# 'value'
     					{
     					$thisval = $match[1];
    -					if ( in_array($attrname, $uris) )
    +					if ( in_array(strtolower($attrname), $uris) )
     						$thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
     
     					if(FALSE === array_key_exists($attrname, $attrarr)) {
    @@ -794,7 +794,7 @@ function wp_kses_hair($attr, $allowed_protocols) {
     					# value
     					{
     					$thisval = $match[1];
    -					if ( in_array($attrname, $uris) )
    +					if ( in_array(strtolower($attrname), $uris) )
     						$thisval = wp_kses_bad_protocol($thisval, $allowed_protocols);
     
     					if(FALSE === array_key_exists($attrname, $attrarr)) {
    @@ -1017,14 +1017,9 @@ function wp_kses_html_error($string) {
      * @return string Sanitized content
      */
     function wp_kses_bad_protocol_once($string, $allowed_protocols) {
    -	global $_kses_allowed_protocols;
    -	$_kses_allowed_protocols = $allowed_protocols;
    -
    -	$string2 = preg_split('/:|:|:/i', $string, 2);
    -	if ( isset($string2[1]) && !preg_match('%/\?%', $string2[0]) )
    -		$string = wp_kses_bad_protocol_once2($string2[0]) . trim($string2[1]);
    -	else
    -		$string = preg_replace_callback('/^((&[^;]*;|[\sA-Za-z0-9])*)'.'(:|:|&#[Xx]3[Aa];)\s*/', 'wp_kses_bad_protocol_once2', $string);
    +	$string2 = preg_split( '/:|&#0*58;|&#x0*3a;/i', $string, 2 );
    +	if ( isset($string2[1]) && ! preg_match('%/\?%', $string2[0]) )
    +		$string = wp_kses_bad_protocol_once2( $string2[0], $allowed_protocols ) . trim( $string2[1] );
     
     	return $string;
     }
    @@ -1038,29 +1033,19 @@ function wp_kses_bad_protocol_once($string, $allowed_protocols) {
      * @access private
      * @since 1.0.0
      *
    - * @param mixed $matches string or preg_replace_callback() matches array to check for bad protocols
    + * @param string $string URI scheme to check against the whitelist
    + * @param string $allowed_protocols Allowed protocols
      * @return string Sanitized content
      */
    -function wp_kses_bad_protocol_once2($matches) {
    -	global $_kses_allowed_protocols;
    -
    -	if ( is_array($matches) ) {
    -		if ( empty($matches[1]) )
    -			return '';
    -
    -		$string = $matches[1];
    -	} else {
    -		$string = $matches;
    -	}
    -
    +function wp_kses_bad_protocol_once2( $string, $allowed_protocols ) {
     	$string2 = wp_kses_decode_entities($string);
     	$string2 = preg_replace('/\s/', '', $string2);
     	$string2 = wp_kses_no_null($string2);
     	$string2 = strtolower($string2);
     
     	$allowed = false;
    -	foreach ( (array) $_kses_allowed_protocols as $one_protocol)
    -		if (strtolower($one_protocol) == $string2) {
    +	foreach ( (array) $allowed_protocols as $one_protocol )
    +		if ( strtolower($one_protocol) == $string2 ) {
     			$allowed = true;
     			break;
     		}
    

Vulnerability mechanics

Root cause

"Missing input normalization (case-insensitive attribute-name checks, entity normalization, and padded-entity handling) in KSES allows protocol-injection and XSS."

Attack vector

An attacker can inject arbitrary script into a WordPress page by supplying a URL or attribute value that uses mixed-case attribute names (e.g. `SRC` instead of `src`) to evade the URI-protocol check [CWE-79]. Additionally, the attacker can pad entity-encoded colons (e.g. `:` or `:`) or use non-normalized entities (e.g. `:`) to hide the colon in a protocol string like `javascript:`, bypassing the protocol whitelist in `esc_url()` and `wp_kses_bad_protocol_once()` [patch_id=1995850]. The crafted input is typically delivered via a comment, post, or other user-submitted content that WordPress filters with KSES.

Affected code

The vulnerability spans two files. In `wp-includes/kses.php`, the `wp_kses_hair()` function did not lowercase attribute names before checking them against a URI list, and `wp_kses_bad_protocol_once()` used an incomplete regex for entity-encoded colons (`:` and `:`), missing padded variants like `&#0*58;`. In `wp-includes/formatting.php`, `esc_url()` did not normalize HTML entities before replacing ampersands, allowing encoded protocol strings to bypass the protocol whitelist.

What the fix does

The patch addresses four XSS vectors. First, `strtolower()` is added when comparing attribute names in `wp_kses_hair()` and `wp_kses_attr()`, so mixed-case names like `SRC` are correctly recognized as URI attributes [patch_id=1995850]. Second, the regex in `wp_kses_bad_protocol_once()` is updated from `:|:` to `&#0*58;|&#x0*3a;`, which matches padded zero variants such as `:`. Third, `wp_kses_bad_protocol_once2()` now receives `$allowed_protocols` as a parameter instead of relying on a global variable, improving correctness. Fourth, `esc_url()` in `formatting.php` now calls `wp_kses_normalize_entities()` before replacing ampersands, so double-encoded or non-normalized entities like `:` are decoded to a colon and then caught by the protocol check.

Preconditions

  • inputThe attacker must be able to submit content (e.g., a comment or post) that is processed by WordPress's KSES filtering functions.
  • configThe target WordPress installation must be running a version prior to 3.0.4.

Generated on May 23, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

10

News mentions

0

No linked articles in our index yet.