文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

php(phar)反序列化漏洞及各种绕过姿势

2023-08-31 15:44

关注

概念:

序列化其实就是将数据转化成一种可逆的数据结构,自然,逆向的过程就叫做反序列化。简单来说就是我在一个地方构造了一个类,但我要在另一个地方去使用它,那怎么传过去呢?于是就想到了序列化这种东西,将对象先序列化为一个字符串(数据),后续需要使用的时候再进行反序列化即可得到要使用的对象,十分方便。

来看看官方手册怎么说:

所有php里面的值都可以使用函数serialize()来返回一个包含字节流的字符串来表示。unserialize()函数能够重新把字符串变回php原来的值。序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法,只会保存类的名字。

为了能够unserialize()一个对象,这个对象的类必须已经定义过。如果序列化类A的一个对象,将会返回一个跟类A相关,而且包含了对象所有变量值的字符串。如果要想在另外一个文件中反序列化一个对象,这个对象的类必须在反序列化之前定义,可以通过包含一个定义该类的文件或使用函数spl_autoload_register()来实现。

php 将数据序列化和反序列化会用到两个函数:

  1. serialize() 将对象格式化成有序的字符串。
  2. unserialize() 将字符串还原成原来的对象。

序列化的目的是方便数据的传输和存储,在PHP中,序列化和反序列化一般用做缓存,比如session缓存,cookie等。

注意:php中创建一个对象和反序列化得到一个对象是有所不同的,例如创建一个对象一般会优先调用 __construct() 方法 ,而反序列化得到一个对象若 存在 __wakeup() 方法则会优先调用它而不去执行 __construct() 。

基本上每个编程语言都有各自的序列化和反序列化方式,格式也各不相同

像有:

  1. 二进制格式
  2. 字节数组
  3. json字符串
  4. xml字符串
  5. python的opCode码 

简单例子

 'dd');$serarr = serialize($arr);echo $serarr;var_dump($arr);

输出

a:3:{i:0;s:2:"aa";i:1;s:2:"bb";s:2:"cc";s:2:"dd";}array(3) {    [0]=> string(2) "aa"    [1]=> string(2) "bb"    ["cc"]=> string(2) "dd"}

输出的这一串序列表示的是什么呢?

a:3:{i:0;s:2:"aa";i:1;s:2:"bb";s:2:"cc";s:2:"dd";}

a:array代表是数组,后面的3说明有三个属性。

i:代表是整型数据int,后面的0是数组下标(O代表Object,也是类)。

s:代表是字符串,后面的2是因为aa长度为2,是字符串长度值。

后面类推。

同时要注意序列化后只有成员变量,没有成员函数。

注意如果变量前是protected,则会在变量名前加上\x00*\x00,private则会在变量名前加上\x00类名\x00,输出时一般需要url编码,如下:

name = $name;        $this->pass = $pass;    }}$a = new test('pankas', '123');$seria = serialize($a);echo $seria.'
';echo urlencode($seria);

直接输出输出则会导致不可见字符\x00的丢失。

O:4:"test":2:{s:7:"*name";s:6:"pankas";s:10:"testpass";s:3:"123";}O%3A4%3A%22test%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A6%3A%22pankas%22%3Bs%3A10%3A%22%00test%00pass%22%3Bs%3A3%3A%22123%22%3B%7D

反序列化常用魔术方法详细用法请参考 官方文档

__construct()//类的构造函数,创建类对象时调用__destruct()//类的析构函数,对象销毁时调用__call()//在对象中调用一个不可访问方法时调用__callStatic()//用静态方式中调用一个不可访问方法时调用__get()//获得一个类的成员变量时调用__set()//设置一个类的成员变量时调用__isset()//当对不可访问属性调用isset()或empty()时调用__unset()//当对不可访问属性调用unset()时被调用。__sleep()//执行serialize()时,先会调用这个函数__wakeup()//执行unserialize()时,先会调用这个函数,执行后不会执行__construct()函数__toString()//类被当成字符串时的回应方法__invoke()//调用函数的方式调用一个对象时的回应方法__set_state()//调用var_export()导出类时,此静态方法会被调用。__clone()//当对象复制完成时调用__autoload()//尝试加载未定义的类__debugInfo()//打印所需调试信息

各种绕过姿势

绕过__wakeup(CVE-2016-7124)

wakeup()魔术方法在执行unserialize()时,会优先调用这个函数,而不会执行`construct()` 函数。

绕过方法:序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行。

a = 'abc';    }    public function __wakeup(){        $this->a='def';    }    public function  __destruct(){        echo $this->a;    }}

其序列化后为 O:4:"test":1:{s:1:"a";s:3:"abc";}

执行反序列化 unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');

得到结果为 def ,发现优先执行了 __wakeup() ,并没有执行 __construct()。

当我们把对象的属性个数改大时,改成 O:4:"test":2:{s:1:"a";s:3:"abc";} ,由原来的1个属性改为2个,但test 类真实的属性只有一个,这样就能绕过 __wakeup() 按没有这个魔术方法一样去执行其他相应的魔术方法。

执行反序列化 unserialize('O:4:"test":2:{s:1:"a";s:3:"abc";}');

得到结果为 abc , 发现优先执行了 __construct() ,并没有执行 __wakeup()。

__destruct()相关

__destruct是PHP对象的一个魔术方法,称为析构函数,顾名思义这是当该对象被销毁的时候自动执行的一个函数。其中以下情况会触发__destruct。

除此之外,PHP还拥有垃圾回收Garbage collection即我们常说的GC机制。

PHP中GC使用引用计数和回收周期自动管理内存对象,那么这时候当我们的对象变成了“垃圾”,就会被GC机制自动回收掉,回收过程中,就会调用函数的__destruct。

刚才我们提到了引用计数,其实当一个对象没有任何引用的时候,则会被视为“垃圾”,即

$a = new test();

test 对象被 变量 a 引用, 所以该对象不是“垃圾”,而如果是这样

new test();

或这样

$a = new test();$a = 1;

这样在 test 在没有被引用或在失去引用时便会被当作“垃圾”进行回收。

如:

i = $i; }function __destruct() { echo $this->i."Destroy...\n"; }}new test('1');$a = new test('2');$a = new test('3');echo "————————————
";

输出

1Destroy...2Destroy...————————————3Destroy...

这里是当a第二次赋值时,test('2')失去引用,执行__destruct,然后执行echo,当程序完了后test('3')销毁,执行它的__destruct。

举个栗子:

这里我们要求输出 success!! ,但执行反序列化后得到的对象有了引用,给了 a 变量,后面程序接着就抛出一个异常,非正常结束,导致未正常完成 GC 机制,即没有执行 __destruct 。

直接构造反序列化 test 类得到:

所以我们要反序列化手动去 “销毁” 创造的对象。这里我们可以利用数组来完成。构造:

class test {}$a = serialize(array(new test, null));echo $a.'
';$a = str_replace(':1', ':0', $a);//将序列化的数组下标为0的元素给为nullecho $a;

得到

a:2:{i:0;O:4:"test":0:{}i:1;N;}a:2:{i:0;O:4:"test":0:{}i:0;N;}//最终payload

传入,成功得到 success!!

我们序列化一个数组对象,考虑反序列化本字符串,因为反序列化的过程是顺序执行的,所以到第一个属性时,会将Array[0]设置为对象,同时我们又将Array[0]设置为null,这样前面的test对象便丢失了引用,就会被GC所捕获,就可以执行__destruct了。

绕过正则

如preg_match('/^O:\d+/')匹配序列化字符串是否是对象字符串开头。

绕过方法

  • 利用加号绕过(注意在url里传参时+要编码为%2B)。
  • 利用数组对象绕过,如 serialize(array($a)); a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)。
a = 'abc';    }    public function  __destruct(){        echo $this->a.PHP_EOL;    }} function match($data){    if (preg_match('/^O:\d+/',$data)){        die('nonono!');    }else{        return $data;    }}$a = 'O:4:"test":1:{s:1:"a";s:3:"abc";}';// +号绕过$b = str_replace('O:4','O:+4', $a);unserialize(match($b));// 将对象放入数组绕过 serialize(array($a));unserialize('a:1:{i:0;O:4:"test":1:{s:1:"a";s:3:"abc";}}');

利用引用绕过

如下,要求输出 you success,但构造的序列化字符串中不能由 aaa

a = 'aaa';    }    public function __destruct(){         if($this->a === $this->b) {            echo 'you success';        }    }}if(isset($_REQUEST['input'])) {    if(preg_match('/aaa/', $_REQUEST['input'])) {       die('nonono');    }    unserialize($_REQUEST['input']);}else {    highlight_file(__FILE__);}

可以利用引用进行绕过

class test {    public $a;    public $b;    public function __construct(){        $this->b = &$this->a;    }}$a = serialize(new test());echo $a;//O:4:"test":2:{s:1:"a";N;s:1:"b";R:2;}

构造引用使得 $b 和 $a 地址相同从而绕过检测,达成要求。

16进制绕过字符的过滤

序列字符串中表示字符类型的s大写时,会被当成16进制解析。

举个栗子:

username = 'admin';    }    public function  __destruct(){        echo 'success';    }}function check($data){    if(preg_match('/username/', $data)){        echo("nonono!!!
"); } else{ return $data; }}// 未作处理前,会被waf拦截$a = 'O:4:"test":1:{s:8:"username";s:5:"admin";}';$a = check($a);unserialize($a);// 将小s改为大S; 做处理后 \75是u的16进制, 成功绕过$a = 'O:4:"test":1:{S:8:"\\75sername";s:5:"admin";}';$a = check($a);unserialize($a);

输出

phar反序列化

有关phar的基本介绍及利用方法

同时应该重点关注官方文档中有关 phar 的介绍 (一定要关注官方文档,官方文档yyds)

生成phar

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

ps:注意phar中存储的对象反序列化之后会被phar对象的metadata属性引用。

一些绕过方式

当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://等绕过。

compress.bzip://phar:///test.phar/test.txtcompress.bzip2://phar:///test.phar/test.txtcompress.zlib://phar:///home/sx/test.phar/test.txt

也可以利用其它协议, 如 filter 过滤器。

php://filter/read=convert.base64-encode/resource=phar://phar.phar

GIF格式验证可以通过在文件头部添加GIF89a绕过。

$phar->setStub(“GIF89a”.""); //设置stub//生成一个phar.phar,修改后缀名为phar.gif

过滤了__HALT_COMPILER();

参考 https://guokeya.github.io/post/uxwHLckwx (原理)

姿势1:

**将phar文件进行gzip压缩** ,使用压缩后phar文件同样也能反序列化 (常用)

linux下使用命令gzip phar.phar 生成。

姿势2:

将phar的内容写进压缩包注释中,也同样能够反序列化成功,压缩为zip也会绕过
$phar_file = serialize($exp);echo $phar_file;$zip = new ZipArchive();$res = $zip->open('1.zip',ZipArchive::CREATE);$zip->addFromString('crispr.txt', 'file content goes here');$zip->setArchiveComment($phar_file);$zip->close();

phar 文件签名修改

对于某些情况,我们需要修改phar文件中的内容而达到某些需求(比如要绕过__wakeup要修改属性数量),而修改后的phar文件由于文件发生改变,所以须要修改签名才能正常使用,官方文档中是这么说:

Phar Signature format(https://www.php.net/manual/zh/phar.fileformat.signature.php#phar.fileformat.signature

Phars containing a signature always have the signature appended to the end of the Phar archive after the loader, manifest, and file contents. The signature formats supported at this time are MD5, SHA1, SHA256, SHA512, and OPENSSL.

用winhex或010-editor查看phar文件签名类型(以上述代码生成的phar文件为例)

以默认的sha1签名为例:

from hashlib import sha1with open('phar.phar', 'rb') as file:    f = file.read()     # 修改内容后的phar文件,以二进制文件形式打开 s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)h = f[-8:] # 获取签名类型以及GBMB标识,各4个字节newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB) with open('newPhar.phar', 'wb') as file:    file.write(newf) # 写入新文件

来源地址:https://blog.csdn.net/MrWangisgoodboy/article/details/130146658

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯