Advisory 03/2009: Piwik Cookie unserialize() Vulnerability

SektionEins GmbH
www.sektioneins.de
-= Security Advisory =-
Advisory: Piwik Cookie Unserialize() Vulnerability
Release Date: 2009/12/09
Last Modified: 2009/12/09
Author: Stefan Esser [stefan.esser[at]sektioneins.de]
Application: Piwik <= 0.4.5
Severity: Piwik unserializes() user input which allows an attacker
to send a carefully crafted cookie that when unserialized
utilizes Piwik's classes to upload arbitrary files or
execute arbitrary PHP code
Risk: Critical
Vendor Status: Piwik 0.5.0 was released which fixes this vulnerability
Reference: http://www.sektioneins.de/en/advisories/advisory-032009-piwik-cookie-unserialize-vulnerability/
http://www.suspekt.org/downloads/RSS09-WebApplicationFirewallBypassesAndPHPExploits.pdf
http://www.suspekt.org/downloads/POC2009-ShockingNewsInPHPExploitation.pdf
Overview:
Quote from http://www.piwik.org
"Piwik is a downloadable, open source (GPL licensed) web analytics
software program. It provides you with detailed real time reports
on your website visitors: the search engines and keywords they
used, the language they speak, your popular pages… and so much more.
Piwik aims to be an open source alternative to Google Analytics."
Piwik recently became sourceforge project of the month and won the
Infoworld Bossie Award for best open source enterprise software which
made it quite popular. Therefore Piwik is nowadays installed on many
high profile websites like: banking websites, political party websites,
gaming websites, blogs and even security company websites.
During our research in unserialize() vulnerabilities it was discovered
that Piwik unserializes data from the user supplied cookie. By
unserializing some of Piwik's objects it is possible to write
arbitrary files to writable locations on the webserver which
can be used to upload e.g. PHP files to writable directories
within the webserver's document root which usually exist in a
standard Piwik installation. In newer versions of Piwik it is
also possible to execute arbitrary PHP code directly.
Combined with the interruption vulnerability exploits demonstrated
by SektionEins at Syscan and Blackhat it is possible to disable
all internal PHP security protections and execute arbitrary code
e.g. local kernel exploits to become root.
Details:
SektionEins recently demonstrated how it is sometimes possible
to execute arbitrary PHP code in an application using unserialize()
on user supplied data. In detail various exploits were shown that
work against all Zend Framework based applications that unserialize()
user input. Part of this research was to find popular PHP open
source applications that are vulnerable to this.
During our search it was discovered that Piwik does unserialize()
data from the cookie and uses parts of the Zend Framework:
protected function loadContentFromCookie()
{
$cookieStr = $_COOKIE[$this->name];
$values = explode( self::VALUE_SEPARATOR, $cookieStr);
foreach($values as $nameValue)
{
$equalPos = strpos($nameValue, '=');
$varName = substr($nameValue,0,$equalPos);
$varValue = substr($nameValue,$equalPos+1);
// no numeric value are base64 encoded so we need to decode them
if(!is_numeric($varValue))
{
$varValue = base64_decode($varValue);
// some of the values may be serialized array so we try to...
if( ($arrayValue = @unserialize($varValue)) !== false
// we set the unserialized version only for arrays...
&& is_array($arrayValue)
)
{
$varValue = $arrayValue;
}
}
$this->set($varName, $varValue);
}
}
While this allows to unserialize() user input the previously
demonstrated exploits will not work on Piwik because only parts of
the Zend Framework are shipped with the Piwik source code. Because
of this we had to evaluate if Piwik's own classes can be utilized
in exploits.
When trying to exploit an unserialize() vulnerability in a PHP
application the first step is to enumerate the objects that contain
__wakeup() or __destruct() methods and read their code to analyze if
these methods are doing something interesting.
When looking at the Piwik source code one particular class can be
found that allows writing arbitrary configuration files to the
webserver. This class is called Piwik_Config and contains the
following code.
function __destruct()
{
if($this->configFileUpdated === true
&& $this->doWriteFileWhenUpdated === true)
{
$configFile = "; <?php exit; ?> DO NOT REMOVE THIS LINE\n";
$configFile .= "; file automatically generated or modified by "
"Piwik; you can manually override the default "
"values in global.ini.php by redefining them "
"in this file.\n";
foreach($this->userConfig as $section => $arraySection)
{
$arraySection = $arraySection->toArray();
$configFile .= "[$section]\n";
foreach($arraySection as $name => $value)
{
...
$configFile .= $name.' = '.$value."\n";
...
}
$configFile .= "\n";
}
chdir($this->correctCwd);
file_put_contents($this->pathIniFileUserConfig, $configFile );
}
}
It should be obvious that when unserializing a Piwik_Config object
it is possible to write arbitrary configurations to arbitrary
locations by just setting the properties of the object correctly.
However it should also be obvious that executing arbitrary PHP code
is not that simple because the authors of Piwik add a show stopper
to the beginning of every configuration file "; <?php exit; ...".
Because of this it is not possible to just write PHP code into a
file and execute it.
There is however a lesser known and nearly never used feature of PHP5
that allows exploiting this situation. By using the PHP5 filter
stream wrapper through the php://filter URI scheme it is possible to
write arbitrary files to the server. By crafting a configuration
filename like
php://filter/write=convert.base64-decode/resource=/var/www/x.php it
is possible to channel all writes to the file through a base64
decoder. Because PHP does ignore invalid characters during base64
decoding it is possible to destroy the show stopper by a single
base64 decoding. By calculating how many base64 compatible characters
are contained in the show stopper it is possible to find out how
many padding bytes are necessary so that base64 injected PHP payload
ends up correctly decoded in the written configuration file.
Because the filter wrapper can be stacked several times before data
is written to the actual file it is possible to construct payloads
that do not only destroy the show stopper but also get rid of all the
bytes. In current Piwik versions 5 times base64 decode is required
for this. In earlier versions of Piwik there have been 3 different
show stoppers strings. Therefore in order to exploit older Piwik
versions different exploits are required.
In order to execute arbitrary code it is possible to overwrite one
of Piwik cache files and triggering a tracking event. Alternatively
it is possible to first write a .htaccess file to one of the
directories within Piwik's tmp directory that allows accessing its
content and then dropping a PHP file in there.
In the most recent Piwik version the Zend Framework components were
upgraded which allows executing arbitrary PHP code directly. To
achieve this the Zend_Log destructor is utilized.
public function __destruct()
{
foreach($this->_writers as $writer) {
$writer->shutdown();
}
}
The Zend_Log destructor iterates through an array which it expects
inside the _writers property. Each element of this array is then
expected to have a method called shutdown() which is then executed.
The next step in creating an exploit is to find classes that contain
a shutdown() method. The best fitting class is Zend_Log_Writer_Mail.
It is the same class that is also utilized in the generic Zend
Framework exploit.
public function shutdown()
{
// If there are events to mail, use them as message body. Otherwise,
// there is no mail to be sent.
if (empty($this->_eventsToMail)) {
return;
}
if ($this->_subjectPrependText !== null) {
// Tack on the summary of entries per-priority to the subject
// line and set it on the Zend_Mail object.
$numEntries = $this->_getFormattedNumEntriesPerPriority();
$this->_mail->setSubject(
"{$this->_subjectPrependText} ({$numEntries})");
}
// Always provide events to mail as plaintext.
$this->_mail->setBodyText(implode('', $this->_eventsToMail));
// If a Zend_Layout instance is being used, set its "events"
// value to the lines formatted for use with the layout.
if ($this->_layout) {
// Set the required "messages" value for the layout. Here we
// are assuming that the layout is for use with HTML.
$this->_layout->events = implode('', $this->_layoutEventsToMail);
// If an exception occurs during rendering, convert it to a notice
// so we can avoid an exception thrown without a stack frame.
try {
$this->_mail->setBodyHtml($this->_layout->render());
} catch (Exception $e) {
...
}
// Finally, send the mail. If an exception occurs, convert ...
// warning-level message so we can avoid an exception thrown ...
// stack frame.
try {
$this->_mail->send();
} catch (Exception $e) {
...
}
}
This shutdown method will check if there are events not yet mailed
and if there are, it will mail them to the address specified in the
Zend_Mail object which has to be within the _mail property. This
allows anyone to send out arbitrary spam to arbitrary mail addresses.
However there is a more interesting exploiting path hidden here that
once again utilizes the HTML rendering. Therefore an attacker has to
find a class that contains a render method. The most promising class
in case of Piwik is Piwik_View. Its render method will do a lot of
things that can be ignored and finally call smarty to render the
template.
public function render()
{
try {
...
} catch(Exception $e) {
// can fail, for example at installation (no plugin loaded yet)
}
...
@header('Content-Type: text/html; charset=utf-8');
@header("Pragma: ");
@header("Cache-Control: no-store, must-revalidate");
return $this->smarty->fetch($this->template);
}
Because most of the code in the render method is within a try and
catch block it can be ignored. The only interesting part of the
method is the fetch method called on the smarty property. Because
smarty templates can execute PHP code the smarty class is the first
one should look at and indeed smarty can be used to execute PHP
code.
function fetch($resource_name, $cache_id = null, ...)
{
...
if ($display && !$this->caching && count($this->_plugins['outputfilter']) == 0) {
if ($this->_is_compiled($resource_name, $_smarty_compile_path)
|| $this->_compile_resource($resource_name, $_smarty_compile_path))
{
include($_smarty_compile_path);
}
} else {
...
The supplied template name is then given to the _compile_resource
method to compile the resource.
function _compile_resource($resource_name, $compile_path)
{
$_params = array('resource_name' => $resource_name);
if (!$this->_fetch_resource_info($_params)) {
return false;
}
...
Before the resource is being compiled the _fetch_resource_info
method is called to determine more information about the resource.
function _fetch_resource_info(&$params)
{
...
$_params = array('resource_name' => $params['resource_name']) ;
...
if ($this->_parse_resource_name($_params)) {
$_resource_type = $_params['resource_type'];
$_resource_name = $_params['resource_name'];
switch ($_resource_type) {
case 'file':
...
break;
default:
// call resource functions to fetch the template source and timestamp
if ($params['get_source']) {
$_source_return = isset($this->_plugins['resource'][$_resource_type]) &&
call_user_func_array($this->_plugins['resource'][$_resource_type][0][0],
array($_resource_name, &$params['source_content'], &$this));
} else {
$_source_return = true;
}
...
}
The _fetch_resource_info method first calls _parse_resource_name to
determine the type and name of resource. The expected format is
resource_type:resource_name. For non file resources a function will
be determined by a lookup in the _plugins[resource] lookup table
that will be called to retrieve the resource data. Because all
properties are attacker supplied an exploit can freely choose what
function should be called.
Because the called function is called with three parameters and only
the first one is user supplied it gets a bit tricky to execute
arbitrary PHP code. Calling eval() is not possible because it is a
language construct and not a function and calling assert() instead
will also fail, because it only accepts one parameter. The trick
here is to use one of the methods inside the smarty class which is
a wrapper around eval().
function _eval($code, $params=null)
{
return eval($code);
}
While this function does only define two parameters in its signature
it is possible to call this function with more than two parameters.
The reason for this is that unlike internal functions that usually
bail out when too many parameters are supplied internal functions
will not do this because otherwise it would not be possible to have
functions with an arbitrary number of parameters.
So to summarize the attack: By carefully crafting a user supplied
cookie it is possible to utilize Piwik's own objects and execute
arbitrary PHP code by supplying the argument to an eval() statement.
Proof of Concept:
SektionEins GmbH is not going to release a proof of concept
exploit for this vulnerability.
Disclosure Timeline:
28. November 2009 - Notified hello@piwik.org
09. December 2009 - Piwik developers released Piwik 0.5.0
09. December 2009 - Public Disclosure
Recommendation:
It is recommended to upgrade to the latest version of Piwik.
Grab your copy at:
http://piwik.org/latest.zip
CVE Information:
The Common Vulnerabilities and Exposures project (cve.mitre.org) has
not assigned a name to this vulnerability.
GPG-Key:
pub 1024D/15ABDA78 2004-10-17 Stefan Esser
Key fingerprint = 7806 58C8 CFA8 CE4A 1C2C 57DD 4AE1 795E 15AB DA78
Copyright 2009 SektionEins GmbH. All rights reserved.