VYPR
Unrated severityNVD Advisory· Published Jan 3, 2020· Updated Aug 6, 2024

CVE-2012-4451

CVE-2012-4451

Description

Multiple XSS vulnerabilities in Zend Framework 2.0.x before 2.0.1 allow remote attackers to inject arbitrary web script or HTML via unsanitized input in several components.

AI Insight

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

Multiple XSS vulnerabilities in Zend Framework 2.0.x before 2.0.1 allow remote attackers to inject arbitrary web script or HTML via unsanitized input in several components.

Vulnerability

Multiple cross-site scripting (XSS) vulnerabilities exist in Zend Framework 2.0.x versions prior to 2.0.1. The affected components—Zend\Debug, Zend\Feed\PubSubHubbub, Zend\Log\Formatter\Xml, Zend\Tag\Cloud\Decorator, Zend\Uri, Zend\View\Helper\HeadStyle, Zend\View\Helper\Navigation\Sitemap, and Zend\View\Helper\Placeholder\Container\AbstractStandalone—failed to use context-appropriate escaping via Zend\Escaper\Escaper when rendering HTML, HTML attributes, or URLs [3][4]. While some escaping was performed, it was not context-aware, leaving the components open to XSS injection.

Exploitation

An attacker can supply crafted input to any of the listed components. Because the input is not properly sanitized before being returned to the user, the attacker can inject arbitrary HTML and script code that executes in the victim's browser session [3][4]. No special privileges or user interaction beyond normal browsing is required; the attack vector is remote and can be triggered by simply visiting a page that renders the attacker-controlled data through one of the vulnerable components.

Impact

Successful exploitation allows an attacker to execute arbitrary web script or HTML in the context of the affected site. This can lead to information disclosure, session hijacking, defacement, or other malicious actions typically associated with stored or reflected XSS [2][3]. The impact is limited to the browser session of the victim, but the attacker can potentially access sensitive data or perform actions on behalf of the user.

Mitigation

The vulnerability is fixed in Zend Framework version 2.0.1 and later, including the 2.1 development branch [3]. Users should upgrade to 2.0.1 or greater immediately. No workarounds are documented; the patch involves composing the Zend\Escaper\Escaper class as an injectable dependency and using its context-appropriate escaping methods [1]. The issue is tracked as ZF2012-03 [3] and has been assigned CVE-2012-4451.

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

Affected products

2

Patches

1
27131ca9520b

Merge branch 'security/escaper-usage'

https://github.com/zendframework/zf2Matthew Weier O'PhinneySep 20, 2012via nvd-ref
22 files changed · +579 275
  • library/Zend/Debug/composer.json+6 2 modified
    @@ -13,6 +13,10 @@
         },
         "target-dir": "Zend/Debug",
         "require": {
    -        "php": ">=5.3.3"
    +        "php": ">=5.3.3",
    +        "zendframework/zend-escaper": "self.version"
    +    },
    +    "suggest": {
    +        "ext/xdebug": "XDebug, for better backtrace output"
         }
    -}
    \ No newline at end of file
    +}
    
  • library/Zend/Debug/Debug.php+32 1 modified
    @@ -10,6 +10,8 @@
     
     namespace Zend\Debug;
     
    +use Zend\Escaper\Escaper;
    +
     /**
      * Concrete class for generating debug dumps related to the output source.
      *
    @@ -18,6 +20,10 @@
      */
     class Debug
     {
    +    /**
    +     * @var Escaper
    +     */
    +    protected static $escaper = null;
     
         /**
          * @var string
    @@ -50,6 +56,31 @@ public static function setSapi($sapi)
             self::$sapi = $sapi;
         }
     
    +    /**
    +     * Set Escaper instance
    +     *
    +     * @param  Escaper $escaper
    +     */
    +    public static function setEscaper(Escaper $escaper)
    +    {
    +        static::$escaper = $escaper;
    +    }
    +    
    +    /**
    +     * Get Escaper instance
    +     *
    +     * Lazy loads an instance if none provided.
    +     *
    +     * @return Escaper
    +     */
    +    public static function getEscaper()
    +    {
    +        if (null === static::$escaper) {
    +            static::setEscaper(new Escaper());
    +        }
    +        return static::$escaper;
    +    }
    +
         /**
          * Debug helper function.  This is a wrapper for var_dump() that adds
          * the <pre /> tags, cleans up newlines and indents, and runs
    @@ -78,7 +109,7 @@ public static function dump($var, $label=null, $echo=true)
                         . PHP_EOL;
             } else {
                 if (!extension_loaded('xdebug')) {
    -                $output = htmlspecialchars($output, ENT_QUOTES);
    +                $output = static::getEscaper()->escapeHtml($output);
                 }
     
                 $output = '<pre>'
    
  • library/Zend/Feed/composer.json+1 0 modified
    @@ -14,6 +14,7 @@
         "target-dir": "Zend/Feed",
         "require": {
             "php": ">=5.3.3",
    +        "zendframework/zend-escaper": "self.version",
             "zendframework/zend-stdlib": "self.version"
         },
         "suggest": {
    
  • library/Zend/Feed/PubSubHubbub/PubSubHubbub.php+41 7 modified
    @@ -10,6 +10,7 @@
     
     namespace Zend\Feed\PubSubHubbub;
     
    +use Zend\Escaper\Escaper;
     use Zend\Feed\Reader;
     use Zend\Http;
     
    @@ -32,10 +33,15 @@ class PubSubHubbub
         const SUBSCRIPTION_NOTVERIFIED = 'not_verified';
         const SUBSCRIPTION_TODELETE    = 'to_delete';
     
    +    /**
    +     * @var Escaper
    +     */
    +    protected static $escaper;
    +
         /**
          * Singleton instance if required of the HTTP client
          *
    -     * @var \Zend\Http\Client
    +     * @var Http\Client
          */
         protected static $httpClient = null;
     
    @@ -67,7 +73,7 @@ public static function detectHubs($source)
          * Allows the external environment to make Zend_Oauth use a specific
          * Client instance.
          *
    -     * @param  \Zend\Http\Client $httpClient
    +     * @param  Http\Client $httpClient
          * @return void
          */
         public static function setHttpClient(Http\Client $httpClient)
    @@ -80,15 +86,15 @@ public static function setHttpClient(Http\Client $httpClient)
          * the instance is reset and cleared of previous parameters GET/POST.
          * Headers are NOT reset but handled by this component if applicable.
          *
    -     * @return \Zend\Http\Client
    +     * @return Http\Client
          */
         public static function getHttpClient()
         {
    -        if (!isset(self::$httpClient)):
    +        if (!isset(self::$httpClient)) {
                 self::$httpClient = new Http\Client;
    -        else:
    +        } else {
                 self::$httpClient->resetParameters();
    -        endif;
    +        }
             return self::$httpClient;
         }
     
    @@ -103,6 +109,33 @@ public static function clearHttpClient()
             self::$httpClient = null;
         }
     
    +    /**
    +     * Set the Escaper instance
    +     *
    +     * If null, resets the instance
    +     * 
    +     * @param  null|Escaper $escaper 
    +     */
    +    public static function setEscaper(Escaper $escaper = null)
    +    {
    +        static::$escaper = $escaper;
    +    }
    +
    +    /**
    +     * Get the Escaper instance
    +     *
    +     * If none registered, lazy-loads an instance.
    +     * 
    +     * @return Escaper
    +     */
    +    public static function getEscaper()
    +    {
    +        if (null === static::$escaper) {
    +            static::setEscaper(new Escaper());
    +        }
    +        return static::$escaper;
    +    }
    +
         /**
          * RFC 3986 safe url encoding method
          *
    @@ -111,7 +144,8 @@ public static function clearHttpClient()
          */
         public static function urlencode($string)
         {
    -        $rawencoded = rawurlencode($string);
    +        $escaper    = static::getEscaper();
    +        $rawencoded = $escaper->escapeUrl($string);
             $rfcencoded = str_replace('%7E', '~', $rawencoded);
             return $rfcencoded;
         }
    
  • library/Zend/Log/composer.json+1 0 modified
    @@ -20,6 +20,7 @@
         "suggest": {
             "ext-mongo": "*",
             "zendframework/zend-db": "Zend\\Db component",
    +        "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML formatter",
             "zendframework/zend-mail": "Zend\\Mail component",
             "zendframework/zend-validator": "Zend\\Validator component"
         }
    
  • library/Zend/Log/Formatter/Xml.php+38 4 modified
    @@ -14,6 +14,7 @@
     use DOMDocument;
     use DOMElement;
     use Traversable;
    +use Zend\Escaper\Escaper;
     use Zend\Stdlib\ArrayUtils;
     
     /**
    @@ -38,6 +39,11 @@ class Xml implements FormatterInterface
          */
         protected $encoding;
     
    +    /**
    +     * @var Escaper instance
    +     */
    +    protected $escaper;
    +
         /**
          * Format specifier for DateTime objects in event data (default: ISO 8601)
          *
    @@ -121,6 +127,33 @@ public function setEncoding($value)
             return $this;
         }
     
    +    /**
    +     * Set Escaper instance
    +     *
    +     * @param  Escaper $escaper
    +     * @return Xml
    +     */
    +    public function setEscaper(Escaper $escaper)
    +    {
    +        $this->escaper = $escaper;
    +        return $this;
    +    }
    +    
    +    /**
    +     * Get Escaper instance
    +     *
    +     * Lazy-loads an instance with the current encoding if none registered.
    +     *
    +     * @return Escaper
    +     */
    +    public function getEscaper()
    +    {
    +        if (null === $this->escaper) {
    +            $this->setEscaper(new Escaper($this->getEncoding()));
    +        }
    +        return $this->escaper;
    +    }
    +
         /**
          * Formats data into a single line to be written by the writer.
          *
    @@ -142,17 +175,18 @@ public function format($event)
                 }
             }
     
    -        $enc = $this->getEncoding();
    -        $dom = new DOMDocument('1.0', $enc);
    -        $elt = $dom->appendChild(new DOMElement($this->rootElement));
    +        $enc     = $this->getEncoding();
    +        $escaper = $this->getEscaper();
    +        $dom     = new DOMDocument('1.0', $enc);
    +        $elt     = $dom->appendChild(new DOMElement($this->rootElement));
     
             foreach ($dataToInsert as $key => $value) {
                 if (empty($value)
                     || is_scalar($value)
                     || (is_object($value) && method_exists($value,'__toString'))
                 ) {
                     if ($key == "message") {
    -                    $value = htmlspecialchars($value, ENT_COMPAT, $enc);
    +                    $value = $escaper->escapeHtml($value);
                     } elseif ($key == "extra" && empty($value)) {
                         continue;
                     }
    
  • library/Zend/Tag/Cloud/Decorator/AbstractCloud.php+1 51 modified
    @@ -10,62 +10,12 @@
     
     namespace Zend\Tag\Cloud\Decorator;
     
    -use Traversable;
    -use Zend\Stdlib\ArrayUtils;
    -use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
    -
     /**
      * Abstract class for cloud decorators
      *
      * @category  Zend
      * @package   Zend_Tag
      */
    -abstract class AbstractCloud implements Decorator
    +abstract class AbstractCloud extends AbstractDecorator
     {
    -    /**
    -     * Option keys to skip when calling setOptions()
    -     *
    -     * @var array
    -     */
    -    protected $skipOptions = array(
    -        'options',
    -        'config',
    -    );
    -
    -    /**
    -     * Create a new cloud decorator with options
    -     *
    -     * @param  array|Traversable $options
    -     */
    -    public function __construct($options = null)
    -    {
    -        if ($options instanceof Traversable) {
    -            $options = ArrayUtils::iteratorToArray($options);
    -        }
    -        if (is_array($options)) {
    -            $this->setOptions($options);
    -        }
    -    }
    -
    -    /**
    -     * Set options from array
    -     *
    -     * @param  array $options Configuration for the decorator
    -     * @return AbstractCloud
    -     */
    -    public function setOptions(array $options)
    -    {
    -        foreach ($options as $key => $value) {
    -            if (in_array(strtolower($key), $this->skipOptions)) {
    -                continue;
    -            }
    -
    -            $method = 'set' . $key;
    -            if (method_exists($this, $method)) {
    -                $this->$method($value);
    -            }
    -        }
    -
    -        return $this;
    -    }
     }
    
  • library/Zend/Tag/Cloud/Decorator/AbstractDecorator.php+190 0 added
    @@ -0,0 +1,190 @@
    +<?php
    +/**
    + * Zend Framework (http://framework.zend.com/)
    + *
    + * @link      http://github.com/zendframework/zf2 for the canonical source repository
    + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
    + * @license   http://framework.zend.com/license/new-bsd New BSD License
    + * @package   Zend_Tag
    + */
    +
    +namespace Zend\Tag\Cloud\Decorator;
    +
    +use Traversable;
    +use Zend\Escaper\Escaper;
    +use Zend\Stdlib\ArrayUtils;
    +use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
    +use Zend\Tag\Exception;
    +
    +/**
    + * Abstract class for decorators
    + *
    + * @category  Zend
    + * @package   Zend_Tag
    + */
    +abstract class AbstractDecorator implements Decorator
    +{
    +    /**
    +     * @var string Encoding to use
    +     */
    +    protected $encoding = 'UTF-8';
    +
    +    /**
    +     * @var Escaper
    +     */
    +    protected $escaper;
    +
    +    /**
    +     * Option keys to skip when calling setOptions()
    +     *
    +     * @var array
    +     */
    +    protected $skipOptions = array(
    +        'options',
    +        'config',
    +    );
    +
    +    /**
    +     * Create a new decorator with options
    +     *
    +     * @param  array|Traversable $options
    +     */
    +    public function __construct($options = null)
    +    {
    +        if ($options instanceof Traversable) {
    +            $options = ArrayUtils::iteratorToArray($options);
    +        }
    +        if (is_array($options)) {
    +            $this->setOptions($options);
    +        }
    +    }
    +
    +    /**
    +     * Set options from array
    +     *
    +     * @param  array $options Configuration for the decorator
    +     * @return AbstractTag
    +     */
    +    public function setOptions(array $options)
    +    {
    +        foreach ($options as $key => $value) {
    +            if (in_array(strtolower($key), $this->skipOptions)) {
    +                continue;
    +            }
    +
    +            $method = 'set' . $key;
    +            if (method_exists($this, $method)) {
    +                $this->$method($value);
    +            }
    +        }
    +
    +        return $this;
    +    }
    +
    +    /**
    +     * Get encoding
    +     *
    +     * @return string
    +     */
    +    public function getEncoding()
    +    {
    +        return $this->encoding;
    +    }
    +
    +    /**
    +     * Set encoding
    +     *
    +     * @param string
    +     * @return HTMLCloud
    +     */
    +    public function setEncoding($value)
    +    {
    +        $this->encoding = (string) $value;
    +        return $this;
    +    }
    +
    +    /**
    +     * Set Escaper instance
    +     *
    +     * @param  Escaper $escaper
    +     * @return HtmlCloud
    +     */
    +    public function setEscaper($escaper)
    +    {
    +        $this->escaper = $escaper;
    +        return $this;
    +    }
    +    
    +    /**
    +     * Retrieve Escaper instance
    +     *
    +     * If none registered, instantiates and registers one using current encoding.
    +     *
    +     * @return Escaper
    +     */
    +    public function getEscaper()
    +    {
    +        if (null === $this->escaper) {
    +            $this->setEscaper(new Escaper($this->getEncoding()));
    +        }
    +        return $this->escaper;
    +    }
    +
    +    /**
    +     * Validate an HTML element name
    +     * 
    +     * @param  string $name 
    +     * @throws Exception\InvalidElementNameException
    +     */
    +    protected function validateElementName($name)
    +    {
    +        if (!preg_match('/^[a-z0-9]+$/i', $name)) {
    +            throw new Exception\InvalidElementNameException(sprintf(
    +                '%s: Invalid element name "%s" provided; please provide valid HTML element names',
    +                __METHOD__,
    +                $this->getEscaper()->escapeHtml($name)
    +            ));
    +        }
    +    }
    +
    +    /**
    +     * Validate an HTML attribute name
    +     * 
    +     * @param  string $name 
    +     * @throws Exception\InvalidAttributeNameException
    +     */
    +    protected function validateAttributeName($name)
    +    {
    +        if (!preg_match('/^[a-z_:][-a-z0-9_:.]*$/i', $name)) {
    +            throw new Exception\InvalidAttributeNameException(sprintf(
    +                '%s: Invalid HTML attribute name "%s" provided; please provide valid HTML attribute names',
    +                __METHOD__,
    +                $this->getEscaper()->escapeHtml($name)
    +            ));
    +        }
    +    }
    +
    +    protected function wrapTag($html)
    +    {
    +        $escaper = $this->getEscaper();
    +        foreach ($this->getHTMLTags() as $key => $data) {
    +            if (is_array($data)) {
    +                $attributes = '';
    +                $htmlTag    = $key;
    +                $this->validateElementName($htmlTag);
    +
    +                foreach ($data as $param => $value) {
    +                    $this->validateAttributeName($param);
    +                    $attributes .= ' ' . $param . '="' . $escaper->escapeHtmlAttr($value) . '"';
    +                }
    +            } else {
    +                $attributes = '';
    +                $htmlTag    = $data;
    +                $this->validateElementName($htmlTag);
    +            }
    +
    +            $html = sprintf('<%1$s%3$s>%2$s</%1$s>', $htmlTag, $html, $attributes);
    +        }
    +        return $html;
    +    }
    +}
    
  • library/Zend/Tag/Cloud/Decorator/AbstractTag.php+1 51 modified
    @@ -10,62 +10,12 @@
     
     namespace Zend\Tag\Cloud\Decorator;
     
    -use Traversable;
    -use Zend\Stdlib\ArrayUtils;
    -use Zend\Tag\Cloud\Decorator\DecoratorInterface as Decorator;
    -
     /**
      * Abstract class for tag decorators
      *
      * @category  Zend
      * @package   Zend_Tag
      */
    -abstract class AbstractTag implements Decorator
    +abstract class AbstractTag extends AbstractDecorator
     {
    -    /**
    -     * Option keys to skip when calling setOptions()
    -     *
    -     * @var array
    -     */
    -    protected $skipOptions = array(
    -        'options',
    -        'config',
    -    );
    -
    -    /**
    -     * Create a new cloud decorator with options
    -     *
    -     * @param  array|Traversable $options
    -     */
    -    public function __construct($options = null)
    -    {
    -        if ($options instanceof Traversable) {
    -            $options = ArrayUtils::iteratorToArray($options);
    -        }
    -        if (is_array($options)) {
    -            $this->setOptions($options);
    -        }
    -    }
    -
    -    /**
    -     * Set options from array
    -     *
    -     * @param  array $options Configuration for the decorator
    -     * @return AbstractTag
    -     */
    -    public function setOptions(array $options)
    -    {
    -        foreach ($options as $key => $value) {
    -            if (in_array(strtolower($key), $this->skipOptions)) {
    -                continue;
    -            }
    -
    -            $method = 'set' . $key;
    -            if (method_exists($this, $method)) {
    -                $this->$method($value);
    -            }
    -        }
    -
    -        return $this;
    -    }
     }
    
  • library/Zend/Tag/Cloud/Decorator/HtmlCloud.php+1 45 modified
    @@ -18,11 +18,6 @@
      */
     class HtmlCloud extends AbstractCloud
     {
    -    /**
    -     * @var string Encoding to use
    -     */
    -    protected $encoding = 'UTF-8';
    -
         /**
          * List of HTML tags
          *
    @@ -39,28 +34,6 @@ class HtmlCloud extends AbstractCloud
          */
         protected $separator = ' ';
     
    -    /**
    -     * Get encoding
    -     *
    -     * @return string
    -     */
    -    public function getEncoding()
    -    {
    -        return $this->encoding;
    -    }
    -
    -    /**
    -     * Set encoding
    -     *
    -     * @param string
    -     * @return HTMLCloud
    -     */
    -    public function setEncoding($value)
    -    {
    -        $this->encoding = (string) $value;
    -        return $this;
    -    }
    -
         /**
          * Set the HTML tags surrounding all tags
          *
    @@ -121,24 +94,7 @@ public function render($tags)
                 ));
             }
             $cloudHTML = implode($this->getSeparator(), $tags);
    -
    -        $enc = $this->getEncoding();
    -        foreach ($this->getHTMLTags() as $key => $data) {
    -            if (is_array($data)) {
    -                $htmlTag    = $key;
    -                $attributes = '';
    -
    -                foreach ($data as $param => $value) {
    -                    $attributes .= ' ' . $param . '="' . htmlspecialchars($value, ENT_COMPAT, $enc) . '"';
    -                }
    -            } else {
    -                $htmlTag    = $data;
    -                $attributes = '';
    -            }
    -
    -            $cloudHTML = sprintf('<%1$s%3$s>%2$s</%1$s>', $htmlTag, $cloudHTML, $attributes);
    -        }
    -
    +        $cloudHTML = $this->wrapTag($cloudHTML);
             return $cloudHTML;
         }
     }
    
  • library/Zend/Tag/Cloud/Decorator/HtmlTag.php+4 47 modified
    @@ -29,11 +29,6 @@ class HtmlTag extends AbstractTag
          */
         protected $classList = null;
     
    -    /**
    -     * @var string Encoding to utilize
    -     */
    -    protected $encoding = 'UTF-8';
    -
         /**
          * Unit for the fontsize
          *
    @@ -107,28 +102,6 @@ public function getClassList()
             return $this->classList;
         }
     
    -    /**
    -     * Get encoding
    -     *
    -     * @return string
    -     */
    -    public function getEncoding()
    -    {
    -         return $this->encoding;
    -    }
    -
    -    /**
    -     * Set encoding
    -     *
    -     * @param  string $value
    -     * @return HTMLTag
    -     */
    -    public function setEncoding($value)
    -    {
    -        $this->encoding = (string) $value;
    -        return $this;
    -    }
    -
         /**
          * Set the font size unit
          *
    @@ -259,32 +232,16 @@ public function render($tags)
     
             $result = array();
     
    -        $enc = $this->getEncoding();
    +        $escaper = $this->getEscaper();
             foreach ($tags as $tag) {
                 if (null === ($classList = $this->getClassList())) {
                     $attribute = sprintf('style="font-size: %d%s;"', $tag->getParam('weightValue'), $this->getFontSizeUnit());
                 } else {
    -                $attribute = sprintf('class="%s"', htmlspecialchars($tag->getParam('weightValue'), ENT_COMPAT, $enc));
    -            }
    -
    -            $tagHTML = sprintf('<a href="%s" %s>%s</a>', htmlSpecialChars($tag->getParam('url'), ENT_COMPAT, $enc), $attribute, $tag->getTitle());
    -
    -            foreach ($this->getHTMLTags() as $key => $data) {
    -                if (is_array($data)) {
    -                    $htmlTag    = $key;
    -                    $attributes = '';
    -
    -                    foreach ($data as $param => $value) {
    -                        $attributes .= ' ' . $param . '="' . htmlspecialchars($value, ENT_COMPAT, $enc) . '"';
    -                    }
    -                } else {
    -                    $htmlTag    = $data;
    -                    $attributes = '';
    -                }
    -
    -                $tagHTML = sprintf('<%1$s%3$s>%2$s</%1$s>', $htmlTag, $tagHTML, $attributes);
    +                $attribute = sprintf('class="%s"', $escaper->escapeHtmlAttr($tag->getParam('weightValue')));
                 }
     
    +            $tagHTML  = sprintf('<a href="%s" %s>%s</a>', $escaper->escapeHtml($tag->getParam('url')), $attribute, $escaper->escapeHtml($tag->getTitle()));
    +            $tagHTML  = $this->wrapTag($tagHTML);
                 $result[] = $tagHTML;
             }
     
    
  • library/Zend/Tag/composer.json+3 2 modified
    @@ -13,6 +13,7 @@
         },
         "target-dir": "Zend/Tag",
         "require": {
    -        "php": ">=5.3.3"
    +        "php": ">=5.3.3",
    +        "zendframework/zend-escaper": "self.version"
         }
    -}
    \ No newline at end of file
    +}
    
  • library/Zend/Tag/Exception/InvalidAttributeNameException.php+16 0 added
    @@ -0,0 +1,16 @@
    +<?php
    +/**
    + * Zend Framework (http://framework.zend.com/)
    + *
    + * @link      http://github.com/zendframework/zf2 for the canonical source repository
    + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
    + * @license   http://framework.zend.com/license/new-bsd New BSD License
    + * @package   Zend_Tag
    + */
    +
    +namespace Zend\Tag\Exception;
    +
    +use DomainException;
    +
    +class InvalidAttributeNameException extends DomainException implements ExceptionInterface
    +{}
    
  • library/Zend/Tag/Exception/InvalidElementNameException.php+16 0 added
    @@ -0,0 +1,16 @@
    +<?php
    +/**
    + * Zend Framework (http://framework.zend.com/)
    + *
    + * @link      http://github.com/zendframework/zf2 for the canonical source repository
    + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
    + * @license   http://framework.zend.com/license/new-bsd New BSD License
    + * @package   Zend_Tag
    + */
    +
    +namespace Zend\Tag\Exception;
    +
    +use DomainException;
    +
    +class InvalidElementNameException extends DomainException implements ExceptionInterface
    +{}
    
  • library/Zend/Uri/composer.json+2 1 modified
    @@ -14,6 +14,7 @@
         "target-dir": "Zend/Uri",
         "require": {
             "php": ">=5.3.3",
    +        "zendframework/zend-escaper": "self.version",
             "zendframework/zend-validator": "self.version"
         }
    -}
    \ No newline at end of file
    +}
    
  • library/Zend/Uri/Uri.php+40 6 modified
    @@ -10,6 +10,7 @@
     
     namespace Zend\Uri;
     
    +use Zend\Escaper\Escaper;
     use Zend\Validator;
     
     /**
    @@ -125,6 +126,11 @@ class Uri implements UriInterface
          */
         protected static $defaultPorts = array();
     
    +    /**
    +     * @var Escaper
    +     */
    +    protected static $escaper;
    +
         /**
          * Create a new URI object
          *
    @@ -152,6 +158,31 @@ public function __construct($uri = null)
             }
         }
     
    +    /**
    +     * Set Escaper instance
    +     * 
    +     * @param  Escaper $escaper 
    +     */
    +    public static function setEscaper(Escaper $escaper)
    +    {
    +        static::$escaper = $escaper;
    +    }
    +
    +    /**
    +     * Retrieve Escaper instance
    +     *
    +     * Lazy-loads one if none provided
    +     * 
    +     * @return Escaper
    +     */
    +    public static function getEscaper()
    +    {
    +        if (null === static::$escaper) {
    +            static::setEscaper(new Escaper());
    +        }
    +        return static::$escaper;
    +    }
    +
         /**
          * Check if the URI is valid
          *
    @@ -935,8 +966,9 @@ public static function encodeUserInfo($userInfo)
             }
     
             $regex   = '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:]|%(?![A-Fa-f0-9]{2}))/';
    -        $replace = function($match) {
    -            return rawurlencode($match[0]);
    +        $escaper = static::getEscaper();
    +        $replace = function ($match) use ($escaper) {
    +            return $escaper->escapeUrl($match[0]);
             };
     
             return preg_replace_callback($regex, $replace, $userInfo);
    @@ -962,8 +994,9 @@ public static function encodePath($path)
             }
     
             $regex   = '/(?:[^' . self::CHAR_UNRESERVED . ':@&=\+\$,\/;%]+|%(?![A-Fa-f0-9]{2}))/';
    -        $replace = function($match) {
    -            return rawurlencode($match[0]);
    +        $escaper = static::getEscaper();
    +        $replace = function ($match) use ($escaper) {
    +            return $escaper->escapeUrl($match[0]);
             };
     
             return preg_replace_callback($regex, $replace, $path);
    @@ -990,8 +1023,9 @@ public static function encodeQueryFragment($input)
             }
     
             $regex   = '/(?:[^' . self::CHAR_UNRESERVED . self::CHAR_SUB_DELIMS . '%:@\/\?]+|%(?![A-Fa-f0-9]{2}))/';
    -        $replace = function($match) {
    -            return rawurlencode($match[0]);
    +        $escaper = static::getEscaper();
    +        $replace = function ($match) use ($escaper) {
    +            return $escaper->escapeUrl($match[0]);
             };
     
             return preg_replace_callback($regex, $replace, $input);
    
  • library/Zend/View/Helper/HeadStyle.php+2 1 modified
    @@ -311,6 +311,7 @@ public function itemToString(stdClass $item, $indent)
                 ) {
                     $enc = $this->view->getEncoding();
                 }
    +            $escaper = $this->getEscaper($enc);
                 foreach ($item->attributes as $key => $value) {
                     if (!in_array($key, $this->optionalAttributes)) {
                         continue;
    @@ -333,7 +334,7 @@ public function itemToString(stdClass $item, $indent)
                             $value = substr($value, 0, -1);
                         }
                     }
    -                $attrString .= sprintf(' %s="%s"', $key, htmlspecialchars($value, ENT_COMPAT, $enc));
    +                $attrString .= sprintf(' %s="%s"', $key, $escaper->escapeHtmlAttr($value));
                 }
             }
     
    
  • library/Zend/View/Helper/Navigation/Sitemap.php+2 8 modified
    @@ -242,14 +242,8 @@ public function getServerUrl()
          */
         protected function xmlEscape($string)
         {
    -        $enc = 'UTF-8';
    -        if ($this->view instanceof View\Renderer\RendererInterface
    -            && method_exists($this->view, 'getEncoding')
    -        ) {
    -            $enc = $this->view->getEncoding();
    -        }
    -
    -        return htmlspecialchars($string, ENT_QUOTES, $enc, false);
    +        $escaper = $this->view->plugin('escapeHtml');
    +        return $escaper($string);
         }
     
         // Public methods:
    
  • library/Zend/View/Helper/Placeholder/Container/AbstractStandalone.php+40 11 modified
    @@ -10,8 +10,10 @@
     
     namespace Zend\View\Helper\Placeholder\Container;
     
    +use Zend\Escaper\Escaper;
     use Zend\View\Exception;
     use Zend\View\Helper\Placeholder\Registry;
    +use Zend\View\Renderer\RendererInterface;
     
     /**
      * Base class for targeted placeholder helpers
    @@ -28,6 +30,11 @@ abstract class AbstractStandalone
          */
         protected $container;
     
    +    /**
    +     * @var Escaper[]
    +     */
    +    protected $escapers = array();
    +
         /**
          * @var \Zend\View\Helper\Placeholder\Registry
          */
    @@ -78,6 +85,35 @@ public function setRegistry(Registry $registry)
             return $this;
         }
     
    +    /**
    +     * Set Escaper instance
    +     *
    +     * @param  Escaper $escaper
    +     * @return AbstractStandalone
    +     */
    +    public function setEscaper(Escaper $escaper)
    +    {
    +        $encoding = $escaper->getEncoding();
    +        $this->escapers[$encoding] = $escaper;
    +        return $this;
    +    }
    +    
    +    /**
    +     * Get Escaper instance
    +     *
    +     * Lazy-loads one if none available
    +     *
    +     * @return mixed
    +     */
    +    public function getEscaper($enc = 'UTF-8')
    +    {
    +        $enc = strtolower($enc);
    +        if (!isset($this->escapers[$enc])) {
    +            $this->setEscaper(new Escaper($enc));
    +        }
    +        return $this->escapers[$enc];
    +    }
    +
         /**
          * Set whether or not auto escaping should be used
          *
    @@ -108,23 +144,16 @@ public function getAutoEscape()
          */
         protected function escape($string)
         {
    -        $enc = 'UTF-8';
    -        if ($this->view instanceof \Zend\View\Renderer\RendererInterface
    +        if ($this->view instanceof RendererInterface
                 && method_exists($this->view, 'getEncoding')
             ) {
    -            $enc = $this->view->getEncoding();
    +            $enc     = $this->view->getEncoding();
                 $escaper = $this->view->plugin('escapeHtml');
                 return $escaper((string) $string);
             }
    -        /**
    -         * bump this out to a protected method to kill the instance penalty!
    -         */
    -        $escaper = new \Zend\Escaper\Escaper($enc);
    +
    +        $escaper = $this->getEscaper();
             return $escaper->escapeHtml((string) $string);
    -        /**
    -         * Replaced to ensure consistent escaping
    -         */
    -        //return htmlspecialchars((string) $string, ENT_COMPAT, $enc);
         }
     
         /**
    
  • tests/ZendTest/Tag/Cloud/CloudTest.php+2 2 modified
    @@ -208,7 +208,7 @@ public function testSkipOptions()
         public function testRender()
         {
             $cloud    = $this->_getCloud(array('tags' => array(array('title' => 'foo', 'weight' => 1), array('title' => 'bar', 'weight' => 3))));
    -        $expected = '<ul class="Zend\Tag\Cloud">'
    +        $expected = '<ul class="Zend&#x5C;Tag&#x5C;Cloud">'
                       . '<li><a href="" style="font-size: 10px;">foo</a></li> '
                       . '<li><a href="" style="font-size: 20px;">bar</a></li>'
                       . '</ul>';
    @@ -224,7 +224,7 @@ public function testRenderEmptyCloud()
         public function testRenderViaToString()
         {
             $cloud = $this->_getCloud(array('tags' => array(array('title' => 'foo', 'weight' => 1), array('title' => 'bar', 'weight' => 3))));
    -        $expected = '<ul class="Zend\Tag\Cloud">'
    +        $expected = '<ul class="Zend&#x5C;Tag&#x5C;Cloud">'
                       . '<li><a href="" style="font-size: 10px;">foo</a></li> '
                       . '<li><a href="" style="font-size: 20px;">bar</a></li>'
                       . '</ul>';
    
  • tests/ZendTest/Tag/Cloud/Decorator/HtmlCloudTest.php+58 3 modified
    @@ -25,7 +25,7 @@ public function testDefaultOutput()
         {
             $decorator = new Decorator\HtmlCloud();
     
    -        $this->assertEquals('<ul class="Zend\Tag\Cloud">foo bar</ul>', $decorator->render(array('foo', 'bar')));
    +        $this->assertEquals('<ul class="Zend&#x5C;Tag&#x5C;Cloud">foo bar</ul>', $decorator->render(array('foo', 'bar')));
         }
     
         public function testNestedTags()
    @@ -41,7 +41,7 @@ public function testSeparator()
             $decorator = new Decorator\HtmlCloud();
             $decorator->setSeparator('-');
     
    -        $this->assertEquals('<ul class="Zend\Tag\Cloud">foo-bar</ul>', $decorator->render(array('foo', 'bar')));
    +        $this->assertEquals('<ul class="Zend&#x5C;Tag&#x5C;Cloud">foo-bar</ul>', $decorator->render(array('foo', 'bar')));
         }
     
         public function testConstructorWithArray()
    @@ -71,5 +71,60 @@ public function testSkipOptions()
             $decorator = new Decorator\HtmlCloud(array('options' => 'foobar'));
             // In case would fail due to an error
         }
    -}
     
    +    public function invalidHtmlTagProvider()
    +    {
    +        return array(
    +            array(array('_foo')),
    +            array(array('&foo')),
    +            array(array(' foo')),
    +            array(array(' foo')),
    +            array(array(
    +                '_foo' => array(),
    +            )),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider invalidHtmlTagProvider
    +     */
    +    public function testInvalidHtmlTagsRaiseAnException($tags)
    +    {
    +        $decorator = new Decorator\HtmlCloud();
    +        $decorator->setHTMLTags($tags);
    +        $this->setExpectedException('Zend\Tag\Exception\InvalidElementNameException');
    +        $decorator->render(array());
    +    }
    +
    +    public function invalidAttributeProvider()
    +    {
    +        return array(
    +            array(array(
    +                'foo' => array(
    +                    '&bar' => 'baz',
    +                ),
    +            )),
    +            array(array(
    +                'foo' => array(
    +                    ':bar&baz' => 'bat',
    +                ),
    +            )),
    +            array(array(
    +                'foo' => array(
    +                    'bar/baz' => 'bat',
    +                ),
    +            )),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider invalidAttributeProvider
    +     */
    +    public function testInvalidAttributeNamesRaiseAnException($tags)
    +    {
    +        $decorator = new Decorator\HtmlCloud();
    +        $decorator->setHTMLTags($tags);
    +        $this->setExpectedException('Zend\Tag\Exception\InvalidAttributeNameException');
    +        $decorator->render(array());
    +    }
    +}
    
  • tests/ZendTest/Tag/Cloud/Decorator/HtmlTagTest.php+82 33 modified
    @@ -10,9 +10,9 @@
     
     namespace ZendTest\Tag\Cloud\Decorator;
     
    -use	Zend\Tag,
    -    Zend\Tag\Cloud\Decorator,
    -    Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException;
    +use	Zend\Tag;
    +use Zend\Tag\Cloud\Decorator;
    +use Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException;
     
     /**
      * @category   Zend
    @@ -74,60 +74,40 @@ public function testEmptyClassList()
         {
             $decorator = new Decorator\HtmlTag();
     
    -        try {
    -            $decorator->setClassList(array());
    -            $this->fail('An expected Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException was not raised');
    -        } catch (InvalidArgumentException $e) {
    -            $this->assertEquals($e->getMessage(), 'Classlist is empty');
    -        }
    +        $this->setExpectedException('Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException', 'Classlist is empty');
    +        $decorator->setClassList(array());
         }
     
         public function testInvalidClassList()
         {
             $decorator = new Decorator\HtmlTag();
     
    -        try {
    -            $decorator->setClassList(array(array()));
    -            $this->fail('An expected Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException was not raised');
    -        } catch (InvalidArgumentException $e) {
    -            $this->assertEquals($e->getMessage(), 'Classlist contains an invalid classname');
    -        }
    +        $this->setExpectedException('Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException', 'Classlist contains an invalid classname');
    +        $decorator->setClassList(array(array()));
         }
     
         public function testInvalidFontSizeUnit()
         {
             $decorator = new Decorator\HtmlTag();
     
    -        try {
    -            $decorator->setFontSizeUnit('foo');
    -            $this->fail('An expected Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException was not raised');
    -        } catch (InvalidArgumentException $e) {
    -            $this->assertEquals($e->getMessage(), 'Invalid fontsize unit specified');
    -        }
    +        $this->setExpectedException('Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException', 'Invalid fontsize unit specified');
    +        $decorator->setFontSizeUnit('foo');
         }
     
         public function testInvalidMinFontSize()
         {
             $decorator = new Decorator\HtmlTag();
     
    -        try {
    -            $decorator->setMinFontSize('foo');
    -            $this->fail('An expected Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException was not raised');
    -        } catch (InvalidArgumentException $e) {
    -            $this->assertEquals($e->getMessage(), 'Fontsize must be numeric');
    -        }
    +        $this->setExpectedException('Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException', 'Fontsize must be numeric');
    +        $decorator->setMinFontSize('foo');
         }
     
         public function testInvalidMaxFontSize()
         {
             $decorator = new Decorator\HtmlTag();
     
    -        try {
    -            $decorator->setMaxFontSize('foo');
    -            $this->fail('An expected Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException was not raised');
    -        } catch (InvalidArgumentException $e) {
    -            $this->assertEquals($e->getMessage(), 'Fontsize must be numeric');
    -        }
    +        $this->setExpectedException('Zend\Tag\Cloud\Decorator\Exception\InvalidArgumentException', 'Fontsize must be numeric');
    +        $decorator->setMaxFontSize('foo');
         }
     
         public function testConstructorWithArray()
    @@ -173,4 +153,73 @@ protected function _getTagList()
     
             return $list;
         }
    +
    +    public function getTags()
    +    {
    +        $tags = new Tag\ItemList();
    +        $tags[] = new Tag\Item(array(
    +            'title' => 'tag',
    +            'weight' => 1,
    +            'params' => array(
    +                'url' => 'http://testing',
    +            ),
    +        ));
    +        return $tags;
    +    }
    +
    +    public function invalidHtmlElementProvider()
    +    {
    +        return array(
    +            array(array('_foo')),
    +            array(array('&foo')),
    +            array(array(' foo')),
    +            array(array(' foo')),
    +            array(array(
    +                '_foo' => array(),
    +            )),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider invalidHtmlElementProvider
    +     */
    +    public function testInvalidElementNamesRaiseAnException($tags)
    +    {
    +        $decorator = new Decorator\HtmlTag();
    +        $decorator->setHTMLTags($tags);
    +        $this->setExpectedException('Zend\Tag\Exception\InvalidElementNameException');
    +        $decorator->render($this->getTags());
    +    }
    +
    +    public function invalidAttributeProvider()
    +    {
    +        return array(
    +            array(array(
    +                'foo' => array(
    +                    '&bar' => 'baz',
    +                ),
    +            )),
    +            array(array(
    +                'foo' => array(
    +                    ':bar&baz' => 'bat',
    +                ),
    +            )),
    +            array(array(
    +                'foo' => array(
    +                    'bar/baz' => 'bat',
    +                ),
    +            )),
    +        );
    +    }
    +
    +    /**
    +     * @dataProvider invalidAttributeProvider
    +     */
    +    public function testInvalidAttributesRaiseAnException($tags)
    +    {
    +        $decorator = new Decorator\HtmlTag();
    +        $decorator->setHTMLTags($tags);
    +        $this->setExpectedException('Zend\Tag\Exception\InvalidAttributeNameException');
    +        $decorator->render($this->getTags());
    +    }
     }
    

Vulnerability mechanics

Root cause

"Multiple Zend Framework 2.0.x components output user-controlled data using raw `htmlspecialchars()` or no escaping instead of the context‑aware `Zend\Escaper\Escaper` methods, enabling cross‑site scripting."

Attack vector

An attacker supplies crafted input (such as a tag title, URL parameter, log message, or debug output) that contains HTML metacharacters like `&lt;`, `&gt;`, `&quot;`, or `&amp;`. Because the affected components did not use `Zend\Escaper\Escaper` to properly escape HTML, HTML attributes, or URLs, the unescaped input is rendered in a web page context, allowing arbitrary script or HTML injection [ref_id=1]. The attack is remote and requires no special privileges; the attacker simply needs to deliver the malicious input through any channel that feeds into one of the eight listed components (e.g., a tag cloud, a feed subscription request, a log entry, a debug dump, or a URI).

Affected code

The patch touches eight components: `Zend\Debug`, `Zend\Feed\PubSubHubbub`, `Zend\Log\Formatter\Xml`, `Zend\Tag\Cloud\Decorator` (AbstractDecorator, AbstractTag, AbstractCloud, HtmlTag, HtmlCloud), `Zend\Uri\Uri`, `Zend\View\Helper\HeadStyle`, `Zend\View\Helper\Navigation\Sitemap`, and `Zend\View\Helper\Placeholder\Container\AbstractStandalone` [patch_id=2243828]. The core defect is that these components output user-controlled data using raw `htmlspecialchars()` or no escaping at all instead of using `Zend\Escaper\Escaper` methods (`escapeHtml`, `escapeHtmlAttr`, `escapeUrl`) [ref_id=1].

What the fix does

The patch introduces `Zend\Escaper\Escaper` instances into each affected component and replaces ad‑hoc `htmlspecialchars()` calls with the appropriate Escaper method (`escapeHtml`, `escapeHtmlAttr`, or `escapeUrl`) [ref_id=1]. For example, in `Zend\Debug` the old `htmlspecialchars($output, ENT_QUOTES)` is replaced by `static::getEscaper()->escapeHtml($output)`, and in `Zend\Tag\Cloud\Decorator\HtmlTag` the `wrapTag()` method now calls `$escaper->escapeHtmlAttr($value)` for attribute values and `$escaper->escapeHtml()` for tag content [patch_id=2243828]. The new `AbstractDecorator` base class provides shared `getEscaper()`/`setEscaper()` methods and a `wrapTag()` helper that consistently applies escaping, while `AbstractTag` and `AbstractCloud` are refactored to extend this base class rather than duplicating option‑handling logic [patch_id=2243828].

Preconditions

  • configThe application must use one of the eight affected Zend Framework 2.0.x components (Debug, Feed\PubSubHubbub, Log\Formatter\Xml, Tag\Cloud\Decorator, Uri, View\Helper\HeadStyle, View\Helper\Navigation\Sitemap, or View\Helper\Placeholder\Container\AbstractStandalone).
  • inputThe attacker must be able to supply input that reaches the vulnerable component (e.g., a tag title, a URL parameter, a log message, or debug output).

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

References

8

News mentions

0

No linked articles in our index yet.