Php_serialize_summary

本篇 Blog 的内容更像是一页目录,主要记录在做题过程中所遇到的关于 php 反序列化 类型的题目

以及对此类型题目所以需要的知识点进行总结,分类,可点击 相关题目中的链接以查看题目的具体解答过程.

PHP 反序列化还不了解的朋友可以阅读下面这篇文章,可以帮助你对其有个大概的了解:

一篇文章带你深入理解漏洞之 PHP 反序列化漏洞

简单总结:

  1. php 序列化和反序列化的出现为了解决 :PHP 在对象传递时会因为 PHP 文件的执行结束而将对象销毁销毁,当我们需要用的被销毁的对象是又得重新new一个.为了能让对象长久保存,于是就出现了php序列化
  2. 序列化他只序列化属性,不序列化方法
  3. **反序列化的时候一定要保证在当前的作用域环境下有该类存在.**反序列化就是将我们压缩格式化的对象还原成初始状态的过程(可以认为是解压缩的过程),因为我们没有序列化方法,因此在反序列化以后我们如果想正常使用这个对象的话我们必须要依托于这个类要在当前作用域存在的条件
  4. 了解魔法方法对php反序列化影响的原因:是因为魔法方法的调用是在该类序列化或者反序列化的同时自动完成的,不需要人工干预,因此可以通过魔法方法来达到更改属性的目的

0x01 PHP魔法函数

1.1 __construct() 构造函数

/*
当对象被初始化时会调用 __construct() 构造函数
*/

class Demo{
    function __construct(){
        echo 'you called the __construct function...'
    }
}

$demo = new Demo();
# 'you called the __construct funtion...'

1.2 __destruct() 析构函数

/*
当对象被销毁时,会调用 __destruct() 析构函数,
对象销毁存在主动和被动两种情况
主动:例如使用 unset()函数
被动:当程序结束时自动销毁


example:*/
    class Demo{
        function __destruct(){
            echo "you called the __destruct function..";
        }
    }

    $demo = new Demo();
    #unset($demo); ## 主动销毁
	// sting "you called the __destruct function.."


	## 程序结束自动销毁
	//stirng "you called the __destruct function.."

1.3 __call()

/*
PHP5 的对象新增了一个专用方法 __call(),这个方法用来监视一个对象中的其它方法。如果你试着调用一个对象中不存在或被权限控制中的方法,__call 方法将会被自动调用。

example:*/

<?php
class Test
{
    function __call($name,$arg){
        echo "you called the __call funtion....";
    }

    private function Persional($name,$age){
        echo "This is private function which named Persional...";
    }
}


$test = new Test();
$test->isPayload();
echo "<br/>";
$test->Personal('php','123');

结果如下:

image-20210826021133365

可以发现,当我们调用类中不存在的方法或者不可调用的方法,都会调用__call()方法

1.4 __callStatic()

1.5 __get()

/*
面向对象编程中使用频率很高的方法.当设置和获取对象的属性不允许访问时性,此方法会被调用。一定注意是不存在或不允许被读写时才会被调用。
因此对于一个对象,其属性不确定时,用这个方法效果很好。

__get($name) 获取对象不存在的属性或无法访问的属性时调用$name表示要获取的属性名

example:*/
class Demo{
    private $passwd;
    funtion __get($propertyName){
        # 获取不存在的属性 $name, 调用此方法
        if($propertyName == 'name'){
        echo "variable name is undefined...";
        }
        elseif($propertyName == 'passwd'){
            echo "permission deny...";
        }
    }
}

$demo = new Demo();
$demo->name;
// string "variable name is undefined..."
$demo->passwd:
// string "permission deny..."

1.6 __set()

/*
面向对象编程中使用频率很高的两个方法.当设置和获取对象的属性不允许访问时性,此方法会被调用。一定注意是不存在或不允许被读写时才会被调用。
因此对于一个对象,其属性不确定时,用这个方法效果很好。

__set($name, $value) 设置对象不存在的属性或无法访问的属性时调用.$name表示要设置的属性名,$value表示要设置的值. 

example:*/
    class Demo{
        private $passwd;
        function __set($propertyName,$value){
            if($propertyName == 'name'){
                echo "variable name is not defined...";
            }elseif ($propertyName == 'passwd'){
                echo "permission deny...";
            }
        }
    }

    $demo = new Demo();
    $demo->name = '123';
	//	string "variable name is not defined...""
    $demo->passwd = 'php';
	// string "permission deny..."

1.7 __isset()

1.8 __unset()

1.9 __sleep()

/*

当执行 serialize()操作时,会先检查是否存在 __sleep() 函数,如果存在,则在序列化之前调用 __sleep() 函数。

example:*/

class Demo{
    public $test="helloworld";
    function __sleep(){
        echo "you called the __sleep function...";
    }
}

$demo = new Demo();
$a = serialize($demo);
// string "you called the __sleep function..."

1.10 __wakeup()

/*

当执行 unserialize()操作时,会先检查是否存在 __wakeup() 函数,如果存在,则优先调用 __wakeup() 函数。
简单理解,如果执行 unserialize() 函数,随后就会执行 __wakeup() 函数,前提是该函数存在。

example:*/

class Demo{
    public $test="helloworld";
    function __wakeup(){
        echo "you called the __wakeup function...";
    }
}

$demo = new Demo();
$a = serialize($demo);
unserialize($a);
// string "you called the __wakeup function..."
1.10.1 考点(CVE-2016-7124)

当成员属性数目大于实际数目时可绕过wakeup方法

题目文章

php反序列化漏洞绕过魔术方法 __wakeup

[漏洞利用] CVE-2016-7124 漏洞复现(总结自一CTF题目)

1.11 __toString()

/*作用
__toString()方法用于一个类被当成字符串时应怎样回应。例如 `echo $obj;` 应该显示些什么。

example:*/
class Demo{
    function __toString(){
         return "helloworld";
    }
}

$demo = new Demo();
echo $demo;

// string "helloworld"

1.12 __invoke()

/*php5.3中新增加的_invoke方法 
实例化对象本身是不能被调用,但是类中如果实现 __invoke() 方法,则把实例对象当作方法调用,会自动调用到 __invoke() 方法,参数顺序相同。


example:*/

class Test{

    function __invoke($name,$age){
        echo "you called the invoke funtion...";
        var_dump($name);
        echo "<br/>";
        var_dump($age);
    }
}

$test = new Test();
$test('hello',123);

结果如图所示:

image-20210826023012331

1.13 __set_state()

1.14 __clone()

1.15 __autoload()

1.16 __debugInfo()

0x02 phar 反序列化

2.1 Phar 文件结构

phar 文件结构由四部分组成:

  1. stub: phar文件的标识, php通过判断stub来判断这是一个phar文件.格式为xxx<?php xxx;__HALT_COMPILER();?> 前面个的内容 xxxx 不受限制,但是必须以 __HALT_COMPILER();?
  2. mainfest: 被压缩文件的相关属性等信息都会被存储在这部分.且这部分还会以 序列化serialize()的形势存储用户自定义的 meta-data,这是 Phar 反序列化攻击的关键.
  3. contents: 被压缩文件的内容
  4. signature: 放在末尾的签名

2.2 常用考点1 (文件类型)

在做题过程中,phar 反序列化的常用利用点主要有两个:

  1. stub: 由于 stub 开头部分的内容没有任何限制,我们可以使用一些文件头来绕过题目中的上传限制.常见的文件类型及文件头如下:
文件类型 文件头
.JPEG .JPE .JPG JPGGraphic File
.gif GIF 89A
.zip Zip Compressed
.doc .xls .xlt .ppt .apr MS Compound Document v1 or Lotus Approach APRfile

构造stub形如

setStub("GIF89a<?php __HALT_COMPILER(); ?>")
  1. minfest: minfest会将meta-data以反序列化的形势,如果存在部分文件操作函数通过phar://伪协议解析phar文件,则在解析phar文件时就会触发反序列化.

举例演示

序列化演示

注意:要将 php.ini 中的 phar.readonly 选项设置为off ,否则无法生成phar文件.

<?php
    class Demo{
    public $test="helloworld";
}
	$phar = new Phar("phar.phar");
	$phar->startBuffering();
	$phar->setStub("<?php __HALT_COMPILER();?>");
	$demo = new Demo();
	$phar->setMetadata($demo);
	$phar->addFromString("test","test");
	$phar->stopBuffering();

运行上述php程序,可以看到在当前目录下生成了一个 phar.phar 文件.随便用个编辑器打开phar.phar

看到以下内容:

image-20210721145619585

很明显可以看到我们刚才实例化的对象$demo被序列化存储在phar.phar中.

反序列化演示

前面说过,使用部分文件操作函数通过phar://伪协议解析phar文件时,会将 phar中的metadata进行反序列化

根据知道创宇18年发布的文章,能使 metadata进行反序列化的文件操作函数有:

参考这篇文章,又得一些新的可用函数

exif

  • exif_thumbnail
  • exif_imagetype

gd

  • imageloadfont
  • imagecreatefrom***

hash

  • hash_hmac_file
  • hash_file
  • hash_update_file
  • md5_file
  • sha1_file

file / url

  • get_meta_tags
  • get_headers

standard

  • getimagesize
  • getimagesizefromstring

演示:

<?php
    class Demo{
    public function __destruct() {
        echo 'Destruct called';
    }
}
    $filename = "phar://phar.phar/test";
	var_dump(file_get_contents($filename));

image-20210721152537808

2.2.3 常见考点2,3 (数字签名和文件内容判断)

除了最基本的反序列化外, phar 的最常见考点就是数字签名.

详细分析可以去看看guokeya师傅的博客从虎符线下CTF深入反序列化利用,这里就直接说结果了.

  1. .phar 的文件签名可以抛弃/构造

  2. gzip tar zip bz2 压缩 .phar 文件后, 仍让能够使用 phar:// 协议 触发反序列化.

例子:

  1. 签名抛弃构造
<?php
    class a{
    public $a;
    public function __weakup(){
        $this->a = "no";
    }
    public function __destruct(){
        if ($this->a = "flag"){
            echo $flag;
        }
    }
}
	
	$a = serialize(new a());
	$a = str_replace('"a":1','"a":2',$a)
    file_put_contents('.phar/.metadata',serialize($a))

随后执行 tar -cf test.tar .phar/

  1. 签名构造
from hashlib import sha1
f = open('../phar.phar', 'rb').read() # 修改内容后的phar文件
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
open('22.phar', 'wb').write(newf) # 写入新文件
  1. 内容压缩

    对生成的 phar.phar文件执行

    gzip phar.phar就行了.

2.4 参考文章

https://paper.seebug.org/680/

2.5 相关题目

PHP反序列化入门之phar

Post not found: GXYCTF2019BabysqliV3-0

0x03 unserialize() 反序列化字符逃逸

反序列化字符逃逸知识

<?php
$a = array("1","2","3");
$b = serialize($a);
var_dump($b);
// 输出: string(42) "a:3:{i:0;s:1:"1";i:1;s:1:"2";i:2;s:1:"3";}"

可以看到序列化的结果是以 ";} 结尾

假设我们能使上面的结果从 a:3:{i:0;s:1:"1";i:1;s:1:"2";i:2;s:1:"3";}

变为 a:3:{i:0;s:1:"1";i:1;s:1:"2";i:2;2:1:"4";}"i:2;s:1:"3";}

即使 结束字符";}提前,就能让反序列化提前闭合结束.那么就能造成反序列化字符逃逸.

如果不好理解,我们假设有模板  a:3:{i:0;s:1:"1";i:1;s:1:"{%TMP%}";i:2;s:1:"3";}
其中模板中的 {%TMP%} 可以是任何字符, 
式子1为:  a:3:{i:0;s:1:"1";i:1;s:1:"2";i:2;s:1:"3";}   即将 {%TMP%} 替换为了 2
式子2为:  a:3:{i:0;s:1:"1";i:1;s:1:"2";i:2;s:1:"4";}"i:2;s:1:"3";}  即将 {%TMP%}  替换为了2";i:2;s:1:"4";}

反序列化 变化后的式子

var_dump(unserialize('a:3:{i:0;s:1:"1";i:1;s:1:"2";i:2;s:1:"4";}"i:2;s:1:"3";}'));

/* 结果如下:
array(3) {
  [0]=>
  string(1) "1"
  [1]=>
  string(1) "2"
  [2]=>
  string(1) "4"
}

可见 我们以及使用 新添加的内容 覆盖了原来数组的 第二个元素
*/

这种考点的特征:在序列化和反序列化对象之间,对对象字符串进行了处理.: 可能是在序列化后,对序列化后的字符进行了处理然后保存, 也可能是在反序列化之前进行了替换更改.

相关文章

Post not found: 安洵杯2019easy-serialize-php Post not found: 0ctf2016piapiapia

持续完善中…