Unserialize反序列化

前置知识

类与对象

类内变量类型
变量类型
public 
private
protected
魔术方法
__construct() //创建对象(实例化类)时调用
__wakeup() //执行unserialize()时,先会调用这个函数
__sleep() //执行serialize()时,先会调用这个函数
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据或者不存在这个键都会调用此方法
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试将对象调用为函数时触发

相关函数

unserialize(): 将传入字符串反序列化;serialize(): 将传入变量序列化

与之类似的还有:json_encode()和json_decode()

序列化与反序列化

序列化是将(任意类型)变量以特定的规则转化成一串字符串;可以用更少的空间保存对象里的信息,便于数据传输;反序列化则是根据规则还原字符串所对应的变量。

如果变量类型是 protected,则会在变量名前加上 %00*%00,private 则会在变量名前加上 %00类名%00,(这里不可见字符用ur编码表示),可以用其他编码方式可视化。

反序列化漏洞

感受漏洞
<?php  
// 定义一个名为 test 的类  
class test {  
    // 定义一个公共属性 $str  
    public $str;  
  
    // 构造函数,当创建类的新实例时自动调用  
    public function __construct() {  
        // 初始化 $str 属性为 "I am here!"  
        $this->str = "I am here!";  
    }  
  
    // 析构函数,当对象不再被引用时自动调用  
    public function __destruct() {  
        // 输出 "die" 并换行  
        echo "die".'<br>';  
    }  
}  
  
// 创建 test 类的一个新实例,并赋值给变量 $testobject1  
$testobject1 = new test();   
  
// 使用 var_dump 输出 $testobject1 的详细信息,包括其属性和类型  
var_dump($testobject1);  
  
// 使用 serialize 函数将 $testobject1 对象序列化为字符串,并赋值给 $teststr1  
$teststr1 = serialize($testobject1);  
  
// 输出序列化后的字符串 $teststr1 并换行  
echo $teststr1.'<br>';  
  
// 使用 str_replace 函数将 $teststr1 中的 "I am here!" 替换为 "I like it!",结果赋值给 $teststr2  
$teststr2 = str_replace("I am here!","I like it!",$teststr1);  
  
// 输出替换后的字符串 $teststr2 并换行  
echo $teststr2.'<br>';  
  
// 尝试使用 unserialize 函数将 $teststr2 反序列化为对象,并使用 var_dump 输出结果  
// 注意:这里的操作是不安全的,因为 $teststr2 是被修改过的序列化字符串  
var_dump(unserialize($teststr2));  
?>

<?php
  class test{
    public $str;
    public function __construct(){
      $this->str = "I am here!";
    }
    public function __destruct(){
      echo "die".'<br>';
    }
  }
//GET请求允许用户在浏览器url中输入内容,造成漏洞
var_dump(unserialize($_GET["str"]));
?>

上面的例子直观感受了反序列化漏洞的作用之一,更改键值对;在大部分情况下,我们要用魔术方法完成题目,最主要的是 __destruct ,一般情况下在这里面的是可供我们利用的代码;由此也引出了第一个防护:__wakeup 魔术方法

千奇百怪绕过法

绕过wakeup

__wakeup()可以起到防护作用是因为它在反序列化时调用,可以改变变量的值或终止程序进行。

绕过的方法很简单,在 PHP5<5.6.25;PHP7<7.0.10>以及一些高版本中通过修改格式可以阻止调用 __wakeup()

修改方法:增大表示对象中表示变量数量的数字

绕过正则

正则是直接检查字符串来确定是否安全,一般是匹配关键词来完成操作

Aa不敏感

PHP对类名的大小写不敏感

S16进制绕过
0:4:"test":2:{s:1:"a";s:3:"abc";s:4:"testb";s:3:"def";}
可以写成
0:4:"test":2:{s:1:"\61";s:3:"abc";s:7:"testb";s:3:"def";}
表示字符类型的s大写时,会被当成16进制解析。

代表字符串的 s 换成 S 时,表示对应字符串采用16进制编码

/^O:\d+/

这个正则表示匹配 O:数字 这种结构

+

O:+数字

字符串逃逸

分为两种类型,可以这样利用是因为反序列化是按照规则读取字符串信息

字符增多型
<?php
include('flag.php');
class test{
  public $a;
  public $b;
  public function __construct($aa,$bb){
    $this->a = $aa;
    $this->b = $bb;
  }
}
$a=$_GET['a'];
$b="you con not change it";
$testobject1 = new test($a,$b);

// 序列化对象$testobject1,将其转换为字符串,并赋值给$str  
$str=serialize($testobject1);
echo $str.'<br>';
 
// 将$str中的"x"替换为"oh",这里有一个问题:如果$str中不存在"x",则替换不会发生  
$str=str_replace("x","oh",$str);
echo $str;
// 反序列化字符串$str,将其转换回对象,并赋值给$testobject2  
$testobject2 =unserialize($str);
// 检查$testobject2的属性$b是否等于字符串"SUCCESS"
if($testobject2->b ==="SUCCESS")
{
    // 如果属性$b等于"SUCCESS",则输出$flag的值  
	echo $flag;
}
?>

上述代码中假设flag.php存在,则本段代码如何能使得最后一个if语句为真,并输出flag

flag.php

<?php
$flag="this is flag";

正常执行结果:

方法:将传入的字符串当作键值对来读取,就可以实现修改值的操作,后面的部分则会被丢弃。

";s:1:"b";s:7:"SUCCESS";}  共用25个字符,第一个"用来构造闭合

一个x顶2个oh,构造方程,x+25=2x,x代表x的数量,得x=25,所以需要25个x

字符减少型
<?php
// file_get_contents('flag.phar');
include('flag.php');
	class test{
	public $a;
	public $b;
    public $flag;
	public function __construct($aa,$bb){
		$this->a = $aa;
		$this->b = $bb;
         $this->flag = "no";
    }
}
$a=$_GET['a'];
$b=$_GET['b'];
$testobject1 = new test($a,$b);
$str=serialize($testobject1);
echo $str.'<br>';
$str=str_replace("oh","x",$str);
echo $str;
$testobject2=unserialize($str);
if($testobject2->flag === "yes")
{
    echo $flag;
}

正常传值

传入的值包含oh

方法:将键名当作值来读取。

b参数传值时需传入一组键值对

session反序列化
php.ini配置项

session.save_path=""---->设置session的存储路径
session.save_handler="“---->设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)

session.auto_start boolen --->指定会话模块是否在请求开始时启动一个会话,默认为0不启动

session.serialize_handler string --->定义用来序列化反序列化的处理器名字,默认使用php

配置项

含义

session.save_path="D:\xamppl\mp'

表明所有的session文件都是存储在xampp/tmp下

session.save_handler=files

表明session是以文件的方式来进行存储的

session.auto_start=0

表明默认不启动session

session.serialize_handler=php

表明session的默认序列话引擎使用的是php序列话引擎

session.serialize_handler 是用来设置session的序列话引擎的,除了默认的 php 引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。假设有$_SESSION['name']='hjbhjb'

引擎

存储方式

例子

php

键名+ | +经过serialize()函数序列处理的值

name | s:6:"hjbhjb"

php_serialize

经过serialize()函数序列化处理的值

a:1:{s:4:"name";s:6:"hjbhjb";}

php_binary

键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

%04names :6 :"spoock"

在 php_binary 的例子中由于 name 的长度是4,4在ASCII表中对应的就是 EOT ,为不可见字符,所以这里用了URL编码的格式,实际上在不特殊编码的情况下我们看到的是 names:6:"spoock"

在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码 ini_set('session.serialize_handler','引擎”)或更改默认引擎

漏洞原因

不同页面之间使用了不同的处理引擎,造成键值对解析歧义导致漏洞,看下面的例子

//first.php
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['TEST'] ='|0:"A":1:{s:1:"a";s:4:"test";}';
var_dump($_SESSION);
//second.php<?php
ini_set('session.serialize_handler', 'php');
session_start();
#$_SESSION['TEST']" = '|0:1:"A":1:({s:1:"a";s:4:"test";}';
class A {
	public $a = 'aa';
	function __wakeup(){
		echo $this->a;
    }
}
var_dump($_SESSION);
//test
//var_dump()

$SESSION 的值发生了改变,另外,在例子中,second.php页面回显了test,说明反序列化的执行

分析

这是因为当使用php引擎的时候,php引擎会以 | 作为key和value的分隔符,那么就会将 a:1:{s:4:"ryat”;s:30:"}作为SESSION的key,将 0:1:"A":1:{s:1:"a”;s:2:”xx";}作为value,然后进行反序列化,最后就会得到A这个类。

这种由于序列化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。

phar反序列化

当读取phar文件,内部的序列化字符串会自动反序列化

phar文件结构

构成部分

作用与要求

stub

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

manifest

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

content

被压缩文件的内容

signature

可以为空,签名,放在未尾

产生phar格式的文件

<?php
	class Test {
	//看情况自定义
	}	
	//要反序列化的字符串
	@unlink("phar.phar");
	$phar =new Phar("phar.phar");//后缀名必须为phar
	$phar->startBuffering();
	$phar->setstub("<?php __HALT_COMPILER(); ?>"); //设置stub
	$o= new Test();
	$phar->setMetadata($o);//将自定义的meta-data存入manifest
	$phar->addFromstring("test.txt","test");//添加要压缩的文件
	//签名自动计算
	$phar->stopBuffering();
?>
漏洞利用条件
  1. phar文件要能够上传到服务器端。
  2. 要有可用的魔术方法作为“跳板”。
  3. 文件操作函数的参数可控,且:、/、 'phar' 等特殊字符没有被过滤。(绕过放到文件包含专题讲)
受影响函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值