Video at: https://www.youtube.com/watch?v=PqsudKzs79c
An introduction to PHP unserialization vulnerabilities, with some practical tips on methodology. Based around three new exploits for old vulnerabilities (CVE-2011-4962, CVE-2013-1453, CVE-2013-4338).
3. 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)
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 for old 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
11. The exploit technique
Code reuse
ROP POP
ret2libc
Return
Oriented
Programming
Property
Oriented
Programming
12. Agenda
• What is PHP (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’
16. Object
public class className
{
private $prop1 = ‘value1’;
protected $prop2 = ‘value2’;
public $prop3 = ‘value3’;
public function meth1()
{
}
}
$x = new className();
17. Agenda
• What is PHP Unserialization?
• Why is it exploitable?
• Let’s exploit it!
20. __wakeup
• Invoked on unserialization
• Reinitialise database connections
• Other reinitialisation tasks
21. __destruct
• Invoked on garbage collection
• Clean up references
• Finish any unfinished business
• Often interesting things happen here!
22. __toString
• Invoked when an object is treated as a string:
echo $object;
• Can contain complex rendering methods
23. __call
Invoked when an undefined method is called
$object->foobar($args)=$object->__call(‘foobar’,$args)
24. 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”
25. Weak typing
“PHP does not require (or support) explicit type
definition in variable declaration”
variables (->object properties) can take any value
26. All functions have variable arguments
foobar() = foobar(null) = foobar(null, null)
27. Weak typing + variable args
=
Many possible POP chains
28. Agenda
• What is PHP Unserialization?
• Why is it exploitable?
• Let’s exploit it!
29. 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
31. 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
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 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
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 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
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
40. Possible start points
• 0 x “function __wakeup”
• 5 x “function __destruct”
• MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite,
Zend_Log
41. 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
42. 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
43. 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
44. 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
45.
46. 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
47. 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
56. 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
58. 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
59. 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’
60. Tom Van Goethem
Genius insight
=
We can abuse this to screw with WordPress’
unserialization
61. 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
63. 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
67. /**
* 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
68. 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
69.
70.
71.
72. 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
73. /**
* 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
74. /**
* 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
81. 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)
82. 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
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 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
85. 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
86. 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
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 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
90. /**
* 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
91. 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)
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
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
104. 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
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 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
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
109.
110.
111. 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!
112. 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